@oh-my-pi/pi-catalog 16.3.10 → 16.3.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,27 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [16.3.12] - 2026-07-08
6
+
7
+ ### Fixed
8
+
9
+ - Fixed LiteLLM discovery stopping at `/model_group/info` when that endpoint omitted `supports_vision`; it now continues to `/model/info` and preserves `model_info.supports_vision=true` for vision-capable proxy models. ([#4747](https://github.com/can1357/oh-my-pi/issues/4747))
10
+ - Fixed LiteLLM discovery to fall back to bundled catalog metadata when `models.dev` lacks a model reference, preserving reasoning and thinking support for models such as `glm-5.2`. ([#4695](https://github.com/can1357/oh-my-pi/issues/4695))
11
+ - Detected Azure AI Inference / Foundry Anthropic routes as strict-tool-incompatible so resolved Anthropic compat disables strict tools before request construction ([#4679](https://github.com/can1357/oh-my-pi/issues/4679)).
12
+
13
+ ## [16.3.11] - 2026-07-06
14
+
15
+ ### Added
16
+
17
+ - Added Claude Haiku 4.5 (JP) model support
18
+ - Added tencent/hy3 model support via ZenMux
19
+
20
+ ### Changed
21
+
22
+ - Updated naming format for various synthetic models to include provider prefix
23
+ - Adjusted context window limit for MiniMax-M3 model
24
+ - Updated pricing for select models
25
+
5
26
  ## [16.3.10] - 2026-07-06
6
27
 
7
28
  ### Fixed
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.3.10",
4
+ "version": "16.3.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.3.10",
37
+ "@oh-my-pi/pi-utils": "16.3.12",
38
38
  "arktype": "^2.2.0",
39
39
  "zod": "^4"
40
40
  },
41
41
  "devDependencies": {
42
- "@oh-my-pi/pi-ai": "16.3.10",
42
+ "@oh-my-pi/pi-ai": "16.3.12",
43
43
  "@types/bun": "^1.3.14"
44
44
  },
45
45
  "engines": {
@@ -99,18 +99,15 @@ export function buildAnthropicCompat(spec: ModelSpec<"anthropic-messages">): Res
99
99
  // (issue #4192).
100
100
  const isZenmux = modelMatchesHost(spec, "zenmux");
101
101
  const requiresThinkingEnabled = modelMatchesHost(spec, "moonshotNative") && matchesKimiK27CodeFamily(spec);
102
+ const isVertex = isVertexAnthropicRoute(baseUrl);
103
+ const isBedrock = isBedrockAnthropicRoute(baseUrl);
104
+ const isAzure = isAzureAnthropicRoute(baseUrl);
102
105
  const signingEndpoint =
103
- official ||
104
- isCopilot ||
105
- isZenmux ||
106
- isCloudflareAnthropicGateway(baseUrl) ||
107
- isVertexAnthropicRoute(baseUrl) ||
108
- isBedrockAnthropicRoute(baseUrl) ||
109
- isAzureAnthropicRoute(baseUrl);
106
+ official || isCopilot || isZenmux || isCloudflareAnthropicGateway(baseUrl) || isVertex || isBedrock || isAzure;
110
107
  const compat: ResolvedAnthropicCompat = {
111
108
  officialEndpoint: official,
112
109
  signingEndpoint,
113
- disableStrictTools: false,
110
+ disableStrictTools: isAzure,
114
111
  disableAdaptiveThinking: false,
115
112
  supportsEagerToolInputStreaming: !isCopilot,
116
113
  // Long cache retention is only sent to the official API by default;
package/src/models.json CHANGED
@@ -9073,6 +9073,35 @@
9073
9073
  "contextWindow": 128000,
9074
9074
  "maxTokens": 4096
9075
9075
  },
9076
+ "jp.anthropic.claude-haiku-4-5-20251001-v1:0": {
9077
+ "id": "jp.anthropic.claude-haiku-4-5-20251001-v1:0",
9078
+ "name": "Claude Haiku 4.5 (JP)",
9079
+ "api": "bedrock-converse-stream",
9080
+ "provider": "amazon-bedrock",
9081
+ "baseUrl": "https://bedrock-runtime.us-east-1.amazonaws.com",
9082
+ "reasoning": true,
9083
+ "input": [
9084
+ "text",
9085
+ "image"
9086
+ ],
9087
+ "cost": {
9088
+ "input": 1,
9089
+ "output": 5,
9090
+ "cacheRead": 0.1,
9091
+ "cacheWrite": 1.25
9092
+ },
9093
+ "contextWindow": 200000,
9094
+ "maxTokens": 64000,
9095
+ "thinking": {
9096
+ "mode": "budget",
9097
+ "efforts": [
9098
+ "minimal",
9099
+ "low",
9100
+ "medium",
9101
+ "high"
9102
+ ]
9103
+ }
9104
+ },
9076
9105
  "jp.anthropic.claude-opus-4-7": {
9077
9106
  "id": "jp.anthropic.claude-opus-4-7",
9078
9107
  "name": "Claude Opus 4.7 (JP)",
@@ -51831,6 +51860,25 @@
51831
51860
  "contextWindow": null,
51832
51861
  "maxTokens": null
51833
51862
  },
51863
+ "tencent/hy3": {
51864
+ "id": "tencent/hy3",
51865
+ "name": "tencent/hy3",
51866
+ "api": "openai-completions",
51867
+ "provider": "nanogpt",
51868
+ "baseUrl": "https://nano-gpt.com/api/v1",
51869
+ "reasoning": false,
51870
+ "input": [
51871
+ "text"
51872
+ ],
51873
+ "cost": {
51874
+ "input": 0,
51875
+ "output": 0,
51876
+ "cacheRead": 0,
51877
+ "cacheWrite": 0
51878
+ },
51879
+ "contextWindow": 262144,
51880
+ "maxTokens": null
51881
+ },
51834
51882
  "tencent/hy3-preview": {
51835
51883
  "id": "tencent/hy3-preview",
51836
51884
  "name": "Hy3 Preview",
@@ -71923,9 +71971,9 @@
71923
71971
  "text"
71924
71972
  ],
71925
71973
  "cost": {
71926
- "input": 0.56,
71927
- "output": 1.76,
71928
- "cacheRead": 0.10400000000000001,
71974
+ "input": 0.9086,
71975
+ "output": 2.8556,
71976
+ "cacheRead": 0.16874,
71929
71977
  "cacheWrite": 0
71930
71978
  },
71931
71979
  "contextWindow": 1048576,
@@ -72145,7 +72193,7 @@
72145
72193
  "synthetic": {
72146
72194
  "hf:MiniMaxAI/MiniMax-M3": {
72147
72195
  "id": "hf:MiniMaxAI/MiniMax-M3",
72148
- "name": "MiniMax-M3",
72196
+ "name": "MiniMaxAI/MiniMax-M3",
72149
72197
  "api": "openai-completions",
72150
72198
  "provider": "synthetic",
72151
72199
  "baseUrl": "https://api.synthetic.new/openai/v1",
@@ -72160,7 +72208,7 @@
72160
72208
  "cacheRead": 0.6,
72161
72209
  "cacheWrite": 0
72162
72210
  },
72163
- "contextWindow": 524288,
72211
+ "contextWindow": 262144,
72164
72212
  "maxTokens": 65536,
72165
72213
  "thinking": {
72166
72214
  "mode": "effort",
@@ -72175,7 +72223,7 @@
72175
72223
  },
72176
72224
  "hf:moonshotai/Kimi-K2.7-Code": {
72177
72225
  "id": "hf:moonshotai/Kimi-K2.7-Code",
72178
- "name": "Kimi K2.7 Code",
72226
+ "name": "moonshotai/Kimi-K2.7-Code",
72179
72227
  "api": "openai-completions",
72180
72228
  "provider": "synthetic",
72181
72229
  "baseUrl": "https://api.synthetic.new/openai/v1",
@@ -72205,7 +72253,7 @@
72205
72253
  },
72206
72254
  "hf:nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-NVFP4": {
72207
72255
  "id": "hf:nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-NVFP4",
72208
- "name": "Nemotron 3 Super 120B A12B",
72256
+ "name": "nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-NVFP4",
72209
72257
  "api": "openai-completions",
72210
72258
  "provider": "synthetic",
72211
72259
  "baseUrl": "https://api.synthetic.new/openai/v1",
@@ -72234,7 +72282,7 @@
72234
72282
  },
72235
72283
  "hf:openai/gpt-oss-120b": {
72236
72284
  "id": "hf:openai/gpt-oss-120b",
72237
- "name": "GPT OSS 120B",
72285
+ "name": "openai/gpt-oss-120b",
72238
72286
  "api": "openai-completions",
72239
72287
  "provider": "synthetic",
72240
72288
  "baseUrl": "https://api.synthetic.new/openai/v1",
@@ -72261,7 +72309,7 @@
72261
72309
  },
72262
72310
  "hf:Qwen/Qwen3.6-27B": {
72263
72311
  "id": "hf:Qwen/Qwen3.6-27B",
72264
- "name": "Qwen3.6 27B",
72312
+ "name": "Qwen/Qwen3.6-27B",
72265
72313
  "api": "openai-completions",
72266
72314
  "provider": "synthetic",
72267
72315
  "baseUrl": "https://api.synthetic.new/openai/v1",
@@ -72290,7 +72338,7 @@
72290
72338
  },
72291
72339
  "hf:zai-org/GLM-4.7-Flash": {
72292
72340
  "id": "hf:zai-org/GLM-4.7-Flash",
72293
- "name": "GLM-4.7-Flash",
72341
+ "name": "zai-org/GLM-4.7-Flash",
72294
72342
  "api": "openai-completions",
72295
72343
  "provider": "synthetic",
72296
72344
  "baseUrl": "https://api.synthetic.new/openai/v1",
@@ -72319,7 +72367,7 @@
72319
72367
  },
72320
72368
  "hf:zai-org/GLM-5.2": {
72321
72369
  "id": "hf:zai-org/GLM-5.2",
72322
- "name": "GLM-5.2",
72370
+ "name": "zai-org/GLM-5.2",
72323
72371
  "api": "openai-completions",
72324
72372
  "provider": "synthetic",
72325
72373
  "baseUrl": "https://api.synthetic.new/openai/v1",
@@ -83219,14 +83267,6 @@
83219
83267
  },
83220
83268
  "contextWindow": 2000000,
83221
83269
  "maxTokens": 2000000,
83222
- "compat": {
83223
- "reasoningEffortMap": {
83224
- "minimal": "low"
83225
- },
83226
- "includeEncryptedReasoning": false,
83227
- "filterReasoningHistory": true,
83228
- "omitReasoningEffort": false
83229
- },
83230
83270
  "thinking": {
83231
83271
  "mode": "effort",
83232
83272
  "efforts": [
@@ -83239,6 +83279,14 @@
83239
83279
  "effortMap": {
83240
83280
  "minimal": "low"
83241
83281
  }
83282
+ },
83283
+ "compat": {
83284
+ "reasoningEffortMap": {
83285
+ "minimal": "low"
83286
+ },
83287
+ "includeEncryptedReasoning": false,
83288
+ "filterReasoningHistory": true,
83289
+ "omitReasoningEffort": false
83242
83290
  }
83243
83291
  },
83244
83292
  "grok-4.3": {
@@ -83260,14 +83308,6 @@
83260
83308
  },
83261
83309
  "contextWindow": 1000000,
83262
83310
  "maxTokens": 1000000,
83263
- "compat": {
83264
- "reasoningEffortMap": {
83265
- "minimal": "low"
83266
- },
83267
- "includeEncryptedReasoning": false,
83268
- "filterReasoningHistory": true,
83269
- "omitReasoningEffort": false
83270
- },
83271
83311
  "thinking": {
83272
83312
  "mode": "effort",
83273
83313
  "efforts": [
@@ -83280,6 +83320,14 @@
83280
83320
  "effortMap": {
83281
83321
  "minimal": "low"
83282
83322
  }
83323
+ },
83324
+ "compat": {
83325
+ "reasoningEffortMap": {
83326
+ "minimal": "low"
83327
+ },
83328
+ "includeEncryptedReasoning": false,
83329
+ "filterReasoningHistory": true,
83330
+ "omitReasoningEffort": false
83283
83331
  }
83284
83332
  },
83285
83333
  "grok-build": {
@@ -88423,6 +88471,35 @@
88423
88471
  "requiresEffort": true
88424
88472
  }
88425
88473
  },
88474
+ "tencent/hy3": {
88475
+ "id": "tencent/hy3",
88476
+ "name": "Hy3",
88477
+ "api": "openai-completions",
88478
+ "provider": "zenmux",
88479
+ "baseUrl": "https://zenmux.ai/api/v1",
88480
+ "reasoning": true,
88481
+ "input": [
88482
+ "text"
88483
+ ],
88484
+ "cost": {
88485
+ "input": 0.134561595,
88486
+ "output": 0.539161765,
88487
+ "cacheRead": 0.033869245,
88488
+ "cacheWrite": 0
88489
+ },
88490
+ "contextWindow": 262144,
88491
+ "maxTokens": null,
88492
+ "thinking": {
88493
+ "mode": "effort",
88494
+ "efforts": [
88495
+ "minimal",
88496
+ "low",
88497
+ "medium",
88498
+ "high",
88499
+ "xhigh"
88500
+ ]
88501
+ }
88502
+ },
88426
88503
  "tencent/hy3-preview": {
88427
88504
  "id": "tencent/hy3-preview",
88428
88505
  "name": "Hy3 preview",
@@ -3032,6 +3032,15 @@ export interface FetchLiteLLMRichModelsOptions<TApi extends Api> {
3032
3032
  }
3033
3033
 
3034
3034
  type LiteLLMRichModelEntry = Record<string, unknown>;
3035
+ type LiteLLMRichEndpointModel<TApi extends Api> = {
3036
+ model: ModelSpec<TApi>;
3037
+ supportsVision: unknown;
3038
+ supportsReasoning: unknown;
3039
+ hasContextWindow: boolean;
3040
+ hasMaxTokens: boolean;
3041
+ hasToolMetadata: boolean;
3042
+ hasSupportedOpenAIParams: boolean;
3043
+ };
3035
3044
 
3036
3045
  const LITELLM_RICH_ENDPOINTS = ["/model_group/info", "/v2/model/info", "/model/info", "/v1/model/info"] as const;
3037
3046
  export const OPENAI_COMPAT_DISCOVERY_DEFAULT_CONTEXT_WINDOW = 128_000;
@@ -3264,7 +3273,7 @@ async function fetchLiteLLMRichEndpoint<TApi extends Api>(
3264
3273
  managementBaseUrl: string,
3265
3274
  runtimeBaseUrl: string,
3266
3275
  signal?: AbortSignal,
3267
- ): Promise<ModelSpec<TApi>[] | null> {
3276
+ ): Promise<{ models: LiteLLMRichEndpointModel<TApi>[]; incompleteVisionMetadata: boolean } | null> {
3268
3277
  const fetchImpl = discoveryFetch(options.fetch);
3269
3278
  const requestHeaders: Record<string, string> = {
3270
3279
  Accept: "application/json",
@@ -3296,17 +3305,39 @@ async function fetchLiteLLMRichEndpoint<TApi extends Api>(
3296
3305
  if (!entries || entries.length === 0) {
3297
3306
  return null;
3298
3307
  }
3299
- const deduped = new Map<string, ModelSpec<TApi>>();
3308
+ const deduped = new Map<string, LiteLLMRichEndpointModel<TApi>>();
3309
+ let incompleteVisionMetadata = false;
3300
3310
  for (const entry of entries) {
3301
3311
  const model = mapLiteLLMRichEntry(entry, options, runtimeBaseUrl);
3302
3312
  if (model) {
3303
- deduped.set(model.id, model);
3313
+ const supportsVision = getLiteLLMMetadataValue(entry, "supports_vision");
3314
+ const supportsReasoning = getLiteLLMMetadataValue(entry, "supports_reasoning");
3315
+ const supportsFunctionCalling = getLiteLLMMetadataValue(entry, "supports_function_calling");
3316
+ const supportedOpenAIParams = getSupportedOpenAIParams(entry);
3317
+ if (supportsVision !== true && supportsVision !== false) {
3318
+ incompleteVisionMetadata = true;
3319
+ }
3320
+ deduped.set(model.id, {
3321
+ model,
3322
+ supportsVision,
3323
+ supportsReasoning,
3324
+ hasContextWindow: toPositiveNumber(getLiteLLMMetadataValue(entry, "max_input_tokens"), null) !== null,
3325
+ hasMaxTokens: toPositiveNumber(getLiteLLMMetadataValue(entry, "max_output_tokens"), null) !== null,
3326
+ hasToolMetadata:
3327
+ supportsFunctionCalling === true ||
3328
+ supportsFunctionCalling === false ||
3329
+ supportedOpenAIParams !== undefined,
3330
+ hasSupportedOpenAIParams: supportedOpenAIParams !== undefined,
3331
+ });
3304
3332
  }
3305
3333
  }
3306
3334
  if (deduped.size === 0) {
3307
3335
  return null;
3308
3336
  }
3309
- return Array.from(deduped.values()).sort((left, right) => left.id.localeCompare(right.id));
3337
+ return {
3338
+ models: Array.from(deduped.values()).sort((left, right) => left.model.id.localeCompare(right.model.id)),
3339
+ incompleteVisionMetadata,
3340
+ };
3310
3341
  }
3311
3342
 
3312
3343
  export async function fetchLiteLLMRichModels<TApi extends Api>(
@@ -3318,13 +3349,55 @@ export async function fetchLiteLLMRichModels<TApi extends Api>(
3318
3349
  return null;
3319
3350
  }
3320
3351
  const fetchModels = async (signal?: AbortSignal): Promise<ModelSpec<TApi>[] | null> => {
3352
+ const deduped = new Map<string, LiteLLMRichEndpointModel<TApi>>();
3321
3353
  for (const endpoint of LITELLM_RICH_ENDPOINTS) {
3322
- const models = await fetchLiteLLMRichEndpoint(endpoint, options, managementBaseUrl, runtimeBaseUrl, signal);
3323
- if (models) {
3324
- return models;
3354
+ const result = await fetchLiteLLMRichEndpoint(endpoint, options, managementBaseUrl, runtimeBaseUrl, signal);
3355
+ if (!result) {
3356
+ continue;
3357
+ }
3358
+ const hadPriorModels = deduped.size > 0;
3359
+ for (const next of result.models) {
3360
+ const existing = deduped.get(next.model.id);
3361
+ if (!existing) {
3362
+ if (!hadPriorModels) {
3363
+ deduped.set(next.model.id, next);
3364
+ }
3365
+ continue;
3366
+ }
3367
+ const model: ModelSpec<TApi> = {
3368
+ ...existing.model,
3369
+ name: next.model.name === next.model.id ? existing.model.name : next.model.name,
3370
+ contextWindow: next.hasContextWindow ? next.model.contextWindow : existing.model.contextWindow,
3371
+ maxTokens: next.hasMaxTokens ? next.model.maxTokens : existing.model.maxTokens,
3372
+ input:
3373
+ next.supportsVision === true || next.supportsVision === false
3374
+ ? next.model.input
3375
+ : existing.model.input,
3376
+ reasoning: typeof next.supportsReasoning === "boolean" ? next.model.reasoning : existing.model.reasoning,
3377
+ compat: next.hasSupportedOpenAIParams ? next.model.compat : existing.model.compat,
3378
+ };
3379
+ if (next.hasToolMetadata) {
3380
+ model.supportsTools = next.model.supportsTools;
3381
+ }
3382
+ deduped.set(next.model.id, { ...next, model });
3383
+ }
3384
+ let hasIncompleteVisionMetadata = false;
3385
+ for (const entry of deduped.values()) {
3386
+ if (entry.supportsVision !== true && entry.supportsVision !== false) {
3387
+ hasIncompleteVisionMetadata = true;
3388
+ break;
3389
+ }
3390
+ }
3391
+ if (!hasIncompleteVisionMetadata) {
3392
+ break;
3325
3393
  }
3326
3394
  }
3327
- return null;
3395
+ if (deduped.size === 0) {
3396
+ return null;
3397
+ }
3398
+ return Array.from(deduped.values())
3399
+ .map(entry => entry.model)
3400
+ .sort((left, right) => left.id.localeCompare(right.id));
3328
3401
  };
3329
3402
  if (options.signal !== undefined) {
3330
3403
  return fetchModels(options.signal);
@@ -3339,18 +3412,20 @@ export function litellmModelManagerOptions(
3339
3412
  const baseUrl = config?.baseUrl ?? Bun.env.LITELLM_BASE_URL ?? "http://localhost:4000/v1";
3340
3413
  return {
3341
3414
  providerId: "litellm",
3342
- // rich-v3 invalidates rows cached before reseller usage-suffix stripping
3343
- // and placeholder-only `all-team-models` filtering; bump the version whenever
3344
- // the mappers below change, or warm authoritative caches keep serving
3345
- // pre-change rows for the full TTL.
3346
- cacheProviderId: `litellm:rich-v3:${Bun.hash(baseUrl).toString(36)}`,
3415
+ // rich-v4 invalidates rows cached before LiteLLM ids gained bundled
3416
+ // reference fallback and before discovery continued past `/model_group/info`
3417
+ // when that endpoint omitted vision metadata. Earlier versions handled
3418
+ // reseller usage-suffix stripping and placeholder-only `all-team-models`
3419
+ // filtering; bump the version whenever the mappers below change, or warm
3420
+ // authoritative caches keep serving pre-change rows for the full TTL.
3421
+ cacheProviderId: `litellm:rich-v4:${Bun.hash(baseUrl).toString(36)}`,
3347
3422
  // litellm is a local-only proxy and is never bundled in models.json (that
3348
3423
  // would leak the machine's localhost catalog). Prefer the proxy's richer
3349
- // management metadata, then fall back to /v1/models and enrich bare ids
3350
- // against models.dev like the gateway providers (fireworks et al.) do.
3424
+ // management metadata, then enrich ids against models.dev with the bundled
3425
+ // catalog as a fallback before using /v1/models.
3351
3426
  fetchDynamicModels: async () => {
3352
3427
  const modelsDevReferences = await loadModelsDevReferences<"openai-completions">(config?.fetch);
3353
- const resolveReference = (id: string) => modelsDevReferences.get(id);
3428
+ const resolveReference = createReferenceResolver(modelsDevReferences);
3354
3429
  const richModels = await fetchLiteLLMRichModels({
3355
3430
  api: "openai-completions",
3356
3431
  provider: "litellm",