jeopi-catalog 16.5.6 → 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,23 @@
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
+
15
+ ## [16.6.0] - 2026-10-01
16
+
17
+ ### Fixed
18
+
19
+ - Refresh Antigravity's live model catalog with lazily resolved OAuth access, account-and-endpoint-scoped caching, and bounded cancellation. Successful discovery replaces stale bundled entries, including an empty catalog; failed refreshes retain the last authoritative snapshot without extending its lifetime.
20
+ - Preserve authoritative empty Antigravity results during provider-only catalog regeneration while retaining previous entries on discovery failure.
21
+
5
22
  ## [16.5.6] - 2026-10-01
6
23
 
7
24
  ### 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,6 +28,8 @@ export interface ModelManagerOptions<TApi extends Api = Api, TModelsDevPayload =
28
28
  cacheTtlMs?: number;
29
29
  /** When true, a successful dynamic fetch is the complete provider catalog and prunes static-only models. */
30
30
  dynamicModelsAuthoritative?: boolean;
31
+ /** Keep a prior complete catalog (including empty) on failed/offline refresh, without extending its TTL. */
32
+ preserveAuthoritativeCacheOnFailure?: boolean;
31
33
  /** Cached model ids to ignore when the cache was written against a different static catalog fingerprint. */
32
34
  dropCachedModelIdsOnStaticMismatch?: readonly string[];
33
35
  /** Trusted provider-wide headers restored locally, never persisted. */
@@ -59,6 +61,8 @@ export interface ModelManagerOptions<TApi extends Api = Api, TModelsDevPayload =
59
61
  export interface ModelResolutionResult<TApi extends Api = Api> {
60
62
  models: Model<TApi>[];
61
63
  stale: boolean;
64
+ /** A stale result retained a complete provider snapshot; callers must not resurrect bundled-only ids. */
65
+ preservedAuthoritativeCache?: boolean;
62
66
  }
63
67
  /**
64
68
  * Stateful facade over provider model resolution.
@@ -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: {
@@ -15,6 +15,10 @@ export interface GoogleAntigravityModelManagerConfig {
15
15
  oauthToken?: string;
16
16
  endpoint?: string;
17
17
  fetch?: FetchImpl;
18
+ /** Stable credential identity; falls back to the token when unavailable. */
19
+ cacheScope?: string;
20
+ /** Resolve a fresh token only when discovery actually needs the network. */
21
+ resolveOAuthToken?: (signal: AbortSignal) => Promise<string | undefined>;
18
22
  }
19
23
  export interface GoogleGeminiCliModelManagerConfig {
20
24
  oauthToken?: string;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "jeopi-catalog",
4
- "version": "16.5.6",
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.5.6",
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.5.6",
42
+ "jeopi-ai": "16.6.2",
43
43
  "@types/bun": "^1.3.14"
44
44
  },
