@dbx-tools/model 0.9.21 → 0.9.28

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.
Files changed (86) hide show
  1. package/README.md +61 -10
  2. package/index.ts +12 -2
  3. package/lib/exports.d.ts +1 -0
  4. package/lib/exports.js +2 -0
  5. package/lib/index.d.ts +12 -2
  6. package/lib/index.js +9 -2
  7. package/lib/src/_metadata-contract.d.ts +42 -0
  8. package/lib/src/_metadata-contract.js +11 -0
  9. package/lib/src/_metadata-generator.d.ts +7 -0
  10. package/lib/src/_metadata-generator.js +182 -0
  11. package/lib/src/_ranking.d.ts +20 -0
  12. package/lib/src/_ranking.js +226 -0
  13. package/lib/src/_selection.d.ts +23 -0
  14. package/lib/src/_selection.js +95 -0
  15. package/lib/src/classes.d.ts +2 -2
  16. package/lib/src/classes.js +10 -8
  17. package/lib/src/classify.d.ts +130 -0
  18. package/lib/src/classify.js +283 -0
  19. package/lib/src/fallback.d.ts +4 -4
  20. package/lib/src/fallback.js +7 -7
  21. package/lib/src/generated/model-capabilities.json +90 -0
  22. package/lib/src/generated/model-rate-limits.json +342 -0
  23. package/lib/src/generated/retired-models.json +32 -0
  24. package/lib/src/invoke.js +3 -3
  25. package/lib/src/metadata.d.ts +33 -0
  26. package/lib/src/metadata.js +120 -0
  27. package/lib/src/model-catalog.d.ts +4 -4
  28. package/lib/src/model-catalog.js +19 -24
  29. package/lib/src/model-client.d.ts +67 -0
  30. package/lib/src/model-client.js +181 -0
  31. package/lib/src/policy.d.ts +14 -8
  32. package/lib/src/policy.js +173 -14
  33. package/lib/src/python.d.ts +2 -0
  34. package/lib/src/python.js +3 -0
  35. package/lib/src/resolve.d.ts +10 -143
  36. package/lib/src/resolve.js +11 -196
  37. package/lib/tsconfig.tsbuildinfo +1 -1
  38. package/package.json +15 -5
  39. package/src/_metadata-contract.ts +56 -0
  40. package/src/_metadata-generator.ts +199 -0
  41. package/src/_ranking.ts +260 -0
  42. package/src/_selection.ts +141 -0
  43. package/src/classes.ts +11 -7
  44. package/src/classify.ts +324 -0
  45. package/src/fallback.ts +11 -7
  46. package/src/generated/model-capabilities.json +90 -0
  47. package/src/generated/model-rate-limits.json +342 -0
  48. package/src/generated/retired-models.json +32 -0
  49. package/src/invoke.ts +2 -2
  50. package/src/metadata.ts +189 -0
  51. package/src/model-catalog.ts +26 -28
  52. package/src/model-client.ts +311 -0
  53. package/src/policy.ts +176 -19
  54. package/src/python.ts +2 -0
  55. package/src/resolve.ts +24 -270
  56. package/._LICENSE +0 -0
  57. package/._README.md +0 -0
  58. package/._index.ts +0 -0
  59. package/._package.json +0 -0
  60. package/lib/._index.d.ts +0 -0
  61. package/lib/._index.js +0 -0
  62. package/lib/._tsconfig.tsbuildinfo +0 -0
  63. package/lib/src/.__native.d.ts +0 -0
  64. package/lib/src/.__native.js +0 -0
  65. package/lib/src/._classes.d.ts +0 -0
  66. package/lib/src/._classes.js +0 -0
  67. package/lib/src/._fallback.d.ts +0 -0
  68. package/lib/src/._fallback.js +0 -0
  69. package/lib/src/._invoke.d.ts +0 -0
  70. package/lib/src/._invoke.js +0 -0
  71. package/lib/src/._model-catalog.d.ts +0 -0
  72. package/lib/src/._model-catalog.js +0 -0
  73. package/lib/src/._policy.d.ts +0 -0
  74. package/lib/src/._policy.js +0 -0
  75. package/lib/src/._resolve.d.ts +0 -0
  76. package/lib/src/._resolve.js +0 -0
  77. package/lib/src/_native.d.ts +0 -14
  78. package/lib/src/_native.js +0 -124
  79. package/src/.__native.ts +0 -0
  80. package/src/._classes.ts +0 -0
  81. package/src/._fallback.ts +0 -0
  82. package/src/._invoke.ts +0 -0
  83. package/src/._model-catalog.ts +0 -0
  84. package/src/._policy.ts +0 -0
  85. package/src/._resolve.ts +0 -0
  86. package/src/_native.ts +0 -174
