@oh-my-pi/pi-catalog 18.0.8 → 18.0.9

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,18 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [18.0.9] - 2026-08-28
6
+
7
+ ### Added
8
+
9
+ - Added Z.AI GLM-5.3-Flash to the bundled GLM Coding Plan catalog, with a 1M-token context window, 131,072-token output limit, native image input, and low/high/max thinking levels. It is now available through model discovery and the model picker.
10
+
11
+ ### Fixed
12
+
13
+ - Fixed Google Antigravity Gemini 3.7 Flash tiered models failing with HTTP 400 errors at minimal or disabled thinking levels; those tiers now use the compatible Gemini 3.7 Flash model routing.
14
+ - Fixed OpenCode Go and Zen GLM-5.3 Flash models exposing an unsupported extra-high reasoning level; they now use the supported low/high/max reasoning levels.
15
+ - Fixed Cloudflare AI Gateway credential handling so stored credentials and routing metadata are parsed and used correctly.
16
+
5
17
  ## [18.0.8] - 2026-08-27
6
18
 
7
19
  ### Fixed
@@ -145,24 +145,28 @@ export declare const supportsCodexReasoningSummary: (modelId: string) => boolean
145
145
  export declare const isOpenAISamplingRestrictedModelId: (modelId: string) => boolean;
146
146
  /**
147
147
  * Reasoning-capable GLM coding SKUs: glm-4.5 and up on the base / `-air` /
148
- * `-turbo` lines. Excludes the vision (`…v`) shape, the non-reasoning
149
- * `-flash`/`-flashx`/`-preview` variants, and pre-4.5 ids. Matching the family
150
- * keeps newly-bumped integers (`glm-5.3`, `glm-6`, …) covered without a per-id
151
- * allowlist.
148
+ * `-turbo` lines, plus `-flash` from GLM-5.3-Flash on. Excludes the vision
149
+ * (`…v`) shape, the non-reasoning `-flashx`/`-preview` variants, and pre-4.5
150
+ * ids. Matching the family keeps newly-bumped integers (`glm-5.4`, `glm-6`, …)
151
+ * covered without a per-id allowlist.
152
152
  */
153
153
  export declare const isReasoningGlmModelId: (modelId: string) => boolean;
154
154
  /** GLM-5.2+ coding SKUs accept `reasoning_effort` in addition to binary thinking. */
155
155
  export declare const isGlm52ReasoningEffortModelId: (modelId: string) => boolean;
156
156
  /**
157
- * GLM-5.3+ coding SKUs. Unlike GLM-5.2 (whose reasoning_effort dialect is
158
- * host-specific), GLM-5.3+ exposes a uniform wire-exact `low`/`high`/`max`
159
- * ladder on every host, and thinking can no longer be disabled —
160
- * `thinking.type` must always be `enabled`. Matching the family keeps future
161
- * bumps (`glm-5.4`, `glm-6`, …) covered while excluding the vision (`…v`)
162
- * shape and the non-reasoning `-flash`/`-flashx`/`-preview` variants.
157
+ * GLM-5.3+ reasoning-effort SKUs. Unlike GLM-5.2 (whose dialect is
158
+ * host-specific), the base, `-air`, `-turbo`, and `-flash` variants expose a
159
+ * uniform wire-exact `low`/`high`/`max` ladder on every host, and thinking can
160
+ * no longer be disabled — `thinking.type` must always be `enabled`. Matching
161
+ * the family keeps future bumps (`glm-5.4`, `glm-6`, …) covered while excluding
162
+ * the vision (`…v`) shape and the non-reasoning `-flashx`/`-preview` variants.
163
163
  */
164
164
  export declare const isGlm53ReasoningEffortModelId: (modelId: string) => boolean;
165
- /** GLM vision SKUs — the `v` that attaches to the version (`glm-4v`, `glm-4.5v`). */
165
+ /**
166
+ * GLM SKUs that accept image input: the `v` that attaches to the version
167
+ * (`glm-4v`, `glm-4.5v`) and the natively multimodal `-flash` line from
168
+ * GLM-5.3-Flash on, whose id carries no `v` marker.
169
+ */
166
170
  export declare const isGlmVisionModelId: (modelId: string) => boolean;