45
45
  "engines": {
@@ -161,12 +161,16 @@ export interface FetchAntigravityDiscoveryModelsOptions {
161
161
  export async function fetchAntigravityDiscoveryModels(
162
162
  options: FetchAntigravityDiscoveryModelsOptions,
163
163
  ): Promise<ModelSpec<"google-gemini-cli">[] | null> {
164
+ if (!options.token.trim() || options.signal?.aborted) return null;
165
+ const timeout = AbortSignal.timeout(10_000);
166
+ const signal = options.signal ? AbortSignal.any([options.signal, timeout]) : timeout;
164
167
  const fetcher = discoveryFetch(options.fetcher);
165
168
  const endpoints = options.endpoint
166
169
  ? [trimTrailingSlashes(options.endpoint)]
167
170
  : DEFAULT_ANTIGRAVITY_DISCOVERY_ENDPOINTS.map(trimTrailingSlashes);
168
171
 
169
172
  for (const endpoint of endpoints) {
173
+ if (signal.aborted) return null;
170
174
  let response: Response;
171
175
  try {
172
176
  response = await fetcher(`${endpoint}${FETCH_AVAILABLE_MODELS_PATH}`, {
@@ -177,7 +181,7 @@ export async function fetchAntigravityDiscoveryModels(
177
181
  "User-Agent": options.userAgent ?? getAntigravityUserAgent(),
178
182
  },
179
183
  body: JSON.stringify({}),
180
- signal: options.signal,
184
+ signal,
181
185
  });
182
186
  } catch {
183
187
  continue;
@@ -194,6 +198,7 @@ export async function fetchAntigravityDiscoveryModels(
194
198
  continue;
195
199
  }
196
200
 
201
+ if (signal.aborted) return null;
197
202
  const parsed = parseAntigravityDiscoveryResponse(payload);
198
203
  if (!parsed) {
199
204
  continue;
@@ -241,6 +246,13 @@ export async function fetchAntigravityDiscoveryModels(
241
246
  }
242
247
 
243
248
  function parseAntigravityDiscoveryResponse(value: unknown): AntigravityDiscoveryApiResponse | null {
249
+ // An absent or corrupt map is a failed discovery, never an authoritative empty catalog.
250
+ if (typeof value !== "object" || value === null || !("models" in value)) return null;
251
+ const models = value.models;
252
+ if (typeof models !== "object" || models === null || Array.isArray(models)) return null;
253
+ for (const [id, model] of Object.entries(models)) {
254
+ if (!id.trim() || typeof model !== "object" || model === null || Array.isArray(model)) return null;
255
+ }
244
256
  const parsed = AntigravityDiscoveryApiResponseSchema(value);
245
257
  if (parsed instanceof type.errors) {
246
258
  return null;
@@ -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,
@@ -39,6 +39,8 @@ export interface ModelManagerOptions<TApi extends Api = Api, TModelsDevPayload =
39
39
  cacheTtlMs?: number;
40
40
  /** When true, a successful dynamic fetch is the complete provider catalog and prunes static-only models. */
41
41
  dynamicModelsAuthoritative?: boolean;
42
+ /** Keep a prior complete catalog (including empty) on failed/offline refresh, without extending its TTL. */
43
+ preserveAuthoritativeCacheOnFailure?: boolean;
42
44
  /** Cached model ids to ignore when the cache was written against a different static catalog fingerprint. */
43
45
  dropCachedModelIdsOnStaticMismatch?: readonly string[];
44
46
  /** Trusted provider-wide headers restored locally, never persisted. */
@@ -71,6 +73,8 @@ export interface ModelManagerOptions<TApi extends Api = Api, TModelsDevPayload =
71
73
  export interface ModelResolutionResult<TApi extends Api = Api> {
72
74
  models: Model<TApi>[];
73
75
  stale: boolean;
76
+ /** A stale result retained a complete provider snapshot; callers must not resurrect bundled-only ids. */
77
+ preservedAuthoritativeCache?: boolean;
74
78
  }
75
79
 
76
80
  /**
@@ -209,6 +213,20 @@ export async function resolveProviderModels<TApi extends Api = Api, TModelsDevPa
209
213
  const shouldUseFreshCacheAsAuthoritative =
210
214
  strategy === "online-if-uncached" && hasUsableFreshCache && hasAuthoritativeCache;
211
215
  const dynamicFetchSucceeded = fetchedDynamicModels !== null;
216
+ if (
217
+ !dynamicFetchSucceeded &&
218
+ dynamicModelsAuthoritative &&
219
+ options.preserveAuthoritativeCacheOnFailure &&
220
+ cache?.authoritative &&
221
+ cacheFingerprintMatches &&
222
+ !cacheHasUnresolvedHeaders
223
+ ) {
224
+ return {
225
+ models: collapseBuiltModelVariants(restoredCache.models),
226
+ stale: true,
227
+ preservedAuthoritativeCache: true,
228
+ };
229
+ }
212
230
  const cacheModels = dynamicFetchSucceeded
213
231
  ? []
214
232
  : prepareCacheModelsForStaticMismatch(
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" },
@@ -1,4 +1,4 @@
1
- import { fetchAntigravityDiscoveryModels } from "../discovery/antigravity";
1
+ import { ANTIGRAVITY_PRIMARY_ENDPOINT, fetchAntigravityDiscoveryModels } from "../discovery/antigravity";
2
2
  import { fetchGeminiModels } from "../discovery/gemini";
3
3
  import type { ModelManagerOptions } from "../model-manager";
4
4
  import type { FetchImpl } from "../types";
@@ -21,6 +21,10 @@ export interface GoogleAntigravityModelManagerConfig {
21
21
  oauthToken?: string;
22
22
  endpoint?: string;
23
23
  fetch?: FetchImpl;
24
+ /** Stable credential identity; falls back to the token when unavailable. */
25
+ cacheScope?: string;
26
+ /** Resolve a fresh token only when discovery actually needs the network. */
27
+ resolveOAuthToken?: (signal: AbortSignal) => Promise<string | undefined>;
24
28
  }
25
29
 
26
30
  export interface GoogleGeminiCliModelManagerConfig {
@@ -60,17 +64,26 @@ export function googleVertexModelManagerOptions(_config?: GoogleVertexModelManag
60
64
  export function googleAntigravityModelManagerOptions(
61
65
  config?: GoogleAntigravityModelManagerConfig,
62
66
  ): ModelManagerOptions<"google-gemini-cli"> {
63
- const token = config?.oauthToken;
67
+ const token = config?.oauthToken?.trim();
68
+ const endpoint = (config?.endpoint ?? ANTIGRAVITY_PRIMARY_ENDPOINT).replace(/\/+$/, "");
64
69
  return {
65
70
  providerId: "google-antigravity",
66
71
  ...(token
67
72
  ? {
68
- fetchDynamicModels: () =>
69
- fetchAntigravityDiscoveryModels({
70
- token,
73
+ cacheProviderId: `google-antigravity:discovery-v2:${Bun.hash(JSON.stringify([endpoint, config?.cacheScope ?? token])).toString(36)}`,
74
+ dynamicModelsAuthoritative: true,
75
+ preserveAuthoritativeCacheOnFailure: true,
76
+ fetchDynamicModels: async () => {
77
+ const signal = AbortSignal.timeout(10_000);
78
+ const resolvedToken = config?.resolveOAuthToken ? await config.resolveOAuthToken(signal) : token;
79
+ if (!resolvedToken?.trim() || signal.aborted) return null;
80
+ return fetchAntigravityDiscoveryModels({
81
+ token: resolvedToken,
71
82
  endpoint: config?.endpoint,
72
83
  fetcher: toDiscoveryFetch(config?.fetch),
73
- }),
84
+ signal,
85
+ });
86
+ },
74
87
  }
75
88
  : undefined),
76
89
  };
@@ -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 },