@@ -0,0 +1,324 @@
1
+ /**
2
+ * Model-class classification for Databricks Model Serving endpoints.
3
+ *
4
+ * Chat capability bands are derived from the live workspace catalogue
5
+ * rather than a hand-maintained table. Databricks publishes
6
+ * per-endpoint `quality` / `speed` / `cost` scores (the AI Playground
7
+ * bars) on the serving list; {@link classifyEndpoints} buckets scored
8
+ * chat models into the chat {@link ModelClass} bands by the *relative*
9
+ * distribution of those scores (quantiles, not fixed cut-offs) so a
10
+ * brand-new model that lands outside today's score range still slots in
11
+ * next to its peers. Embedding endpoints (`task === "llm/v1/embeddings"`)
12
+ * are bucketed into {@link ModelClass.Embedding} by task, independent of
13
+ * any score.
14
+ *
15
+ * Unscored-but-recognizable chat endpoints are still placed by a small
16
+ * family heuristic ({@link classifyByFamily}) so a workspace whose
17
+ * models predate Foundation Model API scoring keeps working. The
18
+ * offline fallback floor - the hard-coded model list reached for when
19
+ * the live catalogue can't be read at all - is a server concern and
20
+ * lives in `@dbx-tools/model`, not here: a browser client never talks
21
+ * to Databricks directly, so it has nothing to fall back to.
22
+ *
23
+ * Pure (no Node-only imports), so a client can classify a `/models`
24
+ * response without server dependencies.
25
+ *
26
+ * @module
27
+ */
28
+
29
+ import {
30
+ ModelClass as ModelClassValues,
31
+ type ModelClass as ModelClassType,
32
+ type ServingEndpointSummary,
33
+ } from "@dbx-tools/shared-model/contracts";
34
+
35
+ type ModelClass = ModelClassType;
36
+ const ModelClass = ModelClassValues;
37
+
38
+ /** Task hint Databricks stamps on chat completion endpoints. */
39
+ const CHAT_TASK = "llm/v1/chat";
40
+
41
+ /** Task hint Databricks stamps on embedding endpoints. */
42
+ const EMBEDDING_TASK = "llm/v1/embeddings";
43
+
44
+ /** What an endpoint can be asked to do, as derived by {@link endpointCapabilities}. */
45
+ export interface EndpointCapabilities {
46
+ /** OpenAI chat/completions + Responses: the surface a chat agent needs. */
47
+ chat: boolean;
48
+ /** Embedding (vector) endpoint. */
49
+ embedding: boolean;
50
+ /** Complete function-tool round-trip (call plus function_call_output replay). */
51
+ tools: boolean;
52
+ }
53
+
54
+ /**
55
+ * Provider families verified against Databricks Responses/Open Responses with
56
+ * both a forced function call and a stateless `function_call_output` replay.
57
+ *
58
+ * Databricks' endpoint list/OpenAPI currently exposes no tools capability bit.
59
+ * Keep this conservative: an unknown chat family is not agent-safe until it is
60
+ * verified. Gemini is excluded because it emits a call but Open Responses
61
+ * cannot replay the required thought signature; GPT-OSS rejects Responses
62
+ * passthrough entirely.
63
+ */
64
+ export function supportsToolsByFamily(name: string): boolean {
65
+ const n = name.toLowerCase();
66
+ if (n.includes("gemini") || n.includes("gpt-oss")) return false;
67
+ return (
68
+ n.includes("claude") ||
69
+ n.includes("gpt") ||
70
+ n.includes("qwen") ||
71
+ n.includes("glm") ||
72
+ n.includes("llama")
73
+ );
74
+ }
75
+
76
+ /**
77
+ * Derive an endpoint's capabilities from its Databricks task hint and its
78
+ * classified {@link ModelClass}, so consumers filter on capability instead of
79
+ * re-deriving it from raw `task` / `class` strings.
80
+ *
81
+ * Either signal alone is enough: the task hint is authoritative when present,
82
+ * and the class covers endpoints Databricks left untasked but the classifier
83
+ * recognized. Embedding wins over chat when both point at it, since the two are
84
+ * not interchangeable.
85
+ */
86
+ export function endpointCapabilities(endpoint: ServingEndpointSummary): EndpointCapabilities {
87
+ const embedding = endpoint.task === EMBEDDING_TASK || endpoint.class === ModelClass.Embedding;
88
+ // Every non-embedding class is a chat band, so "classified but not embedding"
89
+ // is the class-side test for chat capability.
90
+ const chat = !embedding && (endpoint.task === CHAT_TASK || endpoint.class !== undefined);
91
+ const tools = chat && (endpoint.supportsTools ?? supportsToolsByFamily(endpoint.name));
92
+ return { chat, embedding, tools };
93
+ }
94
+
95
+ /** Family-heuristic classification of a single endpoint name. */
96
+ export interface FamilyClass {
97
+ /** Chat capability band the family maps to (never embedding). */
98
+ class: ModelClass;
99
+ /** Intra-family ordering hint (higher is newer / more capable). */
100
+ rank: number;
101
+ }
102
+
103
+ /**
104
+ * Numeric `[major, minor, patch]` version parsed from an endpoint
105
+ * name, used to order siblings within a family/tier. Starts at the
106
+ * first digit in the name, then reads successive separator-delimited,
107
+ * digit-prefixed chunks as the three components (missing ones default
108
+ * to `0`):
109
+ *
110
+ * - `databricks-claude-opus-4-8` -> `[4, 8, 0]`
111
+ * - `databricks-claude-opus-4-10` -> `[4, 10, 0]` (sorts above 4-8)
112
+ * - `databricks-meta-llama-3-3-70b`-> `[3, 3, 70]`
113
+ * - `databricks-bge-large-en` -> `[0, 0, 0]` (no digits)
114
+ *
115
+ * Component-wise comparison (not a decimal collapse) so `4.10` beats
116
+ * `4.8` - the bug a `major + minor/10` score would hit.
117
+ */
118
+ export function versionTuple(name: string): [number, number, number] {
119
+ const start = name.search(/\d/);
120
+ if (start < 0) return [0, 0, 0];
121
+ const nums = name
122
+ .slice(start)
123
+ .split(/[^a-z0-9]+/i)
124
+ .map((chunk) => chunk.match(/^\d+/)?.[0])
125
+ .filter((digits): digits is string => digits !== undefined)
126
+ .map(Number);
127
+ return [nums[0] ?? 0, nums[1] ?? 0, nums[2] ?? 0];
128
+ }
129
+
130
+ /** Compare two version tuples so the higher version sorts first (descending). */
131
+ function compareVersionDesc(a: readonly number[], b: readonly number[]): number {
132
+ for (let i = 0; i < 3; i++) {
133
+ const diff = (b[i] ?? 0) - (a[i] ?? 0);
134
+ if (diff !== 0) return diff;
135
+ }
136
+ return 0;
137
+ }
138
+
139
+ /**
140
+ * Monotonic version key for ordering siblings within a family/tier
141
+ * (e.g. `opus-4-8` over `opus-4-7`). Encodes the {@link versionTuple}
142
+ * as a single number so callers that need a scalar rank (the family
143
+ * heuristic, the static fallback ordering) keep working; the encoding
144
+ * preserves component order without the decimal-collapse bug.
145
+ */
146
+ function versionScore(name: string): number {
147
+ const [major, minor, patch] = versionTuple(name);
148
+ return major * 1_000_000 + minor * 1_000 + patch;
149
+ }
150
+
151
+ /**
152
+ * Best-effort chat capability band for an endpoint we have no live
153
+ * score for, keyed off provider family and the well-known variant words
154
+ * in the name (`opus`/`sonnet`/`haiku`, `pro`/`mini`/`nano`,
155
+ * `flash`/`flash-lite`, Llama parameter sizes, etc). Returns `null` for
156
+ * names we don't recognize so unknown custom endpoints are never
157
+ * auto-selected as a default. The accompanying `rank` orders siblings
158
+ * within a class. Only ever returns a chat band - embedding endpoints
159
+ * are classified by task, not name.
160
+ */
161
+ export function classifyByFamily(name: string): FamilyClass | null {
162
+ const n = name.toLowerCase();
163
+ const at = (cls: ModelClass): FamilyClass => ({ class: cls, rank: versionScore(n) });
164
+
165
+ // Anthropic Claude
166
+ if (n.includes("opus")) return at(ModelClass.ChatThinking);
167
+ if (n.includes("sonnet")) return at(ModelClass.ChatBalanced);
168
+ if (n.includes("haiku")) return at(ModelClass.ChatFast);
169
+
170
+ // OpenAI open-weights (check before the generic gpt branch)
171
+ if (n.includes("gpt-oss")) {
172
+ return at(n.includes("120b") ? ModelClass.ChatBalanced : ModelClass.ChatFast);
173
+ }
174
+ // OpenAI GPT family
175
+ if (n.includes("gpt")) {
176
+ if (n.includes("pro")) return at(ModelClass.ChatThinking);
177
+ if (n.includes("mini") || n.includes("nano")) return at(ModelClass.ChatFast);
178
+ return at(ModelClass.ChatBalanced);
179
+ }
180
+
181
+ // Google Gemini / Gemma
182
+ if (n.includes("gemini")) {
183
+ if (n.includes("flash-lite")) return at(ModelClass.ChatFast);
184
+ if (n.includes("pro")) return at(ModelClass.ChatThinking);
185
+ return at(ModelClass.ChatBalanced);
186
+ }
187
+ if (n.includes("gemma")) return at(ModelClass.ChatFast);
188
+
189
+ // Meta Llama
190
+ if (n.includes("llama")) {
191
+ if (n.includes("maverick") || n.includes("405b")) return at(ModelClass.ChatThinking);
192
+ if (n.includes("70b")) return at(ModelClass.ChatBalanced);
193
+ if (n.includes("8b") || n.includes("1b")) return at(ModelClass.ChatFast);
194
+ return at(ModelClass.ChatBalanced);
195
+ }
196
+
197
+ // Alibaba Qwen
198
+ if (n.includes("qwen")) return at(ModelClass.ChatBalanced);
199
+
200
+ return null;
201
+ }
202
+
203
+ /** Linear-interpolated quantile of an ascending-sorted numeric array. */
204
+ function quantile(sortedAsc: readonly number[], p: number): number {
205
+ if (sortedAsc.length === 0) return Number.NaN;
206
+ const idx = (sortedAsc.length - 1) * p;
207
+ const lo = Math.floor(idx);
208
+ const hi = Math.ceil(idx);
209
+ if (lo === hi) return sortedAsc[lo]!;
210
+ return sortedAsc[lo]! + (sortedAsc[hi]! - sortedAsc[lo]!) * (idx - lo);
211
+ }
212
+
213
+ /** Internal sortable wrapper carrying the ordering keys for a bucket entry. */
214
+ interface Ranked {
215
+ ep: ServingEndpointSummary;
216
+ /** Quality (scored) or family version (unscored) - higher first. */
217
+ sort: number;
218
+ /** Scored endpoints rank ahead of family-only guesses. */
219
+ scored: boolean;
220
+ tieCost: number;
221
+ tieSpeed: number;
222
+ /** Parsed name version, the final tie-breaker (newer sibling first). */
223
+ version: readonly number[];
224
+ }
225
+
226
+ function rankOrder(a: Ranked, b: Ranked): number {
227
+ if (a.scored !== b.scored) return a.scored ? -1 : 1;
228
+ if (b.sort !== a.sort) return b.sort - a.sort;
229
+ if (a.tieCost !== b.tieCost) return a.tieCost - b.tieCost;
230
+ if (b.tieSpeed !== a.tieSpeed) return b.tieSpeed - a.tieSpeed;
231
+ // Same scored-ness, quality, cost, and speed (e.g. several Claude
232
+ // Opus point releases): prefer the newer parsed version so
233
+ // `opus-4-8` beats `opus-4-7`.
234
+ return compareVersionDesc(a.version, b.version);
235
+ }
236
+
237
+ /**
238
+ * Bucket live endpoints into {@link ModelClass}es, ranked best-first
239
+ * within each chat band.
240
+ *
241
+ * Embedding endpoints (`task === "llm/v1/embeddings"`) go into
242
+ * {@link ModelClass.Embedding} by task, in listing order (they carry no
243
+ * capability score to rank on).
244
+ *
245
+ * Chat endpoints (`task === "llm/v1/chat"`) split into the three chat
246
+ * bands. Scored endpoints (those carrying a `profile.quality`) drive
247
+ * the banding: the observed quality distribution is split at its 1/3
248
+ * and 2/3 quantiles, so the top third is {@link ModelClass.ChatThinking},
249
+ * the bottom third {@link ModelClass.ChatFast}, and the middle
250
+ * {@link ModelClass.ChatBalanced}. Because the thresholds come from the
251
+ * data, the split adapts as Databricks adds or rescores models -
252
+ * nothing is pinned to a fixed score band.
253
+ *
254
+ * Unscored chat endpoints are placed by {@link classifyByFamily} and
255
+ * ranked after the scored ones in their band; unrecognized, unscored
256
+ * endpoints (e.g. custom external models) are omitted entirely so they
257
+ * are never picked as an automatic default.
258
+ *
259
+ * Within a chat band, scored models sort by `quality` desc, then `cost`
260
+ * asc, then `speed` desc, then parsed name version desc; family-only
261
+ * models sort by version rank then parsed version. The version
262
+ * tie-break ({@link versionTuple}) is what separates point releases
263
+ * that share a score profile (e.g. `opus-4-8` ahead of `opus-4-7`).
264
+ */
265
+ export function classifyEndpoints(
266
+ endpoints: readonly ServingEndpointSummary[],
267
+ ): Record<ModelClass, ServingEndpointSummary[]> {
268
+ const chat = endpoints.filter((e) => e.task === CHAT_TASK);
269
+ const qualities = chat
270
+ .map((e) => e.profile?.quality)
271
+ .filter((q): q is number => Number.isFinite(q))
272
+ .sort((a, b) => a - b);
273
+ const low = quantile(qualities, 1 / 3);
274
+ const high = quantile(qualities, 2 / 3);
275
+
276
+ const buckets: Record<ModelClass, Ranked[]> = {
277
+ [ModelClass.ChatThinking]: [],
278
+ [ModelClass.ChatBalanced]: [],
279
+ [ModelClass.ChatFast]: [],
280
+ [ModelClass.Embedding]: [],
281
+ };
282
+
283
+ for (const ep of chat) {
284
+ const q = ep.profile?.quality;
285
+ if (Number.isFinite(q)) {
286
+ const quality = q as number;
287
+ const cls =
288
+ quality >= high
289
+ ? ModelClass.ChatThinking
290
+ : quality <= low
291
+ ? ModelClass.ChatFast
292
+ : ModelClass.ChatBalanced;
293
+ buckets[cls].push({
294
+ ep,
295
+ sort: quality,
296
+ scored: true,
297
+ tieCost: ep.profile?.cost ?? Number.POSITIVE_INFINITY,
298
+ tieSpeed: ep.profile?.speed ?? 0,
299
+ version: versionTuple(ep.name),
300
+ });
301
+ continue;
302
+ }
303
+ const family = classifyByFamily(ep.name);
304
+ if (!family) continue;
305
+ buckets[family.class].push({
306
+ ep,
307
+ sort: family.rank,
308
+ scored: false,
309
+ tieCost: Number.POSITIVE_INFINITY,
310
+ tieSpeed: 0,
311
+ version: versionTuple(ep.name),
312
+ });
313
+ }
314
+
315
+ // Embeddings are bucketed by task in listing order - no score to rank.
316
+ const embeddings = endpoints.filter((e) => e.task === EMBEDDING_TASK);
317
+
318
+ return {
319
+ [ModelClass.ChatThinking]: buckets[ModelClass.ChatThinking].sort(rankOrder).map((x) => x.ep),
320
+ [ModelClass.ChatBalanced]: buckets[ModelClass.ChatBalanced].sort(rankOrder).map((x) => x.ep),
321
+ [ModelClass.ChatFast]: buckets[ModelClass.ChatFast].sort(rankOrder).map((x) => x.ep),
322
+ [ModelClass.Embedding]: embeddings,
323
+ };
324
+ }
package/src/fallback.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  * token, the service principal can't list, or the workspace is unreachable -
6
6
  * the resolver still has to name *some* endpoint. This module holds that floor:
