@dbx-tools/model 0.9.19 → 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 (58) 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/lib/src/_native.d.ts +0 -14
  57. package/lib/src/_native.js +0 -124
  58. package/src/_native.ts +0 -174
@@ -0,0 +1,342 @@
1
+ {
2
+ "generatedAt": 1791144427,
3
+ "catalogue": {
4
+ "models": {
5
+ "bge-large-en": {
6
+ "inputTokensPerMinute": null,
7
+ "outputTokensPerMinute": null,
8
+ "queriesPerHour": 2160000
9
+ },
10
+ "claude-4-1-opus": {
11
+ "inputTokensPerMinute": 200000,
12
+ "outputTokensPerMinute": 20000,
13
+ "queriesPerHour": 360000
14
+ },
15
+ "claude-4-5-haiku": {
16
+ "inputTokensPerMinute": 200000,
17
+ "outputTokensPerMinute": 20000,
18
+ "queriesPerHour": 360000
19
+ },
20
+ "claude-4-5-opus": {
21
+ "inputTokensPerMinute": 200000,
22
+ "outputTokensPerMinute": 20000,
23
+ "queriesPerHour": 360000
24
+ },
25
+ "claude-4-5-sonnet": {
26
+ "inputTokensPerMinute": 200000,
27
+ "outputTokensPerMinute": 20000,
28
+ "queriesPerHour": 360000
29
+ },
30
+ "claude-4-6-opus": {
31
+ "inputTokensPerMinute": 200000,
32
+ "outputTokensPerMinute": 20000,
33
+ "queriesPerHour": 360000
34
+ },
35
+ "claude-4-6-sonnet": {
36
+ "inputTokensPerMinute": 200000,
37
+ "outputTokensPerMinute": 20000,
38
+ "queriesPerHour": 360000
39
+ },
40
+ "claude-4-7-opus": {
41
+ "inputTokensPerMinute": 200000,
42
+ "outputTokensPerMinute": 20000,
43
+ "queriesPerHour": 360000
44
+ },
45
+ "claude-4-8-opus": {
46
+ "inputTokensPerMinute": 200000,
47
+ "outputTokensPerMinute": 20000,
48
+ "queriesPerHour": 360000
49
+ },
50
+ "claude-4-sonnet": {
51
+ "inputTokensPerMinute": 200000,
52
+ "outputTokensPerMinute": 20000,
53
+ "queriesPerHour": 360000
54
+ },
55
+ "claude-5-1-fable": {
56
+ "inputTokensPerMinute": 1000000,
57
+ "outputTokensPerMinute": 100000,
58
+ "queriesPerHour": 360000
59
+ },
60
+ "claude-5-5-opus": {
61
+ "inputTokensPerMinute": 1000000,
62
+ "outputTokensPerMinute": 100000,
63
+ "queriesPerHour": 360000
64
+ },
65
+ "claude-5-5-sonnet": {
66
+ "inputTokensPerMinute": 1000000,
67
+ "outputTokensPerMinute": 100000,
68
+ "queriesPerHour": 360000
69
+ },
70
+ "claude-5-fable": {
71
+ "inputTokensPerMinute": 1000000,
72
+ "outputTokensPerMinute": 100000,
73
+ "queriesPerHour": 360000
74
+ },
75
+ "claude-5-opus": {
76
+ "inputTokensPerMinute": 1000000,
77
+ "outputTokensPerMinute": 100000,
78
+ "queriesPerHour": 360000
79
+ },
80
+ "claude-5-sonnet": {
81
+ "inputTokensPerMinute": 1000000,
82
+ "outputTokensPerMinute": 100000,
83
+ "queriesPerHour": 360000
84
+ },
85
+ "deepseek-4-1-flash": {
86
+ "inputTokensPerMinute": 2000000,
87
+ "outputTokensPerMinute": 40000,
88
+ "queriesPerHour": 7200
89
+ },
90
+ "deepseek-4-flash-0731": {
91
+ "inputTokensPerMinute": 200000,
92
+ "outputTokensPerMinute": 10000,
93
+ "queriesPerHour": null
94
+ },
95
+ "deepseek-4-pro-0813": {
96
+ "inputTokensPerMinute": 200000,
97
+ "outputTokensPerMinute": 4000,
98
+ "queriesPerHour": 7200
99
+ },
100
+ "gemini-2-5-flash": {
101
+ "inputTokensPerMinute": 200000,
102
+ "outputTokensPerMinute": 20000,
103
+ "queriesPerHour": 360000
104
+ },
105
+ "gemini-2-5-pro": {
106
+ "inputTokensPerMinute": 200000,
107
+ "outputTokensPerMinute": 20000,
108
+ "queriesPerHour": 360000
109
+ },
110
+ "gemini-3-1-flash-image": {
111
+ "inputTokensPerMinute": 200000,
112
+ "outputTokensPerMinute": 20000,
113
+ "queriesPerHour": 360000
114
+ },
115
+ "gemini-3-1-flash-lite": {
116
+ "inputTokensPerMinute": 200000,
117
+ "outputTokensPerMinute": 20000,
118
+ "queriesPerHour": 360000
119
+ },
120
+ "gemini-3-1-pro-preview": {
121
+ "inputTokensPerMinute": 200000,
122
+ "outputTokensPerMinute": 20000,
123
+ "queriesPerHour": 360000
124
+ },
125
+ "gemini-3-5-flash": {
126
+ "inputTokensPerMinute": 1000000,
127
+ "outputTokensPerMinute": 100000,
128
+ "queriesPerHour": 360000
129
+ },
130
+ "gemini-3-5-flash-lite": {
131
+ "inputTokensPerMinute": 1000000,
132
+ "outputTokensPerMinute": 100000,
133
+ "queriesPerHour": 360000
134
+ },
135
+ "gemini-3-6-flash": {
136
+ "inputTokensPerMinute": 1000000,
137
+ "outputTokensPerMinute": 100000,
138
+ "queriesPerHour": 360000
139
+ },
140
+ "gemini-3-7-flash": {
141
+ "inputTokensPerMinute": 1000000,
142
+ "outputTokensPerMinute": 100000,
143
+ "queriesPerHour": 360000
144
+ },
145
+ "gemini-3-8-flash": {
146
+ "inputTokensPerMinute": 1000000,
147
+ "outputTokensPerMinute": 100000,
148
+ "queriesPerHour": 360000
149
+ },
150
+ "gemini-3-flash": {
151
+ "inputTokensPerMinute": 200000,
152
+ "outputTokensPerMinute": 20000,
153
+ "queriesPerHour": 360000
154
+ },
155
+ "gemini-3-pro-image": {
156
+ "inputTokensPerMinute": 200000,
157
+ "outputTokensPerMinute": 20000,
158
+ "queriesPerHour": 360000
159
+ },
160
+ "gemma-3-12b": {
161
+ "inputTokensPerMinute": 1000000,
162
+ "outputTokensPerMinute": 100000,
163
+ "queriesPerHour": 360000
164
+ },
165
+ "glm-5-2": {
166
+ "inputTokensPerMinute": 200000,
167
+ "outputTokensPerMinute": 20000,
168
+ "queriesPerHour": 7200
169
+ },
170
+ "glm-5-3": {
171
+ "inputTokensPerMinute": 2000000,
172
+ "outputTokensPerMinute": 40000,
173
+ "queriesPerHour": 7200
174
+ },
175
+ "glm-5-3-flash": {
176
+ "inputTokensPerMinute": 2000000,
177
+ "outputTokensPerMinute": 40000,
178
+ "queriesPerHour": 7200
179
+ },
180
+ "gpt-5": {
181
+ "inputTokensPerMinute": 200000,
182
+ "outputTokensPerMinute": 20000,
183
+ "queriesPerHour": 360000
184
+ },
185
+ "gpt-5-1": {
186
+ "inputTokensPerMinute": 200000,
187
+ "outputTokensPerMinute": 20000,
188
+ "queriesPerHour": 360000
189
+ },
190
+ "gpt-5-2": {
191
+ "inputTokensPerMinute": 200000,
192
+ "outputTokensPerMinute": 20000,
193
+ "queriesPerHour": 360000
194
+ },
195
+ "gpt-5-3-codex": {
196
+ "inputTokensPerMinute": 200000,
197
+ "outputTokensPerMinute": 20000,
198
+ "queriesPerHour": 360000
199
+ },
200
+ "gpt-5-4": {
201
+ "inputTokensPerMinute": 200000,
202
+ "outputTokensPerMinute": 20000,
203
+ "queriesPerHour": 360000
204
+ },
205
+ "gpt-5-4-mini": {
206
+ "inputTokensPerMinute": 200000,
207
+ "outputTokensPerMinute": 20000,
208
+ "queriesPerHour": 360000
209
+ },
210
+ "gpt-5-4-nano": {
211
+ "inputTokensPerMinute": 200000,
212
+ "outputTokensPerMinute": 20000,
213
+ "queriesPerHour": 360000
214
+ },
215
+ "gpt-5-5": {
216
+ "inputTokensPerMinute": 200000,
217
+ "outputTokensPerMinute": 20000,
218
+ "queriesPerHour": 360000
219
+ },
220
+ "gpt-5-5-pro": {
221
+ "inputTokensPerMinute": 200000,
222
+ "outputTokensPerMinute": 20000,
223
+ "queriesPerHour": 360000
224
+ },
225
+ "gpt-5-6-luna": {
226
+ "inputTokensPerMinute": 200000,
227
+ "outputTokensPerMinute": 20000,
228
+ "queriesPerHour": 360000
229
+ },
230
+ "gpt-5-6-sol": {
231
+ "inputTokensPerMinute": 200000,
232
+ "outputTokensPerMinute": 20000,
233
+ "queriesPerHour": 360000
234
+ },
235
+ "gpt-5-6-terra": {
236
+ "inputTokensPerMinute": 200000,
237
+ "outputTokensPerMinute": 20000,
238
+ "queriesPerHour": 360000
239
+ },
240
+ "gpt-5-mini": {
241
+ "inputTokensPerMinute": 200000,
242
+ "outputTokensPerMinute": 20000,
243
+ "queriesPerHour": 360000
244
+ },
245
+ "gpt-5-nano": {
246
+ "inputTokensPerMinute": 200000,
247
+ "outputTokensPerMinute": 20000,
248
+ "queriesPerHour": 360000
249
+ },
250
+ "gpt-6-1-sol": {
251
+ "inputTokensPerMinute": 1000000,
252
+ "outputTokensPerMinute": 100000,
253
+ "queriesPerHour": 360000
254
+ },
255
+ "gpt-6-astra": {
256
+ "inputTokensPerMinute": 1000000,
257
+ "outputTokensPerMinute": 100000,
258
+ "queriesPerHour": 360000
259
+ },
260
+ "gpt-6-luna": {
261
+ "inputTokensPerMinute": 1000000,
262
+ "outputTokensPerMinute": 100000,
263
+ "queriesPerHour": 360000
264
+ },
265
+ "gpt-6-sol": {
266
+ "inputTokensPerMinute": 1000000,
267
+ "outputTokensPerMinute": 100000,
268
+ "queriesPerHour": 360000
269
+ },
270
+ "gpt-oss-120b": {
271
+ "inputTokensPerMinute": 1000000,
272
+ "outputTokensPerMinute": 100000,
273
+ "queriesPerHour": 360000
274
+ },
275
+ "gpt-oss-20b": {
276
+ "inputTokensPerMinute": 1000000,
277
+ "outputTokensPerMinute": 100000,
278
+ "queriesPerHour": 360000
279
+ },
280
+ "grok-4-6": {
281
+ "inputTokensPerMinute": 1000000,
282
+ "outputTokensPerMinute": 100000,
283
+ "queriesPerHour": 360000
284
+ },
285
+ "grok-4-7": {
286
+ "inputTokensPerMinute": 1000000,
287
+ "outputTokensPerMinute": 100000,
288
+ "queriesPerHour": 360000
289
+ },
290
+ "gte-large-en": {
291
+ "inputTokensPerMinute": null,
292
+ "outputTokensPerMinute": null,
293
+ "queriesPerHour": 540000
294
+ },
295
+ "inkling": {
296
+ "inputTokensPerMinute": 200000,
297
+ "outputTokensPerMinute": 10000,
298
+ "queriesPerHour": 7200
299
+ },
300
+ "kimi-3": {
301
+ "inputTokensPerMinute": 2000000,
302
+ "outputTokensPerMinute": 40000,
303
+ "queriesPerHour": null
304
+ },
305
+ "llama-3-1-8b-instruct": {
306
+ "inputTokensPerMinute": 1000000,
307
+ "outputTokensPerMinute": 100000,
308
+ "queriesPerHour": 360000
309
+ },
310
+ "llama-3-3-70b-instruct": {
311
+ "inputTokensPerMinute": 1000000,
312
+ "outputTokensPerMinute": 100000,
313
+ "queriesPerHour": 360000
314
+ },
315
+ "llama-4-maverick": {
316
+ "inputTokensPerMinute": 1000000,
317
+ "outputTokensPerMinute": 100000,
318
+ "queriesPerHour": 360000
319
+ },
320
+ "qwen-3-5-122b-a10b": {
321
+ "inputTokensPerMinute": 1000000,
322
+ "outputTokensPerMinute": 100000,
323
+ "queriesPerHour": 360000
324
+ },
325
+ "qwen-3-5-4b": {
326
+ "inputTokensPerMinute": 2000000,
327
+ "outputTokensPerMinute": 40000,
328
+ "queriesPerHour": 7200
329
+ },
330
+ "qwen-3-embedding-0-6b": {
331
+ "inputTokensPerMinute": null,
332
+ "outputTokensPerMinute": null,
333
+ "queriesPerHour": 2160000
334
+ },
335
+ "qwen-3-next-80b-a3b-instruct": {
336
+ "inputTokensPerMinute": 1000000,
337
+ "outputTokensPerMinute": 100000,
338
+ "queriesPerHour": 360000
339
+ }
340
+ }
341
+ }
342
+ }
@@ -0,0 +1,32 @@
1
+ {
2
+ "generatedAt": 1791144427,
3
+ "models": [
4
+ "Anthropic Claude 3.7 Sonnet",
5
+ "Anthropic Claude Sonnet 4",
6
+ "DBRX",
7
+ "DBRX Instruct",
8
+ "DeepSeek V4 Pro (0813)",
9
+ "Gemini 2.5 Flash",
10
+ "Gemini 2.5 Pro",
11
+ "Gemini 3 Pro",
12
+ "Kimi K2.7",
13
+ "Meta Llama 2 13B",
14
+ "Meta Llama 2 70B",
15
+ "Meta Llama 2 7B",
16
+ "Meta Llama 3 (70B)",
17
+ "Meta Llama 3 8B",
18
+ "Meta Llama 3.1 405B",
19
+ "Meta-Llama-2-70B-Chat",
20
+ "Mistral 7B",
21
+ "Mixtral 8x7B",
22
+ "Mixtral-8x7B Instruct",
23
+ "MPT 30B",
24
+ "MPT 30B Instruct",
25
+ "MPT 7B",
26
+ "MPT 7B Instruct",
27
+ "OpenAI GPT-5.1 Codex Max",
28
+ "OpenAI GPT-5.1 Codex Mini",
29
+ "OpenAI GPT-5.2 Codex",
30
+ "Thinking Machine Labs Inkling"
31
+ ]
32
+ }
package/src/invoke.ts CHANGED
@@ -18,7 +18,7 @@
18
18
  * @module
