jeopi-catalog 16.6.1 → 16.6.2

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,16 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [16.6.2] - 2026-10-03
6
+
7
+ ### Changed
8
+
9
+ - Regenerated the Anthropic catalog with Claude Opus 5.5 and Sonnet 5.5 and updated the first-party default to `claude-opus-5-5`.
10
+
11
+ ### Fixed
12
+
13
+ - Normalize Anthropic discovery to `/v1/models`, follow all cursor pages, and prefer live display names, token limits, image support, and reasoning capabilities while retaining reference pricing. Failed pagination keeps the previous complete catalog, and discovery requests have bounded timeouts.
14
+
5
15
  ## [16.6.0] - 2026-10-01
6
16
 
7
17
  ### Fixed
@@ -54,6 +54,8 @@ export interface FetchOpenAICompatibleModelsOptions<TApi extends Api> {
54
54
  timeoutMs?: number;
55
55
  /** Optional fetch implementation override for testing/custom runtimes. */
56
56
  fetch?: FetchImpl;
57
+ /** Follow Anthropic's `has_more` / `last_id` cursor protocol via `after_id`. */
58
+ pagination?: "anthropic";
57
59
  /**
58
60
  * Optional post-normalization filter.
59
61
  * Return false to skip a model.
@@ -28,7 +28,7 @@ export declare const CATALOG_PROVIDERS: readonly [{
28
28
  readonly defaultModel: "us.anthropic.claude-opus-4-8";
29
29
  }, {
30
30
  readonly id: "anthropic";
31
- readonly defaultModel: "claude-opus-4-8";
31
+ readonly defaultModel: "claude-opus-5-5";
32
32
  readonly envVars: readonly ["ANTHROPIC_API_KEY"];
33
33
  readonly createModelManagerOptions: (config: ModelManagerConfig) => import("..").ModelManagerOptions<"anthropic-messages", unknown>;
34
34
  readonly catalogDiscovery: {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "jeopi-catalog",
4
- "version": "16.6.1",
4
+ "version": "16.6.2",
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.6.1",
37
+ "jeopi-utils": "16.6.2",
38
38
  "arktype": "2.2.3",
39
39
  "zod": "^4"
40
40
  },
41
41
  "devDependencies": {
42
- "jeopi-ai": "16.6.1",
42
+ "jeopi-ai": "16.6.2",
43
43
  "@types/bun": "^1.3.14"
44
44
  },
45
45
  "engines": {
@@ -1,6 +1,6 @@
1
1
  import { type } from "arktype";
2
2
  import type { Api, FetchImpl, ModelSpec, Provider } from "../types";
3
- import { discoveryFetch } from "../utils";
3
+ import { discoveryFetch, isRecord } from "../utils";
4
4
 
5
5
  const MODELS_PATH = "/models";
6
6
 
@@ -100,6 +100,8 @@ export interface FetchOpenAICompatibleModelsOptions<TApi extends Api> {
100
100
  timeoutMs?: number;
101
101
  /** Optional fetch implementation override for testing/custom runtimes. */
102
102
  fetch?: FetchImpl;
103
+ /** Follow Anthropic's `has_more` / `last_id` cursor protocol via `after_id`. */
104
+ pagination?: "anthropic";
103
105
  /**
104
106
  * Optional post-normalization filter.
105
107
  * Return false to skip a model.
@@ -139,42 +141,45 @@ export async function fetchOpenAICompatibleModels<TApi extends Api>(
139
141
  }
140
142
 
141
143
  const fetchImpl = discoveryFetch(options.fetch);
142
- const fetchPayload = async (signal?: AbortSignal): Promise<unknown | null> => {
143
- let response: Response;
144
- try {
145
- response = await fetchImpl(`${baseUrl}${MODELS_PATH}`, {
146
- method: "GET",
147
- headers: requestHeaders,
148
- signal,
149
- });
150
- } catch {
151
- return null;
152
- }
153
-
154
- if (!response.ok) {
155
- return null;
156
- }
157
-
158
- try {
159
- return await response.json();
160
- } catch {
161
- return null;
144
+ const fetchEntries = async (signal?: AbortSignal): Promise<ParsedOpenAICompatibleModelRecord[] | null> => {
145
+ const entries: ParsedOpenAICompatibleModelRecord[] = [];
146
+ const cursors = new Set<string>();
147
+ let url = `${baseUrl}${MODELS_PATH}`;
148
+ while (true) {
149
+ let payload: unknown;
150
+ try {
151
+ const response = await fetchImpl(url, {
152
+ method: "GET",
153
+ headers: requestHeaders,
154
+ signal,
155
+ });
156
+ if (!response.ok) return null;
157
+ payload = await response.json();
158
+ } catch {
159
+ return null;
160
+ }
161
+ const page = extractModelEntries(payload);
162
+ if (page === null) return null;
163
+ entries.push(...page);
164
+ if (options.pagination !== "anthropic" || !isRecord(payload) || payload.has_more !== true) {
165
+ return entries;
166
+ }
167
+ const cursor = payload.last_id;
168
+ // A broken/repeated cursor must not loop or cache an incomplete catalog.
169
+ if (typeof cursor !== "string" || cursor.length === 0 || cursors.has(cursor) || page.length === 0) {
170
+ return null;
171
+ }
172
+ cursors.add(cursor);
173
+ url = `${baseUrl}${MODELS_PATH}?after_id=${encodeURIComponent(cursor)}`;
162
174
  }
163
175
  };
164
- const payload =
176
+ const entries =
165
177
  options.signal !== undefined
166
- ? await fetchPayload(options.signal)
178
+ ? await fetchEntries(options.signal)
167
179
  : options.timeoutMs !== undefined
168
- ? await withOpenAICompatibleDiscoveryTimeout(options.timeoutMs, fetchPayload)
169
- : await fetchPayload();
170
- if (payload === null) {
171
- return null;
172
- }
173
-
174
- const entries = extractModelEntries(payload);
175
- if (entries === null) {
176
- return null;
177
- }
180
+ ? await withOpenAICompatibleDiscoveryTimeout(options.timeoutMs, fetchEntries)
181
+ : await fetchEntries();
182
+ if (entries === null) return null;
178
183
 
179
184
  const context: OpenAICompatibleModelMapperContext<TApi> = {
180
185
  api: options.api,
package/src/models.json CHANGED
@@ -11154,6 +11154,44 @@
11154
11154
  "supportsDisplay": true
11155
11155
  }
11156
11156
  },
11157
+ "claude-fable-5-1": {
11158
+ "id": "claude-fable-5-1",
11159
+ "name": "Claude Fable 5.1",
11160
+ "api": "anthropic-messages",
11161
+ "provider": "anthropic",
11162
+ "baseUrl": "https://api.anthropic.com",
11163
+ "reasoning": true,
11164
+ "input": [
11165
+ "text",
11166
+ "image"
11167
+ ],
11168
+ "cost": {
11169
+ "input": 10,
11170
+ "output": 50,
11171
+ "cacheRead": 1,
11172
+ "cacheWrite": 12.5
11173
+ },
11174
+ "contextWindow": 1000000,
11175
+ "maxTokens": 128000,
11176
+ "thinking": {
11177
+ "mode": "anthropic-adaptive",
11178
+ "efforts": [
11179
+ "minimal",
11180
+ "low",
11181
+ "medium",
11182
+ "high",
11183
+ "xhigh"
11184
+ ],
11185
+ "effortMap": {
11186
+ "minimal": "low",
11187
+ "low": "medium",
11188
+ "medium": "high",
11189
+ "high": "xhigh",
11190
+ "xhigh": "max"
11191
+ },
11192
+ "supportsDisplay": true
11193
+ }
11194
+ },
11157
11195
  "claude-haiku-4-5": {
11158
11196
  "id": "claude-haiku-4-5",
11159
11197
  "name": "Claude Haiku 4.5",
@@ -11532,6 +11570,82 @@
11532
11570
  "supportsDisplay": true
11533
11571
  }
11534
11572
  },
11573
+ "claude-opus-5": {
11574
+ "id": "claude-opus-5",
11575
+ "name": "Claude Opus 5",
11576
+ "api": "anthropic-messages",
11577
+ "provider": "anthropic",
11578
+ "baseUrl": "https://api.anthropic.com",
11579
+ "reasoning": true,
11580
+ "input": [
11581
+ "text",
11582
+ "image"
11583
+ ],
11584
+ "cost": {
11585
+ "input": 5,
11586
+ "output": 25,
11587
+ "cacheRead": 0.5,
11588
+ "cacheWrite": 6.25
11589
+ },
11590
+ "contextWindow": 1000000,
11591
+ "maxTokens": 128000,
11592
+ "thinking": {
11593
+ "mode": "anthropic-adaptive",
11594
+ "efforts": [
11595
+ "minimal",
11596
+ "low",
11597
+ "medium",
11598
+ "high",
11599
+ "xhigh"
11600
+ ],
11601
+ "effortMap": {
11602
+ "minimal": "low",
11603
+ "low": "medium",
11604
+ "medium": "high",
11605
+ "high": "xhigh",
11606
+ "xhigh": "max"
11607
+ },
11608
+ "supportsDisplay": true
11609
+ }
11610
+ },
11611
+ "claude-opus-5-5": {
11612
+ "id": "claude-opus-5-5",
11613
+ "name": "Claude Opus 5.5",
11614
+ "api": "anthropic-messages",
11615
+ "provider": "anthropic",
11616
+ "baseUrl": "https://api.anthropic.com",
11617
+ "reasoning": true,
11618
+ "input": [
11619
+ "text",
11620
+ "image"
11621
+ ],
11622
+ "cost": {
11623
+ "input": 4,
11624
+ "output": 20,
11625
+ "cacheRead": 0.2,
11626
+ "cacheWrite": 5
11627
+ },
11628
+ "contextWindow": 1000000,
11629
+ "maxTokens": 128000,
11630
+ "thinking": {
11631
+ "mode": "anthropic-adaptive",
11632
+ "efforts": [
11633
+ "minimal",
11634
+ "low",
11635
+ "medium",
11636
+ "high",
11637
+ "xhigh"
11638
+ ],
11639
+ "effortMap": {
11640
+ "minimal": "low",
11641
+ "low": "medium",
11642
+ "medium": "high",
11643
+ "high": "xhigh",
11644
+ "xhigh": "max"
11645
+ },
11646
+ "supportsDisplay": true
11647
+ }
11648
+ },
11535
11649
  "claude-sonnet-4-0": {
11536
11650
  "id": "claude-sonnet-4-0",
11537
11651
  "name": "Claude Sonnet 4",
@@ -11711,6 +11825,44 @@
11711
11825
  },
11712
11826
  "supportsDisplay": true
11713
11827
  }
11828
+ },
11829
+ "claude-sonnet-5-5": {
11830
+ "id": "claude-sonnet-5-5",
11831
+ "name": "Claude Sonnet 5.5",
11832
+ "api": "anthropic-messages",
11833
+ "provider": "anthropic",
11834
+ "baseUrl": "https://api.anthropic.com",
11835
+ "reasoning": true,
11836
+ "input": [
11837
+ "text",
11838
+ "image"
11839
+ ],
11840
+ "cost": {
11841
+ "input": 2,
11842
+ "output": 10,
11843
+ "cacheRead": 0.2,
11844
+ "cacheWrite": 2.5
11845
+ },
11846
+ "contextWindow": 1000000,
11847
+ "maxTokens": 128000,
11848
+ "thinking": {
11849
+ "mode": "anthropic-adaptive",
11850
+ "efforts": [
11851
+ "minimal",
11852
+ "low",
11853
+ "medium",
11854
+ "high",
11855
+ "xhigh"
11856
+ ],
11857
+ "effortMap": {
11858
+ "minimal": "low",
11859
+ "low": "medium",
11860
+ "medium": "high",
11861
+ "high": "xhigh",
11862
+ "xhigh": "max"
11863
+ },
11864
+ "supportsDisplay": true
11865
+ }
11714
11866
  }
11715
11867
  },
11716
11868
  "azure": {
@@ -80,7 +80,7 @@ export const CATALOG_PROVIDERS = [
80
80
  },
81
81
  {
82
82
  id: "anthropic",
83
- defaultModel: "claude-opus-4-8",
83
+ defaultModel: "claude-opus-5-5",
84
84
  envVars: ["ANTHROPIC_API_KEY"],
85
85
  createModelManagerOptions: (config: ModelManagerConfig) => anthropicModelManagerOptions(config),
86
86
  catalogDiscovery: { label: "Anthropic" },
@@ -160,8 +160,8 @@ function buildAnthropicReferenceMap(
160
160
  for (const model of modelsDevModels) {
161
161
  merged.set(model.id, model);
162
162
  }
163
- // Anthropic /v1/models does not carry token limits, so bundled metadata stays canonical
164
- // for known models while models.dev only fills gaps for newly discovered ids.
163
+ // Bundled metadata supplies pricing and missing capabilities; first-party
164
+ // discovery overrides limits and capabilities when the endpoint reports them.
165
165
  const bundledModels = getBundledModels("anthropic").filter(
166
166
  (model): model is Model<"anthropic-messages"> => model.api === "anthropic-messages",
167
167
  );
@@ -3655,51 +3655,58 @@ export function anthropicModelManagerOptions(
3655
3655
  config?: AnthropicModelManagerConfig,
3656
3656
  ): ModelManagerOptions<"anthropic-messages"> {
3657
3657
  const apiKey = config?.apiKey;
3658
- const baseUrl = config?.baseUrl ?? ANTHROPIC_BASE_URL;
3658
+ const baseUrl = normalizeAnthropicBaseUrl(config?.baseUrl, ANTHROPIC_BASE_URL);
3659
3659
  return {
3660
3660
  providerId: "anthropic",
3661
3661
  modelsDev: {
3662
- fetch: () => fetchModelsDevPayload(config?.fetch),
3662
+ fetch: () => withCatalogDiscoveryTimeout(10_000, signal => fetchModelsDevPayload(config?.fetch, signal)),
3663
3663
  map: payload => mapAnthropicModelsDev(payload, baseUrl),
3664
3664
  },
3665
3665
  ...(apiKey && {
3666
- fetchDynamicModels: async () => {
3667
- const modelsDevModels = await fetchModelsDevPayload(config?.fetch)
3668
- .then(payload => mapAnthropicModelsDev(payload, baseUrl))
3669
- .catch(() => []);
3670
- const references = buildAnthropicReferenceMap(modelsDevModels);
3671
- return (
3672
- fetchOpenAICompatibleModels({
3666
+ fetchDynamicModels: () =>
3667
+ withCatalogDiscoveryTimeout(15_000, async signal => {
3668
+ const modelsDevModels = await fetchModelsDevPayload(config?.fetch, signal)
3669
+ .then(payload => mapAnthropicModelsDev(payload, baseUrl))
3670
+ .catch(() => []);
3671
+ const references = buildAnthropicReferenceMap(modelsDevModels);
3672
+ return fetchOpenAICompatibleModels({
3673
3673
  api: "anthropic-messages",
3674
3674
  provider: "anthropic",
3675
- baseUrl,
3675
+ baseUrl: toAnthropicDiscoveryBaseUrl(baseUrl),
3676
3676
  headers: buildAnthropicDiscoveryHeaders(apiKey),
3677
- mapModel: (
3678
- entry: OpenAICompatibleModelRecord,
3679
- defaults: ModelSpec<"anthropic-messages">,
3680
- _context: OpenAICompatibleModelMapperContext<"anthropic-messages">,
3681
- ): ModelSpec<"anthropic-messages"> => {
3682
- const discoveredName = typeof entry.display_name === "string" ? entry.display_name : defaults.name;
3677
+ pagination: "anthropic",
3678
+ signal,
3679
+ mapModel: (entry, defaults): ModelSpec<"anthropic-messages"> => {
3683
3680
  const reference = references.get(defaults.id);
3684
- if (!reference) {
3685
- return {
3686
- ...defaults,
3687
- name: discoveredName,
3688
- };
3689
- }
3681
+ const capabilities = isRecord(entry.capabilities) ? entry.capabilities : undefined;
3682
+ const thinking =
3683
+ capabilities && isRecord(capabilities.thinking) ? capabilities.thinking : undefined;
3684
+ const imageInput =
3685
+ capabilities && isRecord(capabilities.image_input) ? capabilities.image_input : undefined;
3690
3686
  return {
3691
- ...reference,
3687
+ ...(reference ?? defaults),
3692
3688
  id: defaults.id,
3693
- name: discoveredName,
3689
+ name: toModelName(entry.display_name, reference?.name ?? defaults.name),
3694
3690
  api: "anthropic-messages",
3695
3691
  provider: "anthropic",
3696
3692
  baseUrl,
3693
+ reasoning: toBoolean(thinking?.supported) ?? reference?.reasoning ?? defaults.reasoning,
3694
+ input:
3695
+ toBoolean(imageInput?.supported) === true
3696
+ ? ["text", "image"]
3697
+ : toBoolean(imageInput?.supported) === false
3698
+ ? ["text"]
3699
+ : (reference?.input ?? defaults.input),
3700
+ contextWindow: toPositiveNumber(
3701
+ entry.max_input_tokens,
3702
+ reference?.contextWindow ?? defaults.contextWindow,
3703
+ ),
3704
+ maxTokens: toPositiveNumber(entry.max_tokens, reference?.maxTokens ?? defaults.maxTokens),
3697
3705
  };
3698
3706
  },
3699
3707
  fetch: config?.fetch,
3700
- }) ?? null
3701
- );
3702
- },
3708
+ });
3709
+ }),
3703
3710
  }),
3704
3711
  };
3705
3712
  }
@@ -220,6 +220,33 @@ function geminiFlashFamily(mode: "budget" | "google-level"): EffortVariantFamily
220
220
  };
221
221
  }
222
222
 
223
+ function gemini38FlashFamily(mode: "budget" | "google-level"): EffortVariantFamily {
224
+ const budget = mode === "budget";
225
+ return {
226
+ id: "gemini-3.8-flash",
227
+ name: "Gemini 3.8 Flash",
228
+ members: ["gemini-3.8-flash-tiered", "gemini-3.8-flash"],
229
+ routing: {
230
+ off: "gemini-3.8-flash-tiered",
231
+ [Effort.Minimal]: "gemini-3.8-flash-tiered",
232
+ [Effort.Low]: "gemini-3.8-flash-tiered",
233
+ [Effort.Medium]: "gemini-3.8-flash-tiered",
234
+ [Effort.High]: "gemini-3.8-flash-tiered",
235
+ },
236
+ thinking: budget
237
+ ? { mode: "budget", efforts: GEMINI_3_FLASH_FAMILY_EFFORTS, effortBudgets: GEMINI_3_FLASH_FAMILY_BUDGETS }
238
+ : { mode: "google-level", efforts: GEMINI_3_FLASH_FAMILY_EFFORTS },
239
+ suppressWhenOff: true,
240
+ preserveAbsentEffortRoutes: true,
241
+ extraAliases: [
242
+ "gemini-3.8-flash-tiered",
243
+ "gemini-3.8-flash-low",
244
+ "gemini-3.8-flash-medium",
245
+ "gemini-3.8-flash-high",
246
+ ],
247
+ };
248
+ }
249
+
223
250
  function geminiProFamily(mode: "budget" | "google-level"): EffortVariantFamily {
224
251
  const budget = mode === "budget";
225
252
  return {
@@ -302,12 +329,22 @@ const SHARED_CCA_FAMILIES: readonly EffortVariantFamily[] = [
302
329
 
303
330
  /** `google-antigravity` (daily-cloudcode-pa): Gemini 3.x on the budget transport. */
304
331
  export const ANTIGRAVITY_VARIANT_COLLAPSE_TABLE: VariantCollapseTable = {
305
- families: [geminiFlashFamily("budget"), geminiProFamily("budget"), ...SHARED_CCA_FAMILIES],
332
+ families: [
333
+ gemini38FlashFamily("budget"),
334
+ geminiFlashFamily("budget"),
335
+ geminiProFamily("budget"),
336
+ ...SHARED_CCA_FAMILIES,
337
+ ],
306
338
  };
307
339
 
308
340
  /** `google-gemini-cli` (cloudcode-pa): Gemini 3.x on the level transport (official CLI parity). */
309
341
  export const GEMINI_CLI_VARIANT_COLLAPSE_TABLE: VariantCollapseTable = {
310
- families: [geminiFlashFamily("google-level"), geminiProFamily("google-level"), ...SHARED_CCA_FAMILIES],
342
+ families: [
343
+ gemini38FlashFamily("google-level"),
344
+ geminiFlashFamily("google-level"),
345
+ geminiProFamily("google-level"),
346
+ ...SHARED_CCA_FAMILIES,
347
+ ],
311
348
  };
312
349
  export const DEVIN_VARIANT_COLLAPSE_TABLE: VariantCollapseTable = {
313
350
  families: [
@@ -58,6 +58,8 @@ export interface AntigravityModelWireProfile {
58
58
  maxOutputTokens: number;
59
59
  }
60
60
  export const ANTIGRAVITY_MODEL_WIRE_PROFILES: Readonly<Record<string, AntigravityModelWireProfile>> = {
61
+ "gemini-3.8-flash-tiered": { modelEnum: "MODEL_PLACEHOLDER_M322", maxOutputTokens: 65536 },
62
+ "gemini-3.8-flash": { modelEnum: "MODEL_PLACEHOLDER_M322", maxOutputTokens: 65536 },
61
63
  "gemini-3.5-flash-extra-low": { modelEnum: "MODEL_PLACEHOLDER_M187", maxOutputTokens: 65536 },
62
64
  "gemini-3.5-flash-low": { modelEnum: "MODEL_PLACEHOLDER_M20", maxOutputTokens: 65536 },
63
65
  "gemini-3-flash-agent": { modelEnum: "MODEL_PLACEHOLDER_M132", maxOutputTokens: 65536 },