7
7
  * a small, hard-coded set of well-known Foundation Model API endpoint names,
8
- * each bucketed into a chat {@link ModelClass} by the Rust family policy
8
+ * each bucketed into a chat {@link ModelClass} by the TypeScript family policy
9
9
  * and ordered best-first.
10
10
  *
11
11
  * This is deliberately *server-only*. A browser client never talks to
@@ -16,16 +16,20 @@
16
16
  * @module
17
17
  */
18
18
 
19
- import { model, type ServingEndpointSummary } from "@dbx-tools/shared-model";
19
+ import {
20
+ ModelClass as ModelClassValues,
21
+ type ModelClass as ModelClassType,
22
+ type ServingEndpointSummary,
23
+ } from "@dbx-tools/shared-model/contracts";
20
24
 
21
- import { rankEndpoints } from "./_native.ts";
25
+ import { rankEndpoints } from "./_ranking.ts";
22
26
 
23
- type ModelClass = model.ModelClass;
24
- const { ModelClass } = model;
27
+ type ModelClass = ModelClassType;
28
+ const ModelClass = ModelClassValues;
25
29
 
26
30
  /**
27
31
  * Small, last-resort set of well-known Foundation Model API endpoint names,
28
- * ordered best-first within each class by the Rust model ranker.
32
+ * ordered best-first within each class by the model ranker.
29
33
  * Used only as the floor when the live `/serving-endpoints` catalogue can't be
30
34
  * read at resolve time; the live, score-driven classification supersedes it
31
35
  * whenever the workspace listing is available. Classes are not hard-coded here
@@ -53,7 +57,7 @@ const FALLBACK_ENDPOINTS: readonly ServingEndpointSummary[] = FALLBACK_MODEL_NAM
53
57
  * {@link FALLBACK_MODEL_NAMES} list and ordered best-first by family rank. Sync
54
58
  * and workspace-independent: this is the *fallback opinion* used to seed default
55
59
  * lists or when the live catalogue is unreachable - live resolution prefers
56
- * the Rust-classified workspace catalogue. Returns `[]` for
60
+ * the classified workspace catalogue. Returns `[]` for
57
61
  * {@link ModelClass.Embedding} (the floor is chat-only).
58
62
  */