19
19
  */
20
20
 
21
- import { isResponsesOnly as isResponsesOnlyRs } from "@dbx-tools/model-rs";
21
+ import { isResponsesOnly as isResponsesOnlyByPolicy } from "./policy.ts";
22
22
 
23
23
  /**
24
24
  * Path segment appended to a serving endpoint for OpenAI-compatible chat
@@ -83,7 +83,7 @@ export function chatCompletionsUrl(host: string): string {
83
83
  * those to {@link responsesUrl} instead of {@link invocationsUrl}.
84
84
  */
85
85
  export function isResponsesOnly(endpoint: string): boolean {
86
- return isResponsesOnlyRs(endpoint);
86
+ return isResponsesOnlyByPolicy(endpoint);
87
87
  }
88
88
 
89
89
  /**
@@ -0,0 +1,189 @@
1
+ /**
2
+ * Build-generated Databricks model retirement, capability, and rate-limit metadata.
3
+ *
4
+ * The committed snapshots are refreshed by `bun run model:metadata`. Runtime
5
+ * lookups build their normalized sets and maps once per process and perform no
6
+ * documentation network requests.
7
+ *
8
+ * @module
9
+ */
10
+ import * as functionUtils from "@dbx-tools/shared-core/function-utils";
11
+ import type { ModelStatus, ServingEndpointSummary } from "@dbx-tools/shared-model/contracts";
12
+
13
+ import capabilitiesSnapshotJson from "./generated/model-capabilities.json" with { type: "json" };
14
+ import rateLimitsSnapshotJson from "./generated/model-rate-limits.json" with { type: "json" };
15
+ import retiredModelsSnapshotJson from "./generated/retired-models.json" with { type: "json" };
16
+ import {
17
+ MODEL_METADATA_TTL_MS,
18
+ MODEL_RATE_LIMITS_URL,
19
+ OPENAI_RESPONSES_MODELS_URL,
20
+ RETIRED_MODELS_URL,
21
+ WEB_SEARCH_MODELS_URL,
22
+ type ModelCapabilitiesSnapshot,
23
+ type ModelCapabilityCatalogue,
24
+ type ModelRateLimits,
25
+ type ModelRateLimitsSnapshot,
26
+ type RetiredModelsSnapshot,
27
+ } from "./_metadata-contract.ts";
28
+ import { modelSearchQuery } from "./policy.ts";
29
+
30
+ export {
31
+ MODEL_METADATA_TTL_MS,
32
+ MODEL_RATE_LIMITS_URL,
33
+ OPENAI_RESPONSES_MODELS_URL,
34
+ RETIRED_MODELS_URL,
35
+ WEB_SEARCH_MODELS_URL,
36
+ };
37
+ export type {
38
+ ModelCapabilitiesSnapshot,
39
+ ModelCapabilityCatalogue,
40
+ ModelRateLimitCatalogue,
41
+ ModelRateLimits,
42
+ ModelRateLimitsSnapshot,
43
+ RetiredModelsSnapshot,
44
+ } from "./_metadata-contract.ts";
45
+
46
+ const retiredModelsSnapshot = retiredModelsSnapshotJson as RetiredModelsSnapshot;
47
+ const capabilitiesSnapshot = capabilitiesSnapshotJson as ModelCapabilitiesSnapshot;
48
+ const rateLimitsSnapshot = rateLimitsSnapshotJson as ModelRateLimitsSnapshot;
49
+
50
+ /** Documented capabilities resolved for one model identity. */
51
+ export interface ModelCapabilities {
52
+ readonly responses: boolean;
53
+ readonly imageInput: boolean;
54
+ readonly applyPatch: boolean;
55
+ readonly webSearch: boolean;
56
+ }
57
+
58
+ /** Combined build-generated metadata resolved for one model identity. */
59
+ export interface ModelMetadata {
60
+ readonly status: ModelStatus;
61
+ readonly capabilities: ModelCapabilities;
62
+ readonly rateLimits?: ModelRateLimits;
63
+ }
64
+
65
+ interface MetadataIndex {
66
+ readonly retiredNames: readonly string[];
67
+ readonly retiredKeys: ReadonlySet<string>;
68
+ readonly capabilities: {
69
+ readonly [Key in keyof ModelCapabilityCatalogue]: ReadonlySet<string>;
70
+ };
71
+ readonly rateLimits: ReadonlyMap<string, ModelRateLimits>;
72
+ }
73
+
74
+ const metadataIndex = functionUtils.memoize<MetadataIndex>(() => ({
75
+ retiredNames: Object.freeze([...retiredModelsSnapshot.models]),
76
+ retiredKeys: new Set(retiredModelsSnapshot.models.map(retiredModelKey).filter(Boolean)),
77
+ capabilities: {
78
+ responses: new Set(capabilitiesSnapshot.capabilities.responses),
79
+ imageInput: new Set(capabilitiesSnapshot.capabilities.imageInput),
80
+ applyPatch: new Set(capabilitiesSnapshot.capabilities.applyPatch),
81
+ webSearch: new Set(capabilitiesSnapshot.capabilities.webSearch),
82
+ },
83
+ rateLimits: new Map(Object.entries(rateLimitsSnapshot.catalogue.models)),
84
+ }));
85
+
86
+ /** Return the committed retired-model names. The array is cached and immutable. */
87
+ export function retiredModelNames(): readonly string[] {
88
+ return metadataIndex().retiredNames;
89
+ }
90
+
91
+ /** Resolve whether any supplied identity is listed in the retirement snapshot. */
92
+ export function modelStatusFor(
93
+ ...models: readonly (string | ServingEndpointSummary)[]
94
+ ): ModelStatus {
95
+ const retired = metadataIndex().retiredKeys;
96
+ const deprecated = models
97
+ .flatMap((model) => modelIdentities(model))
98
+ .some((identity) => {
99
+ const candidate = retiredModelKey(identity);
100
+ for (const key of retired) {
101
+ if (candidate === key || candidate.startsWith(`${key}-`)) return true;
102
+ }
103
+ return false;
104
+ });
105
+ return { deprecated };
106
+ }
107
+
108
+ /** Resolve documented model capabilities from a name or endpoint summary. */
109
+ export function modelCapabilitiesFor(model: string | ServingEndpointSummary): ModelCapabilities {
110
+ const capabilities = metadataIndex().capabilities;
111
+ const keys = modelIdentities(model, false).flatMap((identity) => modelKey(identity) ?? []);
112
+ return {
113
+ responses: keys.some((key) => capabilities.responses.has(key)),
114
+ imageInput: keys.some((key) => capabilities.imageInput.has(key)),
115
+ applyPatch: keys.some((key) => capabilities.applyPatch.has(key)),
116
+ webSearch: keys.some((key) => capabilities.webSearch.has(key)),
117
+ };
118
+ }
119
+
120
+ /** Resolve published ITPM, OTPM, and QPH limits from a model identity. */
121
+ export function modelRateLimitsFor(
122
+ model: string | ServingEndpointSummary,
123
+ ): ModelRateLimits | undefined {
124
+ const limits = metadataIndex().rateLimits;
125
+ for (const identity of modelIdentities(model)) {
126
+ const key = modelKey(identity);
127
+ if (!key) continue;
128
+ const found = limits.get(key);
129
+ if (found) return found;
130
+ }
131
+ return undefined;
132
+ }
133
+
134
+ /** Resolve retirement, capability, and rate-limit metadata together. */
135
+ export function modelMetadataFor(model: string | ServingEndpointSummary): ModelMetadata {
136
+ const rateLimits = modelRateLimitsFor(model);
137
+ return {
138
+ status: modelStatusFor(model),
139
+ capabilities: modelCapabilitiesFor(model),
140
+ ...(rateLimits ? { rateLimits } : {}),
141
+ };
142
+ }
143
+
144
+ /** Return generation timestamps for the three committed metadata snapshots. */
145
+ export function modelMetadataGeneratedAt(): Readonly<{
146
+ retiredModels: number;
147
+ capabilities: number;
148
+ rateLimits: number;
149
+ }> {
150
+ return {
151
+ retiredModels: retiredModelsSnapshot.generatedAt,
152
+ capabilities: capabilitiesSnapshot.generatedAt,
153
+ rateLimits: rateLimitsSnapshot.generatedAt,
154
+ };
155
+ }
156
+
157
+ function modelIdentities(
158
+ model: string | ServingEndpointSummary,
159
+ includeDisplayName = true,
160
+ ): string[] {
161
+ if (typeof model === "string") return [model];
162
+ return [
163
+ model.name,
164
+ ...(includeDisplayName ? [model.displayName] : []),
165
+ model.modelServiceName,
166
+ ...Object.values(model.serviceNames ?? {}),
167
+ ].filter((value): value is string => Boolean(value));
168
+ }
169
+
170
+ function modelKey(value: string): string | undefined {
171
+ return modelSearchQuery(value)?.replaceAll(" ", "-");
172
+ }
173
+
174
+ const RETIRED_MODEL_PREFIXES = new Set([
175
+ "ai",
176
+ "anthropic",
177
+ "databricks",
178
+ "dbx",
179
+ "google",
180
+ "meta",
181
+ "openai",
182
+ "system",
183
+ ]);
184
+
185
+ function retiredModelKey(value: string): string {
186
+ const tokens = value.toLowerCase().match(/[a-z0-9]+/g) ?? [];
187
+ while (tokens[0] && RETIRED_MODEL_PREFIXES.has(tokens[0])) tokens.shift();
188
+ return tokens.join("-");
189
+ }
@@ -9,7 +9,7 @@
9
9
  * `quality` / `speed` / `cost` profile when present, the classified
