jeopi-catalog 16.4.2 → 16.4.3
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 +10 -0
- package/dist/types/types.d.ts +2 -0
- package/dist/types/wire/github-copilot.d.ts +1 -7
- package/package.json +3 -3
- package/src/discovery/cursor.ts +3 -0
- package/src/identity/classify.ts +9 -4
- package/src/model-manager.ts +29 -11
- package/src/model-thinking.ts +11 -6
- package/src/provider-models/openai-compat.ts +5 -8
- package/src/provider-models/special.ts +3 -0
- package/src/types.ts +2 -0
- package/src/wire/github-copilot.ts +1 -7
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [16.4.3] - 2026-07-22
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Fixed SAP AI Core Claude ids in version-first order (`anthropic--claude-4.8-opus`) parsing as unknown, restoring Anthropic adaptive thinking metadata and capability gates. Ported from oh-my-pi (upstream `c1480b29e`).
|
|
10
|
+
- Fixed GitHub Copilot Business and Enterprise discovery downgrading vision-capable models to text-only unconditionally; the catalog now honors upstream's `supports.vision` flag on non-personal endpoints (only an explicit `false`, or an omitted flag, falls back to text-only) instead of ignoring it outright. Ported from oh-my-pi (upstream `e58d2c460`, `4bae9a42a`, `b0f22caf8`).
|
|
11
|
+
- Fixed stale cached model limits overriding updated static catalog limits after a static catalog fingerprint mismatch: same-id cache rows are now sanitized (`contextWindow`/`maxTokens` reset to `null`, deferring to the fresh static entry) instead of passed through verbatim, while cache-only ids (no static counterpart) keep their cached limits. Ported from oh-my-pi (upstream `b7aa046ed`).
|
|
12
|
+
- Fixed Cursor discovery to preserve `GetUsableModels` max-mode metadata for premium models and invalidate stale pre-max-mode cache rows (bumped the Cursor cache namespace so old cache entries written before `cursorMaxMode` was tracked can never satisfy a fresh-cache read). Ported from oh-my-pi (upstream `358811115`, `f98ef2e1e`).
|
|
13
|
+
- Fixed Z.ai GLM-5.2 on the `anthropic-messages` coding endpoint (`api.z.ai/api/anthropic`) deriving generic `budget` mode with five synthetic effort tiers instead of the wire-exact `anthropic-budget-effort` mode with the two-tier high/max scale Z.ai's Anthropic proxy actually serves (same as Umans). The reasoning-effort predicate now matches both `umans` and `zai` providers on `anthropic-messages`. Ported from oh-my-pi (upstream `f34034fa7` by @oldschoola; adapted to jeopi's `Effort` enum, which has no `Max` tier — jeopi exposes `[High, XHigh]` with `effortMap: { xhigh: "max" }` rather than upstream's literal `[high, max]` efforts, matching the existing Umans/Ollama Cloud GLM-5.2 convention. The bundled `models.json` `zai/glm-5.2` entry's stale `thinking` blob was intentionally left as-is per repo convention — `buildModel()` always re-derives `thinking` from this policy at runtime via `resolveModelThinking`, so the stored JSON field is inert; a full live `gen:models` regeneration was out of scope for this targeted fix).
|
|
14
|
+
|
|
5
15
|
## [16.2.29] - 2026-07-10
|
|
6
16
|
|
|
7
17
|
### Fixed
|
package/dist/types/types.d.ts
CHANGED
|
@@ -572,6 +572,8 @@ export interface Model<TApi extends Api = Api> {
|
|
|
572
572
|
supportsTools?: boolean;
|
|
573
573
|
/** GitLab Duo Workflow root namespace selected during catalog discovery. */
|
|
574
574
|
gitlabDuoWorkflowRootNamespaceId?: string;
|
|
575
|
+
/** Cursor `max_mode` request flag returned by `GetUsableModels` for premium models that require max mode. */
|
|
576
|
+
cursorMaxMode?: boolean;
|
|
575
577
|
cost: {
|
|
576
578
|
input: number;
|
|
577
579
|
output: number;
|
|
@@ -28,13 +28,7 @@ export type ParsedGitHubCopilotApiKey = {
|
|
|
28
28
|
apiEndpoint?: string;
|
|
29
29
|
};
|
|
30
30
|
export declare function isPublicGitHubHost(host: string): boolean;
|
|
31
|
-
/**
|
|
32
|
-
* Canonical personal-Copilot API host. The business
|
|
33
|
-
* (`api.business.githubcopilot.com`) and enterprise (`copilot-api.{domain}`)
|
|
34
|
-
* endpoints respond with HTTP 400 "vision is not supported" on image inputs,
|
|
35
|
-
* so catalog discovery and capability gates MUST honour the upstream's
|
|
36
|
-
* `supports.vision` flag only for this exact base URL.
|
|
37
|
-
*/
|
|
31
|
+
/** Canonical personal-Copilot API host. */
|
|
38
32
|
export declare const PERSONAL_GITHUB_COPILOT_BASE_URL: "https://api.githubcopilot.com";
|
|
39
33
|
/** `true` when the resolved base URL is the canonical personal-Copilot host. */
|
|
40
34
|
export declare function isPersonalGitHubCopilotBaseUrl(baseUrl: string | undefined): boolean;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "jeopi-catalog",
|
|
4
|
-
"version": "16.4.
|
|
4
|
+
"version": "16.4.3",
|
|
5
5
|
"description": "Model catalog for jeopi: bundled model database, provider discovery descriptors, model identity, classification, and equivalence",
|
|
6
6
|
"homepage": "https://github.com/akillness/jeopi",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -34,12 +34,12 @@
|
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@bufbuild/protobuf": "^2.12.0",
|
|
37
|
-
"jeopi-utils": "16.4.
|
|
37
|
+
"jeopi-utils": "16.4.3",
|
|
38
38
|
"arktype": "^2.2.0",
|
|
39
39
|
"zod": "^4"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"jeopi-ai": "16.4.
|
|
42
|
+
"jeopi-ai": "16.4.3",
|
|
43
43
|
"@types/bun": "^1.3.14"
|
|
44
44
|
},
|
|
45
45
|
"engines": {
|
package/src/discovery/cursor.ts
CHANGED
|
@@ -28,6 +28,7 @@ const CursorModelDetailsSchema = type({
|
|
|
28
28
|
displayModelId: OptionalDisplayNameSchema.default(undefined),
|
|
29
29
|
aliases: CursorAliasesSchema.default(() => []),
|
|
30
30
|
"thinkingDetails?": "unknown",
|
|
31
|
+
maxMode: "boolean = false",
|
|
31
32
|
});
|
|
32
33
|
|
|
33
34
|
const CursorModelsInnerSchema = type("unknown[]");
|
|
@@ -283,6 +284,7 @@ function normalizeCursorModel(
|
|
|
283
284
|
name,
|
|
284
285
|
baseUrl: baseUrlOverride ?? reference.baseUrl,
|
|
285
286
|
reasoning,
|
|
287
|
+
cursorMaxMode: details.maxMode,
|
|
286
288
|
};
|
|
287
289
|
}
|
|
288
290
|
return {
|
|
@@ -296,6 +298,7 @@ function normalizeCursorModel(
|
|
|
296
298
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
297
299
|
contextWindow: DEFAULT_CONTEXT_WINDOW,
|
|
298
300
|
maxTokens: DEFAULT_MAX_TOKENS,
|
|
301
|
+
cursorMaxMode: details.maxMode,
|
|
299
302
|
};
|
|
300
303
|
}
|
|
301
304
|
|
package/src/identity/classify.ts
CHANGED
|
@@ -106,15 +106,20 @@ export const parseGeminiModel = parser((modelId): GeminiModel | null => {
|
|
|
106
106
|
});
|
|
107
107
|
|
|
108
108
|
export const parseAnthropicModel = parser((modelId): AnthropicModel | null => {
|
|
109
|
-
const
|
|
110
|
-
|
|
109
|
+
const kindFirst = /claude-(opus|sonnet|fable|mythos)-(\d{1,2}(?:[.-]\d{1,2}){0,2})\b/.exec(modelId);
|
|
110
|
+
const versionFirst = kindFirst
|
|
111
|
+
? null
|
|
112
|
+
: /claude-(\d{1,2}(?:[.-]\d{1,2}){0,2})-(opus|sonnet|fable|mythos)\b/.exec(modelId);
|
|
113
|
+
const kind = kindFirst?.[1] ?? versionFirst?.[2];
|
|
114
|
+
const versionInput = kindFirst?.[2] ?? versionFirst?.[1];
|
|
115
|
+
if (!kind || !versionInput) {
|
|
111
116
|
return null;
|
|
112
117
|
}
|
|
113
|
-
const version = parseSemVer(
|
|
118
|
+
const version = parseSemVer(versionInput);
|
|
114
119
|
if (!version) {
|
|
115
120
|
return null;
|
|
116
121
|
}
|
|
117
|
-
return { family: "anthropic", kind:
|
|
122
|
+
return { family: "anthropic", kind: kind as AnthropicKind, version };
|
|
118
123
|
});
|
|
119
124
|
|
|
120
125
|
export const parseOpenAIModel = parser((modelId): OpenAIModel | null => {
|
package/src/model-manager.ts
CHANGED
|
@@ -152,8 +152,9 @@ export async function resolveProviderModels<TApi extends Api = Api, TModelsDevPa
|
|
|
152
152
|
const dynamicFetchSucceeded = fetchedDynamicModels !== null;
|
|
153
153
|
const cacheModels = dynamicFetchSucceeded
|
|
154
154
|
? []
|
|
155
|
-
:
|
|
155
|
+
: prepareCacheModelsForStaticMismatch(
|
|
156
156
|
normalizeModelList<TApi>(cache?.models ?? []),
|
|
157
|
+
staticModels,
|
|
157
158
|
cacheFingerprintMatches,
|
|
158
159
|
options.dropCachedModelIdsOnStaticMismatch,
|
|
159
160
|
);
|
|
@@ -188,8 +189,9 @@ export async function resolveProviderModels<TApi extends Api = Api, TModelsDevPa
|
|
|
188
189
|
collapseBuiltModelVariants(
|
|
189
190
|
mergeDynamicModels(
|
|
190
191
|
mergeModelSources(staticModels, modelsDevModels),
|
|
191
|
-
|
|
192
|
+
prepareCacheModelsForStaticMismatch(
|
|
192
193
|
normalizeModelList<TApi>(latestCache?.models ?? cache?.models ?? []),
|
|
194
|
+
staticModels,
|
|
193
195
|
cacheFingerprintMatches,
|
|
194
196
|
options.dropCachedModelIdsOnStaticMismatch,
|
|
195
197
|
),
|
|
@@ -260,16 +262,29 @@ function shouldFetchRemoteSources(
|
|
|
260
262
|
return false;
|
|
261
263
|
}
|
|
262
264
|
|
|
263
|
-
function
|
|
265
|
+
function prepareCacheModelsForStaticMismatch<TApi extends Api>(
|
|
264
266
|
models: readonly Model<TApi>[],
|
|
267
|
+
staticModels: readonly Model<TApi>[],
|
|
265
268
|
cacheFingerprintMatches: boolean,
|
|
266
269
|
ids: readonly string[] | undefined,
|
|
267
270
|
): Model<TApi>[] {
|
|
268
|
-
if (
|
|
269
|
-
return
|
|
271
|
+
if (models.length === 0) {
|
|
272
|
+
return [];
|
|
273
|
+
}
|
|
274
|
+
if (cacheFingerprintMatches) {
|
|
275
|
+
return [...models];
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const droppedIds = ids && ids.length > 0 ? new Set(ids) : undefined;
|
|
279
|
+
const staticIds = staticModels.length > 0 ? new Set(staticModels.map(model => model.id)) : undefined;
|
|
280
|
+
const sanitizedModels: Model<TApi>[] = [];
|
|
281
|
+
for (const model of models) {
|
|
282
|
+
if (droppedIds?.has(model.id)) {
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
sanitizedModels.push(staticIds?.has(model.id) ? { ...model, contextWindow: null, maxTokens: null } : model);
|
|
270
286
|
}
|
|
271
|
-
|
|
272
|
-
return models.filter(model => !droppedIds.has(model.id));
|
|
287
|
+
return sanitizedModels;
|
|
273
288
|
}
|
|
274
289
|
|
|
275
290
|
function mergeModelSources<TApi extends Api>(...sources: readonly (readonly Model<TApi>[])[]): Model<TApi>[] {
|
|
@@ -351,11 +366,14 @@ function fingerprintStatic<TApi extends Api>(
|
|
|
351
366
|
function mergeDynamicModel<TApi extends Api>(existingModel: Model<TApi>, dynamicModel: Model<TApi>): Model<TApi> {
|
|
352
367
|
// When discovery resolves the same model id to a different endpoint (e.g.
|
|
353
368
|
// a GitHub Copilot business/enterprise host), the bundled reference's
|
|
354
|
-
// capabilities are pinned to
|
|
355
|
-
//
|
|
356
|
-
//
|
|
369
|
+
// capabilities are pinned to another endpoint and no longer apply. Copilot
|
|
370
|
+
// dynamic discovery also pre-applies the correct image fallback for omitted
|
|
371
|
+
// `supports.vision`, so its explicit `false` must not be OR-upgraded by the
|
|
372
|
+
// canonical bundled model.
|
|
357
373
|
const endpointChanged = existingModel.baseUrl !== dynamicModel.baseUrl;
|
|
358
|
-
const
|
|
374
|
+
const dynamicInputAuthoritative =
|
|
375
|
+
endpointChanged || (existingModel.provider === "github-copilot" && dynamicModel.provider === "github-copilot");
|
|
376
|
+
const supportsImage = dynamicInputAuthoritative
|
|
359
377
|
? dynamicModel.input.includes("image")
|
|
360
378
|
: existingModel.input.includes("image") || dynamicModel.input.includes("image");
|
|
361
379
|
// Re-build from spec stage: sparse compat comes from `compatConfig` (the
|
package/src/model-thinking.ts
CHANGED
|
@@ -306,7 +306,7 @@ function getModelDefinedEfforts<TApi extends Api>(
|
|
|
306
306
|
if (isZaiThinkingFormat(compat) || isOpenRouterThinkingFormat(compat)) {
|
|
307
307
|
return DEFAULT_REASONING_EFFORTS_WITH_XHIGH;
|
|
308
308
|
}
|
|
309
|
-
if (
|
|
309
|
+
if (isAnthropicMessagesGlm52ReasoningEffortModel(spec) || isOllamaCloudGlm52ReasoningEffortModel(spec)) {
|
|
310
310
|
return GLM_52_HIGH_MAX_REASONING_EFFORTS;
|
|
311
311
|
}
|
|
312
312
|
}
|
|
@@ -325,8 +325,12 @@ function isOllamaCloudGlm52ReasoningEffortModel<TApi extends Api>(spec: ModelSpe
|
|
|
325
325
|
return spec.api === "ollama-chat" && spec.provider === "ollama-cloud" && isGlm52ReasoningEffortModelId(spec.id);
|
|
326
326
|
}
|
|
327
327
|
|
|
328
|
-
function
|
|
329
|
-
return
|
|
328
|
+
function isAnthropicMessagesGlm52ReasoningEffortModel<TApi extends Api>(spec: ModelSpec<TApi>): boolean {
|
|
329
|
+
return (
|
|
330
|
+
spec.api === "anthropic-messages" &&
|
|
331
|
+
(spec.provider === "umans" || spec.provider === "zai") &&
|
|
332
|
+
isGlm52ReasoningEffortModelId(spec.id)
|
|
333
|
+
);
|
|
330
334
|
}
|
|
331
335
|
|
|
332
336
|
function isMinimaxReasoningModelOnAnthropicEndpoint<TApi extends Api>(spec: ModelSpec<TApi>): boolean {
|
|
@@ -382,7 +386,8 @@ function inferDetectedEffortMap<TApi extends Api>(
|
|
|
382
386
|
// `xhigh` 400s — collapse minimal->none, low/medium/high->high, xhigh->max.
|
|
383
387
|
// - OpenRouter: `max` 400s and `xhigh` IS its max tier, so it passes `xhigh`
|
|
384
388
|
// through literally (no map; the tier is exposed via getModelDefinedEfforts).
|
|
385
|
-
// - Umans
|
|
389
|
+
// - Umans, Ollama Cloud, and Z.ai's anthropic-messages proxy expose only
|
|
390
|
+
// high/max on their GLM-5.2 routes.
|
|
386
391
|
// - Other openai-compat hosts (Fireworks, resellers) keep their distinct
|
|
387
392
|
// lower tiers and host quirks (e.g. Fireworks rejects `minimal`, so
|
|
388
393
|
// `minimal->none` stays) and only remap the top `xhigh` UI tier onto the
|
|
@@ -391,7 +396,7 @@ function inferDetectedEffortMap<TApi extends Api>(
|
|
|
391
396
|
if (isGlm52 && isZaiThinkingFormat(compat)) {
|
|
392
397
|
return ZAI_GLM_52_REASONING_EFFORT_MAP;
|
|
393
398
|
}
|
|
394
|
-
if (
|
|
399
|
+
if (isAnthropicMessagesGlm52ReasoningEffortModel(spec) || isOllamaCloudGlm52ReasoningEffortModel(spec)) {
|
|
395
400
|
return GLM_52_XHIGH_MAX_EFFORT_MAP;
|
|
396
401
|
}
|
|
397
402
|
if (isSakanaFuguReasoningModel(spec)) {
|
|
@@ -581,7 +586,7 @@ function inferThinkingControlMode<TApi extends Api>(
|
|
|
581
586
|
if (isMinimaxReasoningModelOnAnthropicEndpoint(spec)) {
|
|
582
587
|
return "anthropic-adaptive";
|
|
583
588
|
}
|
|
584
|
-
if (
|
|
589
|
+
if (isAnthropicMessagesGlm52ReasoningEffortModel(spec)) {
|
|
585
590
|
return "anthropic-budget-effort";
|
|
586
591
|
}
|
|
587
592
|
if (parsedModel.family === "anthropic") {
|
|
@@ -3510,16 +3510,13 @@ export function githubCopilotModelManagerOptions(config?: GithubCopilotModelMana
|
|
|
3510
3510
|
? entry.name
|
|
3511
3511
|
: (reference?.name ?? defaults.name);
|
|
3512
3512
|
const api = inferCopilotApi(defaults.id);
|
|
3513
|
-
// `supports.vision` reports the model's intrinsic capability, but
|
|
3514
|
-
// the business/enterprise endpoints respond `400 vision is not
|
|
3515
|
-
// supported` on image inputs. Only honour the flag for the
|
|
3516
|
-
// canonical personal-Copilot host.
|
|
3517
3513
|
const supportsVision = extractCopilotSupportsVision(entry);
|
|
3518
|
-
const input: ModelSpec<Api>["input"] =
|
|
3519
|
-
|
|
3514
|
+
const input: ModelSpec<Api>["input"] =
|
|
3515
|
+
supportsVision === true
|
|
3520
3516
|
? ["text", "image"]
|
|
3521
|
-
:
|
|
3522
|
-
|
|
3517
|
+
: supportsVision === false || !isPersonalGitHubCopilotBaseUrl(baseUrl)
|
|
3518
|
+
? ["text"]
|
|
3519
|
+
: (reference?.input ?? defaults.input);
|
|
3523
3520
|
// With COPILOT_API_HEADERS the served window is the long-context
|
|
3524
3521
|
// ceiling; the default tier ends at token_prices.default.context_max
|
|
3525
3522
|
// prompt tokens. Cap the base entry to the default tier — the long
|
|
@@ -42,10 +42,13 @@ export interface CursorModelManagerConfig {
|
|
|
42
42
|
clientVersion?: string;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
const CURSOR_CACHE_PROVIDER_ID = "cursor:max-mode-v2";
|
|
46
|
+
|
|
45
47
|
export function cursorModelManagerOptions(config: CursorModelManagerConfig = {}): ModelManagerOptions<"cursor-agent"> {
|
|
46
48
|
const { apiKey, baseUrl, clientVersion } = config;
|
|
47
49
|
return {
|
|
48
50
|
providerId: "cursor",
|
|
51
|
+
cacheProviderId: CURSOR_CACHE_PROVIDER_ID,
|
|
49
52
|
...(apiKey
|
|
50
53
|
? {
|
|
51
54
|
fetchDynamicModels: async () => {
|
package/src/types.ts
CHANGED
|
@@ -701,6 +701,8 @@ export interface Model<TApi extends Api = Api> {
|
|
|
701
701
|
supportsTools?: boolean;
|
|
702
702
|
/** GitLab Duo Workflow root namespace selected during catalog discovery. */
|
|
703
703
|
gitlabDuoWorkflowRootNamespaceId?: string;
|
|
704
|
+
/** Cursor `max_mode` request flag returned by `GetUsableModels` for premium models that require max mode. */
|
|
705
|
+
cursorMaxMode?: boolean;
|
|
704
706
|
cost: {
|
|
705
707
|
input: number; // $/million tokens
|
|
706
708
|
output: number; // $/million tokens
|
|
@@ -45,13 +45,7 @@ export function isPublicGitHubHost(host: string): boolean {
|
|
|
45
45
|
return PUBLIC_GITHUB_HOSTS.has(host.trim().toLowerCase());
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
/**
|
|
49
|
-
* Canonical personal-Copilot API host. The business
|
|
50
|
-
* (`api.business.githubcopilot.com`) and enterprise (`copilot-api.{domain}`)
|
|
51
|
-
* endpoints respond with HTTP 400 "vision is not supported" on image inputs,
|
|
52
|
-
* so catalog discovery and capability gates MUST honour the upstream's
|
|
53
|
-
* `supports.vision` flag only for this exact base URL.
|
|
54
|
-
*/
|
|
48
|
+
/** Canonical personal-Copilot API host. */
|
|
55
49
|
export const PERSONAL_GITHUB_COPILOT_BASE_URL = "https://api.githubcopilot.com" as const;
|
|
56
50
|
|
|
57
51
|
/** `true` when the resolved base URL is the canonical personal-Copilot host. */
|