167
171
  /**
168
172
  * Coarse vendor-lineage token for "are two models the same family?" checks
@@ -0,0 +1,18 @@
1
+ /** Provider-native AI Gateway prefix before the upstream route segment. */
2
+ export declare const CLOUDFLARE_AI_GATEWAY_BASE_URL = "https://gateway.ai.cloudflare.com/v1/<account>/<gateway>";
3
+ /** Anthropic Messages passthrough endpoint. */
4
+ export declare const CLOUDFLARE_AI_GATEWAY_ANTHROPIC_BASE_URL = "https://gateway.ai.cloudflare.com/v1/<account>/<gateway>/anthropic";
5
+ /** OpenAI Responses passthrough endpoint. */
6
+ export declare const CLOUDFLARE_AI_GATEWAY_OPENAI_BASE_URL = "https://gateway.ai.cloudflare.com/v1/<account>/<gateway>/openai";
7
+ /** OpenAI-compatible Workers AI endpoint. */
8
+ export declare const CLOUDFLARE_AI_GATEWAY_COMPAT_BASE_URL = "https://gateway.ai.cloudflare.com/v1/<account>/<gateway>/compat";
9
+ /** Opaque API-key credential payload persisted by Cloudflare AI Gateway login. */
10
+ export interface CloudflareAiGatewayCredential {
11
+ token: string;
12
+ accountId?: string;
13
+ gatewayId?: string;
14
+ }
15
+ /** Parse both structured login credentials and legacy plain gateway tokens. */
16
+ export declare function parseCloudflareAiGatewayCredential(value: string): CloudflareAiGatewayCredential | null;
17
+ /** Serialize the gateway token with the routing identifiers collected during login. */
18
+ export declare function serializeCloudflareAiGatewayCredential(token: string, accountId: string, gatewayId: string): string;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-catalog",
4
- "version": "18.0.8",
4
+ "version": "18.0.9",
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": "Stencil Labs, Inc.",
@@ -34,11 +34,11 @@
34
34
  "gen:proto": "bun scripts/generate-protocols.ts"
35
35
  },
36
36
  "dependencies": {
37
- "@oh-my-pi/omptype": "18.0.8",
38
- "@oh-my-pi/pi-utils": "18.0.8"
37
+ "@oh-my-pi/omptype": "18.0.9",
38
+ "@oh-my-pi/pi-utils": "18.0.9"
39
39
  },
40
40
  "devDependencies": {
41
- "@oh-my-pi/pi-ai": "18.0.8",
41
+ "@oh-my-pi/pi-ai": "18.0.9",
42
42
  "@types/bun": "^1.3.14"
43
43
  },