10
10
  * {@link ModelClass}, and (for embedding endpoints) the measured vector
11
11
  * `dimension` - and snaps loose, human-typed names to real endpoint ids
12
- * through the Rust model ranker so tokens like `"claude sonnet"` resolve to
12
+ * through the TypeScript model ranker so tokens like `"claude sonnet"` resolve to
13
13
  * `databricks-claude-sonnet-4-6`.
14
14
  *
15
15
  * The class stamp and embedding dimension are computed once per cache load:
@@ -27,7 +27,7 @@ import { appkit } from "@dbx-tools/appkit";
27
27
  import { errorUtils, log } from "@dbx-tools/shared-core";
28
28
  import { model, type ServingEndpointSummary } from "@dbx-tools/shared-model";
29
29
 
30
- import { classifyEndpointClasses, normalizeEndpoints, rankEndpoints } from "./_native.ts";
30
+ import { classifyEndpointClasses, normalizeEndpoints, rankEndpoints } from "./_ranking.ts";
31
31
 
32
32
  const { ModelClass } = model;
33
33
 
@@ -43,7 +43,7 @@ export type WorkspaceClientLike = Pick<appkit.WorkspaceClientLike, "servingEndpo
43
43
  /** Default TTL for the in-memory endpoint cache. Matches the Databricks SDK's session lifetime budget. */