59
63
  export function modelsForClass(cls: ModelClass): readonly string[] {
@@ -0,0 +1,90 @@
1
+ {
2
+ "generatedAt": 1791150180,
3
+ "capabilities": {
4
+ "responses": [
5
+ "gpt-5",
6
+ "gpt-5-1",
7
+ "gpt-5-2",
8
+ "gpt-5-3-codex",
9
+ "gpt-5-4",
10
+ "gpt-5-4-mini",
11
+ "gpt-5-4-nano",
12
+ "gpt-5-5",
13
+ "gpt-5-5-pro",
14
+ "gpt-5-6-luna",
15
+ "gpt-5-6-sol",
16
+ "gpt-5-6-terra",
17
+ "gpt-5-mini",
18
+ "gpt-5-nano",
19
+ "gpt-6-1-sol",
20
+ "gpt-6-astra",
21
+ "gpt-6-luna",
22
+ "gpt-6-sol"
23
+ ],
24
+ "imageInput": [
25
+ "gpt-5",
26
+ "gpt-5-1",
27
+ "gpt-5-2",
28
+ "gpt-5-3-codex",
29
+ "gpt-5-4",
30
+ "gpt-5-4-mini",
31
+ "gpt-5-4-nano",
32
+ "gpt-5-5",
33
+ "gpt-5-5-pro",
34
+ "gpt-5-6-luna",
35
+ "gpt-5-6-sol",
36
+ "gpt-5-6-terra",
37
+ "gpt-5-mini",
38
+ "gpt-5-nano",
39
+ "gpt-6-1-sol",
40
+ "gpt-6-astra",
41
+ "gpt-6-luna",
42
+ "gpt-6-sol"
43
+ ],
44
+ "applyPatch": [
45
+ "gpt-5",
46
+ "gpt-5-1",
47
+ "gpt-5-2",
48
+ "gpt-5-3-codex",
49
+ "gpt-5-4",
50
+ "gpt-5-4-mini",
51
+ "gpt-5-4-nano",
52
+ "gpt-5-5",
53
+ "gpt-5-5-pro",
54
+ "gpt-5-6-luna",
55
+ "gpt-5-6-sol",
56
+ "gpt-5-6-terra",
57
+ "gpt-5-mini",
58
+ "gpt-5-nano",
59
+ "gpt-6-1-sol",
60
+ "gpt-6-astra",
61
+ "gpt-6-luna",
62
+ "gpt-6-sol"
63
+ ],
64
+ "webSearch": [
65
+ "gemini-2-5-flash",
66
+ "gemini-2-5-pro",
67
+ "gemini-3-1-flash-lite",
68
+ "gemini-3-1-pro",
69
+ "gemini-3-flash",
70
+ "gpt-5",
71
+ "gpt-5-1",
72
+ "gpt-5-2",
73
+ "gpt-5-3-codex",
74
+ "gpt-5-4",
75
+ "gpt-5-4-mini",
76
+ "gpt-5-4-nano",
77
+ "gpt-5-5",
78
+ "gpt-5-5-pro",
79
+ "gpt-5-6-luna",
80
+ "gpt-5-6-sol",
81
+ "gpt-5-6-terra",
82
+ "gpt-5-mini",
83
+ "gpt-5-nano",
84
+ "gpt-6-1-sol",
85
+ "gpt-6-astra",
86
+ "gpt-6-luna",
87
+ "gpt-6-sol"
88
+ ]
89
+ }
90
+ }