@oh-my-pi/pi-catalog 16.3.11 → 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 +8 -0
- package/package.json +3 -3
- package/src/compat/anthropic.ts +5 -8
- package/src/provider-models/openai-compat.ts +91 -16
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
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
|
+
|
|
5
13
|
## [16.3.11] - 2026-07-06
|
|
6
14
|
|
|
7
15
|
### Added
|
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.
|
|
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.
|
|
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.
|
|
42
|
+
"@oh-my-pi/pi-ai": "16.3.12",
|
|
43
43
|
"@types/bun": "^1.3.14"
|
|
44
44
|
},
|
|
45
45
|
"engines": {
|
package/src/compat/anthropic.ts
CHANGED
|
@@ -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:
|
|
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;
|
|
@@ -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<
|
|
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,
|
|
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
|
-
|
|
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
|
|
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
|
|
3323
|
-
if (
|
|
3324
|
-
|
|
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
|
-
|
|
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-
|
|
3343
|
-
//
|
|
3344
|
-
//
|
|
3345
|
-
//
|
|
3346
|
-
|
|
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
|
|
3350
|
-
//
|
|
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 = (
|
|
3428
|
+
const resolveReference = createReferenceResolver(modelsDevReferences);
|
|
3354
3429
|
const richModels = await fetchLiteLLMRichModels({
|
|
3355
3430
|
api: "openai-completions",
|
|
3356
3431
|
provider: "litellm",
|