44
44
  export const DEFAULT_MODEL_CACHE_TTL_MS = 5 * 60 * 1000;
45
45
 
46
- /** Default Rust distance threshold below which a fuzzy match is accepted. */
46
+ /** Default fuzzy distance threshold below which a match is accepted. */
47
47
  export const DEFAULT_FUZZY_THRESHOLD = 0.4;
48
48
 
49
49
  /** Cache key parts under which endpoint listings are stored. */
@@ -129,46 +129,44 @@ export async function listServingEndpointsUncached(
129
129
 
130
130
  async function fetchEndpoints(client: WorkspaceClientLike): Promise<ServingEndpointSummary[]> {
131
131
  const startedAt = Date.now();
132
- const out = await listServingEndpointsUncached(client);
133
- stampModelClasses(out);
134
- await measureEmbeddingDimensions(client, out);
132
+ const classified = stampModelClasses(await listServingEndpointsUncached(client));
133
+ const out = await measureEmbeddingDimensions(client, classified);
135
134
  logger.debug("listed", { count: out.length, elapsedMs: Date.now() - startedAt });
136
135
  return out;
137
136
  }
138
137
 
139
138
  /**
140
139
  * Stamp each summary's {@link ServingEndpointSummary.class} from the relative
141
- * classification of the whole set. Mutates `summaries` in place. Endpoints the
142
- * classifier doesn't recognize (custom, unscored, non-LLM) are left without a
143
- * class.
140
+ * classification of the whole set. Endpoints the classifier doesn't recognize
141
+ * (custom, unscored, non-LLM) are left without a class.
144
142
  */
145
- function stampModelClasses(summaries: ServingEndpointSummary[]): void {
143
+ function stampModelClasses(
144
+ summaries: readonly ServingEndpointSummary[],
145
+ ): ServingEndpointSummary[] {
146
146
  const classOf = classifyEndpointClasses(summaries);
147
- for (const summary of summaries) {
147
+ return summaries.map((summary) => {
148
148
  const cls = classOf.get(summary.name);
149
- if (cls !== undefined) summary.class = cls;
150
- }
149
+ return cls === undefined ? summary : { ...summary, class: cls };
150
+ });
151
151
  }
152
152
 
153
153
  /**
154
154
  * Measure the embedding vector dimension of every {@link ModelClass.Embedding}
155
- * endpoint by pinging it once, all in parallel. Mutates the matching summaries
156
- * in place with the resulting `dimension`. Runs only on a cache miss (it's
155
+ * endpoint by pinging it once, all in parallel. Runs only on a cache miss (it's
157
156
  * called from {@link fetchEndpoints}), so the probe cost is amortized across the
158
- * cached TTL window. Per-endpoint failures are swallowed (logged at warn) so
159
- * one unreachable embedding model never fails the listing.
157
+ * cached TTL window. Per-endpoint failures are swallowed (logged at warn) so one
158
+ * unreachable embedding model never fails the listing.
160
159
  */
161
160
  async function measureEmbeddingDimensions(
162
161
  client: WorkspaceClientLike,
163
- summaries: ServingEndpointSummary[],
164
- ): Promise<void> {
165
- await Promise.all(
166
- summaries
167
- .filter((s) => s.class === ModelClass.Embedding)
168
- .map(async (summary) => {
169
- const dimension = await pingEmbeddingDimension(client, summary.name);
170
- if (dimension !== undefined) summary.dimension = dimension;
171
- }),
162
+ summaries: readonly ServingEndpointSummary[],
163
+ ): Promise<ServingEndpointSummary[]> {
164
+ return Promise.all(
165
+ summaries.map(async (summary) => {
166
+ if (summary.class !== ModelClass.Embedding) return summary;
167
+ const dimension = await pingEmbeddingDimension(client, summary.name);
168
+ return dimension === undefined ? summary : { ...summary, dimension };
169
+ }),
172
170
  );
173
171
  }
174
172
 
@@ -215,7 +213,7 @@ export async function clearServingEndpointsCache(
215
213
 
216
214
  /**
217
215
  * Result of fuzzy-resolving a user-supplied model name against the live
218
- * endpoint list. `score` is the Rust ranker's distance (`0` is exact, `1` is no match);
216
+ * endpoint list. `score` is the ranker's distance (`0` is exact, `1` is no match);
219
217
  * `matched` is `false` when the score exceeds the configured threshold so
220
218
  * callers can fall back to the original input (Databricks will then return a
221
219
  * clean 404).
@@ -246,7 +244,7 @@ export interface ScoredEndpoint {
246
244
  * (lowest score) first, keeping only those within `threshold`:
247
245
  *
248
246
  * 1. An exact name match short-circuits to a single `score: 0` result.
249
- * 2. Otherwise Rust tokenizes the input and scores every endpoint through the
247
+ * 2. Otherwise the shared TypeScript policy tokenizes and scores every endpoint through the
250
248
  * same fuzzy-distance policy used by {@link lookupModels}.
251
249
  *
252
250
  * Returns `[]` for an empty endpoint list or when `input` tokenizes to nothing,