@oh-my-pi/pi-catalog 16.0.4 → 16.0.6

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.
@@ -1,6 +1,6 @@
1
1
  import * as http2 from "node:http2";
2
2
  import { create, fromBinary, toBinary } from "@bufbuild/protobuf";
3
- import { z } from "zod/v4";
3
+ import { type } from "arktype";
4
4
  import { getBundledModels } from "../models";
5
5
  import { toModelSpec } from "../provider-models/bundled-references";
6
6
  import type { Model, ModelSpec } from "../types";
@@ -13,27 +13,34 @@ const CURSOR_GET_USABLE_MODELS_PATH = "/agent.v1.AgentService/GetUsableModels";
13
13
  const DEFAULT_CONTEXT_WINDOW = 200_000;
14
14
  const DEFAULT_MAX_TOKENS = 64_000;
15
15
 
16
- const OptionalDisplayNameSchema = z.string().optional().catch(undefined);
17
- const CursorAliasesSchema = z
18
- .array(z.unknown())
19
- .optional()
20
- .catch([])
21
- .transform(aliases => (aliases ?? []).filter((alias: unknown): alias is string => typeof alias === "string"));
22
-
23
- const CursorModelDetailsSchema = z.object({
24
- modelId: z.string(),
25
- displayName: OptionalDisplayNameSchema,
26
- displayNameShort: OptionalDisplayNameSchema,
27
- displayModelId: OptionalDisplayNameSchema,
28
- aliases: CursorAliasesSchema,
29
- thinkingDetails: z.unknown().optional(),
16
+ const OptionalDisplayNameSchema = type("unknown").pipe(raw => (typeof raw === "string" ? raw : undefined));
17
+ const CursorAliasesSchema = type("unknown").pipe(raw => {
18
+ if (Array.isArray(raw)) {
19
+ return raw.filter((alias: unknown): alias is string => typeof alias === "string");
20
+ }
21
+ return [];
22
+ });
23
+
24
+ const CursorModelDetailsSchema = type({
25
+ modelId: "string",
26
+ displayName: OptionalDisplayNameSchema.default(undefined),
27
+ displayNameShort: OptionalDisplayNameSchema.default(undefined),
28
+ displayModelId: OptionalDisplayNameSchema.default(undefined),
29
+ aliases: CursorAliasesSchema.default(() => []),
30
+ "thinkingDetails?": "unknown",
31
+ });
32
+
33
+ const CursorModelsInnerSchema = type("unknown[]");
34
+ const ResilientCursorModelsSchema = type("unknown").pipe(raw => {
35
+ const out = CursorModelsInnerSchema(raw);
36
+ return out instanceof type.errors ? [] : out;
30
37
  });
31
38
 
32
- const CursorDecodedResponseSchema = z.object({
33
- models: z.array(z.unknown()).optional().catch([]),
39
+ const CursorDecodedResponseSchema = type({
40
+ models: ResilientCursorModelsSchema.default(() => []),
34
41
  });
35
42
 
36
- type CursorModelDetailsValue = z.infer<typeof CursorModelDetailsSchema>;
43
+ type CursorModelDetailsValue = typeof CursorModelDetailsSchema.infer;
37
44
 
38
45
  /**
39
46
  * Options for fetching dynamic Cursor models from `GetUsableModels`.
@@ -74,13 +81,13 @@ export async function fetchCursorUsableModels(
74
81
  return null;
75
82
  }
76
83
  const decoded = decodeGetUsableModelsResponse(responseBuffer);
77
- const parsedDecoded = CursorDecodedResponseSchema.safeParse(decoded);
78
- if (!parsedDecoded.success) {
84
+ const parsedDecoded = CursorDecodedResponseSchema(decoded);
85
+ if (parsedDecoded instanceof type.errors) {
79
86
  return null;
80
87
  }
81
88
 
82
89
  const references = createCursorReferenceMap();
83
- return normalizeCursorModels(parsedDecoded.data.models, options.baseUrl, references);
90
+ return normalizeCursorModels(parsedDecoded.models, options.baseUrl, references);
84
91
  } catch {
85
92
  return null;
86
93
  }
@@ -254,12 +261,12 @@ function normalizeCursorModel(
254
261
  baseUrlOverride: string | undefined,
255
262
  references: Map<string, ModelSpec<"cursor-agent">>,
256
263
  ): ModelSpec<"cursor-agent"> | null {
257
- const parsedModel = CursorModelDetailsSchema.safeParse(model);
258
- if (!parsedModel.success) {
264
+ const parsedModel = CursorModelDetailsSchema(model);
265
+ if (parsedModel instanceof type.errors) {
259
266
  return null;
260
267
  }
261
268
 
262
- const details = parsedModel.data;
269
+ const details = parsedModel;
263
270
  const id = details.modelId.trim();
264
271
  if (!id) {
265
272
  return null;
@@ -1,4 +1,4 @@
1
- import { z } from "zod/v4";
1
+ import { type } from "arktype";
2
2
  import { getBundledModels } from "../models";
3
3
  import { toModelSpec } from "../provider-models/bundled-references";
4
4
  import type { FetchImpl, Model, ModelSpec } from "../types";
@@ -7,36 +7,45 @@ const GOOGLE_GENERATIVE_AI_BASE_URL = "https://generativelanguage.googleapis.com
7
7
  const DEFAULT_PAGE_SIZE = 100;
8
8
  const DEFAULT_MAX_PAGES = 25;
9
9
 
10
- const geminiModelListItemSchema = z.object({
11
- name: z.string().optional().catch(undefined),
12
- displayName: z.string().optional().catch(undefined),
13
- supportedGenerationMethods: z.array(z.string()).optional(),
14
- inputTokenLimit: z.number().finite().optional().catch(undefined),
15
- outputTokenLimit: z.number().finite().optional().catch(undefined),
10
+ const resilientString = type("unknown").pipe(val => {
11
+ if (val === undefined) return undefined;
12
+ const out = type("string")(val);
13
+ return out instanceof type.errors ? undefined : out;
16
14
  });
17
15
 
18
- const geminiModelListResponseSchema = z.object({
19
- models: z
20
- .array(z.unknown())
21
- .optional()
22
- .transform(items => {
23
- if (!items) {
24
- return [];
25
- }
26
- const parsedItems: GeminiModelListItem[] = [];
27
- for (const item of items) {
28
- const parsed = geminiModelListItemSchema.safeParse(item);
29
- if (parsed.success) {
30
- parsedItems.push(parsed.data);
31
- }
32
- }
33
- return parsedItems;
34
- }),
35
- nextPageToken: z.string().optional().catch(undefined),
16
+ const resilientNumber = type("unknown").pipe(val => {
17
+ if (val === undefined) return undefined;
18
+ const out = type("number")(val);
19
+ return out instanceof type.errors ? undefined : out;
20
+ });
21
+
22
+ const geminiModelListItemSchema = type({
23
+ "name?": resilientString,
24
+ "displayName?": resilientString,
25
+ "supportedGenerationMethods?": "string[]",
26
+ "inputTokenLimit?": resilientNumber,
27
+ "outputTokenLimit?": resilientNumber,
36
28
  });
37
29
 
38
- type GeminiModelListItem = z.infer<typeof geminiModelListItemSchema>;
30
+ type GeminiModelListItem = typeof geminiModelListItemSchema.infer;
39
31
 
32
+ const modelsSchema = type("unknown[]")
33
+ .pipe(items => {
34
+ const parsedItems: GeminiModelListItem[] = [];
35
+ for (const item of items) {
36
+ const parsed = geminiModelListItemSchema(item);
37
+ if (!(parsed instanceof type.errors)) {
38
+ parsedItems.push(parsed);
39
+ }
40
+ }
41
+ return parsedItems;
42
+ })
43
+ .default(() => []);
44
+
45
+ const geminiModelListResponseSchema = type({
46
+ models: modelsSchema,
47
+ "nextPageToken?": resilientString,
48
+ });
40
49
  /**
41
50
  * Configuration for Google Generative AI model discovery.
42
51
  */
@@ -103,19 +112,19 @@ export async function fetchGeminiModels(
103
112
  return null;
104
113
  }
105
114
 
106
- const parsed = geminiModelListResponseSchema.safeParse(payload);
107
- if (!parsed.success) {
115
+ const parsed = geminiModelListResponseSchema(payload);
116
+ if (parsed instanceof type.errors) {
108
117
  return null;
109
118
  }
110
119
 
111
- for (const item of parsed.data.models) {
120
+ for (const item of parsed.models) {
112
121
  const model = normalizeModel(item, baseUrl, bundledById);
113
122
  if (model) {
114
123
  modelsById.set(model.id, model);
115
124
  }
116
125
  }
117
126
 
118
- const token = normalizePageToken(parsed.data.nextPageToken);
127
+ const token = normalizePageToken(parsed.nextPageToken);
119
128
  if (!token) {
120
129
  break;
121
130
  }
@@ -1,4 +1,4 @@
1
- import { z } from "zod/v4";
1
+ import { type } from "arktype";
2
2
  import type { Api, FetchImpl, ModelSpec, Provider } from "../types";
3
3
 
4
4
  const MODELS_PATH = "/models";
@@ -32,28 +32,23 @@ export interface OpenAICompatibleModelsEnvelope {
32
32
  [key: string]: unknown;
33
33
  }
34
34
 
35
- const openAICompatibleModelRecordSchema = z
36
- .object({
37
- id: z.string().min(1),
38
- name: z.string().optional().nullable(),
39
- object: z.unknown().optional(),
40
- owned_by: z.unknown().optional(),
41
- })
42
- .loose();
35
+ const openAICompatibleModelRecordSchema = type({
36
+ id: "string >= 1",
37
+ "name?": "string | null",
38
+ "object?": "unknown",
39
+ "owned_by?": "unknown",
40
+ });
43
41
 
44
- const openAICompatibleModelsEnvelopeSchema = z
45
- .object({
46
- data: z.unknown().optional(),
47
- models: z.unknown().optional(),
48
- result: z.unknown().optional(),
49
- items: z.unknown().optional(),
50
- })
51
- .loose();
42
+ const openAICompatibleModelsEnvelopeSchema = type({
43
+ "data?": "unknown",
44
+ "models?": "unknown",
45
+ "result?": "unknown",
46
+ "items?": "unknown",
47
+ });
52
48
 
53
- const openAICompatibleModelsPayloadSchema = z.union([z.array(z.unknown()), openAICompatibleModelsEnvelopeSchema]);
54
-
55
- type ParsedOpenAICompatibleModelRecord = z.infer<typeof openAICompatibleModelRecordSchema>;
49
+ const openAICompatibleModelsPayloadSchema = type("unknown[]").or(openAICompatibleModelsEnvelopeSchema);
56
50
 
51
+ type ParsedOpenAICompatibleModelRecord = typeof openAICompatibleModelRecordSchema.infer;
57
52
  /**
58
53
  * Context passed to custom OpenAI-compatible model mappers.
59
54
  */
@@ -196,22 +191,17 @@ function extractModelEntries(payload: unknown): ParsedOpenAICompatibleModelRecor
196
191
  }
197
192
 
198
193
  function extractModelEntriesFromNode(node: unknown): ParsedOpenAICompatibleModelRecord[] | null {
199
- const parsedPayload = openAICompatibleModelsPayloadSchema.safeParse(node);
200
- if (!parsedPayload.success) {
194
+ const parsedPayload = openAICompatibleModelsPayloadSchema(node);
195
+ if (parsedPayload instanceof type.errors) {
201
196
  return null;
202
197
  }
203
- if (Array.isArray(parsedPayload.data)) {
204
- const parsedEntries = parsedPayload.data
205
- .map(entry => openAICompatibleModelRecordSchema.safeParse(entry))
206
- .flatMap(entry => (entry.success ? [entry.data] : []));
198
+ if (Array.isArray(parsedPayload)) {
199
+ const parsedEntries = parsedPayload
200
+ .map(entry => openAICompatibleModelRecordSchema(entry))
201
+ .flatMap(entry => (entry instanceof type.errors ? [] : [entry]));
207
202
  return parsedEntries;
208
203
  }
209
- for (const candidate of [
210
- parsedPayload.data.data,
211
- parsedPayload.data.models,
212
- parsedPayload.data.result,
213
- parsedPayload.data.items,
214
- ]) {
204
+ for (const candidate of [parsedPayload.data, parsedPayload.models, parsedPayload.result, parsedPayload.items]) {
215
205
  if (candidate === undefined) {
216
206
  continue;
217
207
  }
@@ -11,7 +11,8 @@ export type Dialect =
11
11
  | "pi"
12
12
  | "qwen3"
13
13
  | "gemini"
14
- | "gemma";
14
+ | "gemma"
15
+ | "minimax";
15
16
 
16
17
  export const FALLBACK_DIALECT: Dialect = "xml";
17
18
 
@@ -31,6 +32,8 @@ export function preferredDialect(modelId: string): Dialect {
31
32
  return "qwen3";
32
33
  case "deepseek":
33
34
  return "deepseek";
35
+ case "minimax":
36
+ return "minimax";
34
37
  case "openai":
35
38
  case "gpt-oss":
36
39
  return "harmony";
@@ -56,6 +56,19 @@ export function isMimoModelIdOrName(value: string): boolean {
56
56
  return value.toLowerCase().includes("mimo");
57
57
  }
58
58
 
59
+ const GROK_EFFORT_CAPABLE_PREFIXES = ["grok-3-mini", "grok-4.20-multi-agent", "grok-4.3"] as const;
60
+
61
+ /**
62
+ * Grok SKUs that expose the wire `reasoning.effort` dial. Other Grok reasoners
63
+ * (e.g. `grok-build`, `grok-4.20-0309-reasoning`) think natively but reject the
64
+ * param, so callers must omit reasoning effort for them.
65
+ */
66
+ export function isGrokReasoningEffortCapable(modelId: string): boolean {
67
+ const bare = bareModelId(modelId).trim().toLowerCase();
68
+ if (!bare) return false;
69
+ return GROK_EFFORT_CAPABLE_PREFIXES.some(prefix => bare.startsWith(prefix));
70
+ }
71
+
59
72
  /**
60
73
  * MiniMax M2-generation family (M2, M2.1, M2.5, M2.7, including `-highspeed`/
61
74
  * `-lightning`/`-her`/`-turbo` variants, dotless aliases like `minimax-m21`,
@@ -73,6 +86,13 @@ export function isMinimaxM2FamilyModelId(modelId: string): boolean {
73
86
  return /(?:^|[/.-])m2\d*(?:[.-]\d+)?(?:[-.:_]|$)/i.test(lower);
74
87
  }
75
88
 
89
+ /** MiniMax M3 family ids in bundled/default and aggregator namespace forms. */
90
+ export function isMinimaxM3FamilyModelId(modelId: string): boolean {
91
+ const lower = modelId.toLowerCase();
92
+ if (!lower.includes("minimax")) return false;
93
+ return /(?:^|[/._-])(?:minimax[/._-])?m3(?:[-.:_]|$)/i.test(lower);
94
+ }
95
+
76
96
  /**
77
97
  * OpenAI gpt-oss family (`gpt-oss-20b`, `gpt-oss-120b`, `gpt-oss:120b`,
78
98
  * `vendor/gpt-oss-…`). The Harmony reasoning format only accepts
@@ -139,7 +159,7 @@ export function modelFamilyToken(modelId: string): string {
139
159
  if (isOpenAIModelId(modelId)) return "openai";
140
160
  if (isKimiModelId(modelId)) return "kimi";
141
161
  if (isQwenModelId(modelId)) return "qwen";
142
- if (isMinimaxM2FamilyModelId(modelId)) return "minimax";
162
+ if (isMinimaxM2FamilyModelId(modelId) || isMinimaxM3FamilyModelId(modelId)) return "minimax";
143
163
  if (isOpenAIGptOssModelId(modelId)) return "gpt-oss";
144
164
  if (isDeepseekModelIdOrName(modelId)) return "deepseek";
145
165
  if (isMimoModelIdOrName(modelId)) return "mimo";
@@ -7,12 +7,14 @@ import { getModelDbPath } from "@oh-my-pi/pi-utils";
7
7
  import type { Api, Model, ModelSpec } from "./types";
8
8
 
9
9
  // Rows persist ModelSpec JSON (sparse `compat`, never the resolved record);
10
- // the model manager rebuilds via `buildModel` on load. v6 invalidates rows
11
- // that may contain the retired unknown-limit sentinels (222222/8888); v5
12
- // invalidated rows predating effort-tier variant collapsing (raw
13
- // `-low`/`-high`/`-thinking` member ids); v4 dropped the pre-efforts
14
- // ThinkingConfig shape.
15
- const CACHE_SCHEMA_VERSION = 6;
10
+ // the model manager rebuilds via `buildModel` on load. v7 invalidates rows
11
+ // predating the Antigravity Gemini budget-mode migration (cached specs still
12
+ // carrying `thinking.mode: "google-level"` and the old 3.5-flash effort
13
+ // routing); v6 invalidates rows that may contain the retired unknown-limit
14
+ // sentinels (222222/8888); v5 invalidated rows predating effort-tier variant
15
+ // collapsing (raw `-low`/`-high`/`-thinking` member ids); v4 dropped the
16
+ // pre-efforts ThinkingConfig shape.
17
+ const CACHE_SCHEMA_VERSION = 7;
16
18
 
17
19
  interface CacheRow {
18
20
  provider_id: string;
@@ -57,6 +57,7 @@ const GEMINI_3_FLASH_EFFORTS: readonly Effort[] = [Effort.Minimal, Effort.Low, E
57
57
  const GPT_5_2_PLUS_EFFORTS: readonly Effort[] = [Effort.Low, Effort.Medium, Effort.High, Effort.XHigh];
58
58
  const GPT_5_1_CODEX_MINI_EFFORTS: readonly Effort[] = [Effort.Medium, Effort.High];
59
59
  const LOW_MEDIUM_HIGH_REASONING_EFFORTS: readonly Effort[] = [Effort.Low, Effort.Medium, Effort.High];
60
+ const GLM_52_HIGH_MAX_REASONING_EFFORTS: readonly Effort[] = [Effort.High, Effort.XHigh];
60
61
 
61
62
  type EffortMap = Partial<Record<Effort, string>>;
62
63
 
@@ -84,6 +85,9 @@ const ZAI_GLM_52_REASONING_EFFORT_MAP: Readonly<EffortMap> = {
84
85
  [Effort.High]: "high",
85
86
  [Effort.XHigh]: "max",
86
87
  };
88
+ const OLLAMA_CLOUD_GLM_52_REASONING_EFFORT_MAP: Readonly<EffortMap> = {
89
+ [Effort.XHigh]: "max",
90
+ };
87
91
 
88
92
  /**
89
93
  * Effort → wire-value map for the 5-tier adaptive scale (Opus 4.7+ and
@@ -221,7 +225,7 @@ export function deriveThinking<TApi extends Api>(spec: ModelSpec<TApi>, compat:
221
225
  * True when the model reasons natively but rejects the wire `reasoning.effort`
222
226
  * param. Scoped to openai-responses* because that's the only API surface where
223
227
  * `compat.supportsReasoningEffort: false` means "omit the field entirely"
224
- * (xAI Grok off the GROK_EFFORT_CAPABLE_PREFIXES allowlist: grok-build,
228
+ * (xAI Grok off the `isGrokReasoningEffortCapable` allowlist: grok-build,
225
229
  * grok-4.20-0309-reasoning). openai-completions keeps its thinking config even
226
230
  * without effort support — binary thinking formats (zai/qwen) drive reasoning
227
231
  * through other request fields.
@@ -266,11 +270,18 @@ function sameEffortList(left: readonly Effort[], right: readonly Effort[]): bool
266
270
  return true;
267
271
  }
268
272
 
273
+ function isOpenAICompatReasoningApi(api: Api): boolean {
274
+ return api === "openai-completions" || api === "openrouter";
275
+ }
276
+
269
277
  function getModelDefinedEfforts<TApi extends Api>(spec: ModelSpec<TApi>): readonly Effort[] | undefined {
270
- if (spec.api === "openai-completions" && isZaiGlm52ReasoningEffortModel(spec)) {
278
+ if (isOpenAICompatReasoningApi(spec.api) && isZaiGlm52ReasoningEffortModel(spec)) {
271
279
  return DEFAULT_REASONING_EFFORTS_WITH_XHIGH;
272
280
  }
273
- return spec.api === "openai-completions" && (isMinimaxM2FamilyModelId(spec.id) || isOpenAIGptOssModelId(spec.id))
281
+ if (isOllamaCloudGlm52ReasoningEffortModel(spec)) {
282
+ return GLM_52_HIGH_MAX_REASONING_EFFORTS;
283
+ }
284
+ return isOpenAICompatReasoningApi(spec.api) && (isMinimaxM2FamilyModelId(spec.id) || isOpenAIGptOssModelId(spec.id))
274
285
  ? LOW_MEDIUM_HIGH_REASONING_EFFORTS
275
286
  : undefined;
276
287
  }
@@ -280,6 +291,10 @@ function isZaiGlm52ReasoningEffortModel<TApi extends Api>(spec: ModelSpec<TApi>)
280
291
  return modelMatchesHost(spec, "zai") || modelMatchesHost(spec, "zhipu");
281
292
  }
282
293
 
294
+ function isOllamaCloudGlm52ReasoningEffortModel<TApi extends Api>(spec: ModelSpec<TApi>): boolean {
295
+ return spec.api === "ollama-chat" && spec.provider === "ollama-cloud" && isGlm52ReasoningEffortModelId(spec.id);
296
+ }
297
+
283
298
  function readCompatEffortMap(compat: CompatOf<Api>): EffortMap | undefined {
284
299
  if (compat === undefined || !("reasoningEffortMap" in compat)) {
285
300
  return undefined;
@@ -298,7 +313,10 @@ function inferDetectedEffortMap<TApi extends Api>(
298
313
  ? ANTHROPIC_ADAPTIVE_EFFORT_MAP_5_TIER
299
314
  : ANTHROPIC_ADAPTIVE_EFFORT_MAP_4_TIER;
300
315
  }
301
- if (spec.api !== "openai-completions") {
316
+ if (isOllamaCloudGlm52ReasoningEffortModel(spec)) {
317
+ return OLLAMA_CLOUD_GLM_52_REASONING_EFFORT_MAP;
318
+ }
319
+ if (!isOpenAICompatReasoningApi(spec.api)) {
302
320
  return undefined;
303
321
  }
304
322
  if (spec.provider === "groq" && spec.id === "qwen/qwen3-32b") {
@@ -437,7 +455,7 @@ function inferFallbackEfforts<TApi extends Api>(spec: ModelSpec<TApi>, compat: C
437
455
  if (spec.api === "bedrock-converse-stream") {
438
456
  return DEFAULT_REASONING_EFFORTS;
439
457
  }
440
- if (spec.api === "openai-completions") {
458
+ if (isOpenAICompatReasoningApi(spec.api)) {
441
459
  const resolved = compat as ResolvedOpenAICompat;
442
460
  if (resolved.thinkingFormat === "openai" && resolved.supportsReasoningEffort) {
443
461
  return DEFAULT_REASONING_EFFORTS_WITH_XHIGH;
@@ -503,7 +521,7 @@ function isOpenRouterAnthropicAdaptiveReasoningModel<TApi extends Api>(
503
521
  parsedModel: AnthropicModel,
504
522
  spec: ModelSpec<TApi>,
505
523
  ): boolean {
506
- if (spec.api !== "openai-completions") return false;
524
+ if (!isOpenAICompatReasoningApi(spec.api)) return false;
507
525
  if (!modelMatchesHost(spec, "openrouter")) return false;
508
526
  return isFableOrMythos(parsedModel.kind) || (parsedModel.kind === "opus" && semverGte(parsedModel.version, "4.6"));
509
527
  }