44
44
  "engines": {
@@ -9,6 +9,7 @@
9
9
 
10
10
  import {
11
11
  bareModelId,
12
+ type GlmModel,
12
13
  isAnthropicAdaptiveGenAtLeast,
13
14
  isFableOrMythos,
14
15
  parseAnthropicModel,
@@ -283,19 +284,35 @@ export const isOpenAISamplingRestrictedModelId = memo((modelId: string): boolean
283
284
  return isOpenAIWireGen5Plus(modelId) || O_SERIES_REASONING_RE.test(bare);
284
285
  });
285
286
 
287
+ /**
288
+ * GLM SKU shapes that carry the reasoning ladders. `base`/`air`/`turbo` have
289
+ * always been reasoning lines; `flash` joins at GLM-5.3-Flash, the first flash
290
+ * SKU whose thinking is mandatory (pre-5.3 `-flash` SKUs are non-reasoning).
291
+ * `flashx` and `preview` stay out.
292
+ */
293
+ function isGlmReasoningVariant(glm: GlmModel): boolean {
294
+ switch (glm.variant) {
295
+ case "base":
296
+ case "air":
297
+ case "turbo":
298
+ return true;
299
+ case "flash":
300
+ return semverGte(glm.version, "5.3");
301
+ default:
302
+ return false;
303
+ }
304
+ }
305
+
286
306
  /**
287
307
  * Reasoning-capable GLM coding SKUs: glm-4.5 and up on the base / `-air` /
288
- * `-turbo` lines. Excludes the vision (`…v`) shape, the non-reasoning
289
- * `-flash`/`-flashx`/`-preview` variants, and pre-4.5 ids. Matching the family
290
- * keeps newly-bumped integers (`glm-5.3`, `glm-6`, …) covered without a per-id
291
- * allowlist.
308
+ * `-turbo` lines, plus `-flash` from GLM-5.3-Flash on. Excludes the vision
309
+ * (`…v`) shape, the non-reasoning `-flashx`/`-preview` variants, and pre-4.5
310
+ * ids. Matching the family keeps newly-bumped integers (`glm-5.4`, `glm-6`, …)
311
+ * covered without a per-id allowlist.
292
312
  */
293
313
  export const isReasoningGlmModelId = memo((modelId: string): boolean => {
294
314
  const glm = parseGlmModel(bareModelId(modelId));
295
- if (!glm || glm.vision) {
296
- return false;
297
- }
298
- if (glm.variant !== "base" && glm.variant !== "air" && glm.variant !== "turbo") {
315
+ if (!glm || glm.vision || !isGlmReasoningVariant(glm)) {
299
316
  return false;
300
317
  }
301
318
  return semverGte(glm.version, "4.5");
@@ -304,37 +321,39 @@ export const isReasoningGlmModelId = memo((modelId: string): boolean => {
304
321
  /** GLM-5.2+ coding SKUs accept `reasoning_effort` in addition to binary thinking. */
305
322
  export const isGlm52ReasoningEffortModelId = memo((modelId: string): boolean => {
306
323
  const glm = parseGlmModel(bareModelId(modelId));
307
- if (!glm || glm.vision) {
308
- return false;
309
- }
310
- if (glm.variant !== "base" && glm.variant !== "air" && glm.variant !== "turbo") {
324
+ if (!glm || glm.vision || !isGlmReasoningVariant(glm)) {
311
325
  return false;
312
326
  }
313
327
  return semverGte(glm.version, "5.2");
314
328
  });
315
329
 
316
330
  /**
317
- * GLM-5.3+ coding SKUs. Unlike GLM-5.2 (whose reasoning_effort dialect is
318
- * host-specific), GLM-5.3+ exposes a uniform wire-exact `low`/`high`/`max`
319
- * ladder on every host, and thinking can no longer be disabled —
320
- * `thinking.type` must always be `enabled`. Matching the family keeps future
321
- * bumps (`glm-5.4`, `glm-6`, …) covered while excluding the vision (`…v`)
322
- * shape and the non-reasoning `-flash`/`-flashx`/`-preview` variants.
331
+ * GLM-5.3+ reasoning-effort SKUs. Unlike GLM-5.2 (whose dialect is
332
+ * host-specific), the base, `-air`, `-turbo`, and `-flash` variants expose a
333
+ * uniform wire-exact `low`/`high`/`max` ladder on every host, and thinking can
334
+ * no longer be disabled — `thinking.type` must always be `enabled`. Matching
335
+ * the family keeps future bumps (`glm-5.4`, `glm-6`, …) covered while excluding
336
+ * the vision (`…v`) shape and the non-reasoning `-flashx`/`-preview` variants.
323
337
  */
324
338
  export const isGlm53ReasoningEffortModelId = memo((modelId: string): boolean => {
325
339
  const glm = parseGlmModel(bareModelId(modelId));
326
- if (!glm || glm.vision) {
327
- return false;
328
- }
329
- if (glm.variant !== "base" && glm.variant !== "air" && glm.variant !== "turbo") {
340
+ if (!glm || glm.vision || !isGlmReasoningVariant(glm)) {
330
341
  return false;
331
342
  }
332
343
  return semverGte(glm.version, "5.3");
333
344
  });
334
345
 
335
- /** GLM vision SKUs — the `v` that attaches to the version (`glm-4v`, `glm-4.5v`). */
346
+ /**
347
+ * GLM SKUs that accept image input: the `v` that attaches to the version
348
+ * (`glm-4v`, `glm-4.5v`) and the natively multimodal `-flash` line from
349
+ * GLM-5.3-Flash on, whose id carries no `v` marker.
350
+ */
336
351
  export const isGlmVisionModelId = memo((modelId: string): boolean => {
337
- return parseGlmModel(bareModelId(modelId))?.vision === true;
352
+ const glm = parseGlmModel(bareModelId(modelId));
353
+ if (!glm) {
354
+ return false;
355
+ }
356
+ return glm.vision || (glm.variant === "flash" && semverGte(glm.version, "5.3"));
338
357
  });
339
358
 
340
359
  /**
package/src/models.json CHANGED
@@ -64887,38 +64887,6 @@
64887
64887
  "requiresGlyphTokenization": false,
64888
64888
  "supportsComputerUse": false
64889
64889
  },
64890
- "gemini-3.7-flash-tiered": {
64891
- "id": "gemini-3.7-flash-tiered",
64892
- "name": "gemini-3.7-flash-tiered",
64893
- "api": "google-gemini-cli",
64894
- "provider": "google-antigravity",
64895
- "baseUrl": "https://daily-cloudcode-pa.googleapis.com",
64896
- "reasoning": true,
64897
- "input": [
64898
- "text",
64899
- "image"
64900
- ],
64901
- "cost": {
64902
- "input": 0,
64903
- "output": 0,
64904
- "cacheRead": 0,
64905
- "cacheWrite": 0
64906
- },
64907
- "contextWindow": 1048576,
64908
- "maxTokens": 65536,
64909
- "thinking": {
64910
- "mode": "google-level",
64911
- "efforts": [
64912
- "minimal",
64913
- "low",
64914
- "medium",
64915
- "high"
64916
- ],
64917
- "requiresEffort": true
64918
- },
64919
- "requiresGlyphTokenization": false,
64920
- "supportsComputerUse": false
64921
- },
64922
64890
  "gpt-oss-120b": {
64923
64891
  "id": "gpt-oss-120b",
64924
64892
  "name": "GPT OSS 120B",
@@ -294589,6 +294557,55 @@
294589
294557
  },
294590
294558
  "supportsComputerUseConfig": false
294591
294559
  },
294560
+ "glm-5.3-flash": {
294561
+ "id": "glm-5.3-flash",
294562
+ "name": "GLM-5.3-Flash",
294563
+ "api": "anthropic-messages",
294564
+ "provider": "zai",
294565
+ "baseUrl": "https://api.z.ai/api/anthropic",
294566
+ "reasoning": true,
294567
+ "input": [
294568
+ "text",
294569
+ "image"
294570
+ ],
294571
+ "cost": {
294572
+ "input": 0.15,
294573
+ "output": 0.5,
294574
+ "cacheRead": 0.03,
294575
+ "cacheWrite": 0
294576
+ },
294577
+ "contextWindow": 1000000,
294578
+ "maxTokens": 131072,
294579
+ "thinking": {
294580
+ "mode": "anthropic-budget-effort",
294581
+ "efforts": [
294582
+ "low",
294583
+ "high",
294584
+ "max"
294585
+ ],
294586
+ "defaultLevel": "max",
294587
+ "requiresEffort": true
294588
+ },
294589
+ "requiresGlyphTokenization": false,
294590
+ "tokenizer": "glm5",
294591
+ "supportsComputerUse": false,
294592
+ "compat": {
294593
+ "officialEndpoint": false,
294594
+ "signingEndpoint": false,
294595
+ "disableStrictTools": false,
294596
+ "disableAdaptiveThinking": false,
294597
+ "allowAnthropicHeaderOverrides": false,
294598
+ "supportsEagerToolInputStreaming": false,
294599
+ "supportsLongCacheRetention": false,
294600
+ "supportsMidConversationSystem": false,
294601
+ "supportsForcedToolChoice": true,
294602
+ "supportsSamplingParams": true,
294603
+ "requiresToolResultId": true,
294604
+ "requiresThinkingEnabled": false,
294605
+ "replayUnsignedThinking": true,
294606
+ "escapeBuiltinToolNames": false
294607
+ }
294608
+ },
294592
294609
  "glm-5v-turbo": {
294593
294610
  "id": "glm-5v-turbo",
294594
294611
  "name": "GLM-5V-Turbo",
@@ -312261,4 +312278,4 @@
312261
312278
  }
312262
312279
  }
312263
312280
  }
312264
- }
312281
+ }
@@ -2823,6 +2823,9 @@ const OPENCODE_GO_API_ID_OVERRIDES: Readonly<Record<string, Api>> = {
2823
2823
  "qwen3.5-plus": "openai-completions",
2824
2824
  "qwen3.6-plus": "openai-completions",
2825
2825
  };
2826
+ // Runtime-discovered rows cached before model-identity corrections retain
2827
+ // stale capability metadata until the authoritative catalog TTL expires.
2828
+ const OPENCODE_CACHE_MIGRATION_MODEL_IDS = ["glm-5.3-flash"] as const;
2826
2829
 
2827
2830
  // Billing-variant suffixes the OpenCode gateways append to a base model id
2828
2831
  // without changing its transport (`deepseek-v4-flash-free`,
@@ -2883,12 +2886,10 @@ function openCodeModelManagerOptions(
2883
2886
  providerId,
2884
2887
  cacheProviderId: resolveModelCacheProviderId(providerId, { apiKey, baseUrl: discoveryBaseUrl }),
2885
2888
  dynamicModelsAuthoritative: true,
2886
- // The per-id API pins are cache identity: without this, rows cached
2887
- // before a pin was added keep the wrong endpoint until TTL expiry
2888
- // (#8957 17.3.7 caches held muse-spark-1.2[-contributor] on chat
2889
- // completions after the pin shipped). Sibling-catalog drift is bounded
2890
- // by the 2h cache TTL instead.
2891
- dropCachedModelIdsOnStaticMismatch: Object.keys(apiOverrides),
2889
+ // Per-id route pins and capability migrations are cache identity:
2890
+ // without this, rows cached before a correction keep the stale route or
2891
+ // thinking surface until TTL expiry (#8957, #9960).
2892
+ dropCachedModelIdsOnStaticMismatch: [...Object.keys(apiOverrides), ...OPENCODE_CACHE_MIGRATION_MODEL_IDS],
2892
2893
  modelsDev: {
2893
2894
  fetch: () => fetchRevalidatedWellKnownModelsWithTimeout(config?.fetch),
2894
2895
  map: payload => {
@@ -307,7 +307,7 @@ function geminiLevelFlashFamily(version: "3.6" | "3.7", ...additionalMembers: st
307
307
  }
308
308
 
309
309
  const GEMINI_36_FLASH_FAMILY = geminiLevelFlashFamily("3.6", "gemini-3.6-flash-tiered");
310
- const GEMINI_37_FLASH_FAMILY = geminiLevelFlashFamily("3.7");
310
+ const GEMINI_37_FLASH_FAMILY = geminiLevelFlashFamily("3.7", "gemini-3.7-flash-tiered");
311
311
 
312
312
  function geminiProFamily(mode: "budget" | "google-level"): EffortVariantFamily {
313
313
  const budget = mode === "budget";
@@ -0,0 +1,44 @@
1
+ import { isRecord } from "../utils";
2
+
3
+ /** Provider-native AI Gateway prefix before the upstream route segment. */
4
+ export const CLOUDFLARE_AI_GATEWAY_BASE_URL = "https://gateway.ai.cloudflare.com/v1/<account>/<gateway>";
5
+ /** Anthropic Messages passthrough endpoint. */
6
+ export const CLOUDFLARE_AI_GATEWAY_ANTHROPIC_BASE_URL = `${CLOUDFLARE_AI_GATEWAY_BASE_URL}/anthropic`;
7
+ /** OpenAI Responses passthrough endpoint. */
8
+ export const CLOUDFLARE_AI_GATEWAY_OPENAI_BASE_URL = `${CLOUDFLARE_AI_GATEWAY_BASE_URL}/openai`;
9
+ /** OpenAI-compatible Workers AI endpoint. */
10
+ export const CLOUDFLARE_AI_GATEWAY_COMPAT_BASE_URL = `${CLOUDFLARE_AI_GATEWAY_BASE_URL}/compat`;
11
+
12
+ /** Opaque API-key credential payload persisted by Cloudflare AI Gateway login. */
13
+ export interface CloudflareAiGatewayCredential {
14
+ token: string;
15
+ accountId?: string;
16
+ gatewayId?: string;
17
+ }
18
+
19
+ /** Parse both structured login credentials and legacy plain gateway tokens. */
20
+ export function parseCloudflareAiGatewayCredential(value: string): CloudflareAiGatewayCredential | null {
21
+ const trimmed = value.trim();
22
+ if (!trimmed) return null;
23
+ if (!trimmed.startsWith("{")) return { token: trimmed };
24
+ try {
25
+ const parsed: unknown = JSON.parse(trimmed);
26
+ if (!isRecord(parsed)) return null;
27
+ if (typeof parsed.token !== "string" || !parsed.token.trim()) return null;
28
+ if (parsed.accountId !== undefined && typeof parsed.accountId !== "string") return null;
29
+ if (parsed.gatewayId !== undefined && typeof parsed.gatewayId !== "string") return null;
30
+ const credential: CloudflareAiGatewayCredential = { token: parsed.token.trim() };
31
+ const accountId = parsed.accountId?.trim();
32
+ const gatewayId = parsed.gatewayId?.trim();
33
+ if (accountId) credential.accountId = accountId;
34
+ if (gatewayId) credential.gatewayId = gatewayId;
35
+ return credential;
36
+ } catch {
37
+ return null;
38
+ }
39
+ }
40
+
41
+ /** Serialize the gateway token with the routing identifiers collected during login. */
42
+ export function serializeCloudflareAiGatewayCredential(token: string, accountId: string, gatewayId: string): string {
43
+ return JSON.stringify({ token: token.trim(), accountId: accountId.trim(), gatewayId: gatewayId.trim() });
44
+ }