@dbx-tools/model 0.9.38 → 0.9.43
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/index.ts +3 -1
- package/lib/index.d.ts +3 -1
- package/lib/index.js +3 -2
- package/lib/package.json +137 -0
- package/lib/src/_metadata-generator.js +11 -8
- package/lib/src/_ranking.js +2 -2
- package/lib/src/_retirement.d.ts +10 -0
- package/lib/src/_retirement.js +59 -0
- package/lib/src/bindings.d.ts +42 -0
- package/lib/src/bindings.js +43 -0
- package/lib/src/classify.d.ts +5 -0
- package/lib/src/classify.js +13 -5
- package/lib/src/metadata-cache.js +7 -10
- package/lib/src/metadata.d.ts +2 -7
- package/lib/src/metadata.js +16 -53
- package/lib/src/model-client.d.ts +2 -0
- package/lib/src/model-client.js +13 -7
- package/lib/src/policy.d.ts +14 -0
- package/lib/src/policy.js +47 -2
- package/lib/src/reasoning-translation.js +8 -3
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +14 -6
- package/src/_metadata-generator.ts +11 -11
- package/src/_ranking.ts +1 -1
- package/src/_retirement.ts +70 -0
- package/src/bindings.ts +77 -0
- package/src/classify.ts +13 -4
- package/src/metadata-cache.ts +6 -11
- package/src/metadata.ts +43 -77
- package/src/model-client.ts +13 -8
- package/src/policy.ts +45 -1
- package/src/reasoning-translation.ts +7 -4
package/src/bindings.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Binding-safe facade for Databricks model discovery and routing.
|
|
3
|
+
*
|
|
4
|
+
* Generated language bindings should target this module instead of binding the
|
|
5
|
+
* full model package. It exposes plain TypeScript records while delegating all
|
|
6
|
+
* authentication, catalogue caching, fuzzy matching, policy, and URL selection
|
|
7
|
+
* to {@link createModelClient}.
|
|
8
|
+
*
|
|
9
|
+
* @module
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { ModelClass, ModelMetadata } from "@dbx-tools/shared-model/contracts";
|
|
13
|
+
import { createModelClient, type ModelProtocol } from "./model-client.ts";
|
|
14
|
+
|
|
15
|
+
/** Binding-friendly fuzzy model route request. */
|
|
16
|
+
export interface ResolveModelRouteOptions {
|
|
17
|
+
readonly profile?: string;
|
|
18
|
+
readonly model?: string;
|
|
19
|
+
readonly fuzzy?: boolean;
|
|
20
|
+
readonly threshold?: number;
|
|
21
|
+
readonly requiresTools?: boolean;
|
|
22
|
+
readonly modelClass?: ModelClass;
|
|
23
|
+
readonly fallbacks?: readonly string[];
|
|
24
|
+
readonly liveOnly?: boolean;
|
|
25
|
+
readonly protocol?: ModelProtocol;
|
|
26
|
+
readonly refresh?: boolean;
|
|
27
|
+
readonly login?: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Binding-friendly model endpoint metadata used by Graphiti and other runtimes. */
|
|
31
|
+
export interface ResolvedModelRoute {
|
|
32
|
+
readonly modelId: string;
|
|
33
|
+
readonly endpointName?: string;
|
|
34
|
+
readonly endpointTask?: string;
|
|
35
|
+
readonly endpointDimension?: number;
|
|
36
|
+
readonly source: "explicit" | "fuzzy-match" | "class" | "fallback";
|
|
37
|
+
readonly protocol: ModelProtocol;
|
|
38
|
+
readonly host: string;
|
|
39
|
+
readonly apiBase: string;
|
|
40
|
+
readonly url: string;
|
|
41
|
+
readonly headers: Readonly<Record<string, string>>;
|
|
42
|
+
readonly metadata: ModelMetadata;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Resolve one fuzzy model request through the canonical Node model client. */
|
|
46
|
+
export async function resolveModelRoute(
|
|
47
|
+
options: ResolveModelRouteOptions = {},
|
|
48
|
+
): Promise<ResolvedModelRoute> {
|
|
49
|
+
const client = await createModelClient({
|
|
50
|
+
...(options.profile ? { auth: { profile: options.profile } } : {}),
|
|
51
|
+
});
|
|
52
|
+
const route = await client.route({
|
|
53
|
+
...(options.model ? { explicit: options.model } : {}),
|
|
54
|
+
...(options.fuzzy === undefined ? {} : { fuzzy: options.fuzzy }),
|
|
55
|
+
...(options.threshold === undefined ? {} : { threshold: options.threshold }),
|
|
56
|
+
...(options.requiresTools === undefined ? {} : { requiresTools: options.requiresTools }),
|
|
57
|
+
...(options.modelClass ? { modelClass: options.modelClass } : {}),
|
|
58
|
+
...(options.fallbacks ? { fallbacks: [...options.fallbacks] } : {}),
|
|
59
|
+
...(options.liveOnly === undefined ? {} : { liveOnly: options.liveOnly }),
|
|
60
|
+
...(options.protocol ? { protocol: options.protocol } : {}),
|
|
61
|
+
...(options.refresh === undefined ? {} : { refresh: options.refresh }),
|
|
62
|
+
...(options.login === undefined ? {} : { login: options.login }),
|
|
63
|
+
});
|
|
64
|
+
return {
|
|
65
|
+
modelId: route.modelId,
|
|
66
|
+
...(route.endpoint?.name ? { endpointName: route.endpoint.name } : {}),
|
|
67
|
+
...(route.endpoint?.task ? { endpointTask: route.endpoint.task } : {}),
|
|
68
|
+
...(route.endpoint?.dimension ? { endpointDimension: route.endpoint.dimension } : {}),
|
|
69
|
+
source: route.source,
|
|
70
|
+
protocol: route.protocol,
|
|
71
|
+
host: route.host,
|
|
72
|
+
apiBase: route.apiBase,
|
|
73
|
+
url: route.url,
|
|
74
|
+
headers: { ...route.headers },
|
|
75
|
+
metadata: route.metadata,
|
|
76
|
+
};
|
|
77
|
+
}
|
package/src/classify.ts
CHANGED
|
@@ -111,15 +111,24 @@ export function versionTuple(name: string): [number, number, number] {
|
|
|
111
111
|
return [nums[0] ?? 0, nums[1] ?? 0, nums[2] ?? 0];
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
-
/**
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
114
|
+
/**
|
|
115
|
+
* Compare version tuples component-wise with missing parts as 0.
|
|
116
|
+
* Positive when `left` is newer.
|
|
117
|
+
*/
|
|
118
|
+
export function compareVersionTuples(left: readonly number[], right: readonly number[]): number {
|
|
119
|
+
const length = Math.max(left.length, right.length, 3);
|
|
120
|
+
for (let index = 0; index < length; index += 1) {
|
|
121
|
+
const diff = (left[index] ?? 0) - (right[index] ?? 0);
|
|
118
122
|
if (diff !== 0) return diff;
|
|
119
123
|
}
|
|
120
124
|
return 0;
|
|
121
125
|
}
|
|
122
126
|
|
|
127
|
+
/** Compare two version tuples so the higher version sorts first (descending). */
|
|
128
|
+
function compareVersionDesc(a: readonly number[], b: readonly number[]): number {
|
|
129
|
+
return compareVersionTuples(b, a);
|
|
130
|
+
}
|
|
131
|
+
|
|
123
132
|
/**
|
|
124
133
|
* Monotonic version key for ordering siblings within a family/tier
|
|
125
134
|
* (e.g. `opus-4-8` over `opus-4-7`). Encodes the {@link versionTuple}
|
package/src/metadata-cache.ts
CHANGED
|
@@ -21,6 +21,7 @@ import * as log from "@dbx-tools/shared-core/log";
|
|
|
21
21
|
import cacache from "cacache";
|
|
22
22
|
import envPaths from "env-paths";
|
|
23
23
|
|
|
24
|
+
import packageManifest from "../package.json" with { type: "json" };
|
|
24
25
|
import { MODEL_METADATA_TTL_MS } from "./_metadata-contract.ts";
|
|
25
26
|
|
|
26
27
|
const logger = log.logger("model/metadata-cache");
|
|
@@ -30,15 +31,10 @@ interface PackageIdentity {
|
|
|
30
31
|
readonly version: string;
|
|
31
32
|
}
|
|
32
33
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
identifier: mod.PACKAGE_IDENTIFIER,
|
|
38
|
-
version: mod.PACKAGE_VERSION,
|
|
39
|
-
}));
|
|
40
|
-
return packageIdentityPromise;
|
|
41
|
-
}
|
|
34
|
+
const PACKAGE_IDENTITY: PackageIdentity = {
|
|
35
|
+
identifier: packageManifest.name,
|
|
36
|
+
version: packageManifest.version,
|
|
37
|
+
};
|
|
42
38
|
|
|
43
39
|
/** Envelope stored as JSON bytes in cacache for every key. */
|
|
44
40
|
export interface MetadataCacheRecord<T> {
|
|
@@ -247,7 +243,7 @@ async function resolveCacheDir(options: {
|
|
|
247
243
|
readonly version?: string;
|
|
248
244
|
}): Promise<string> {
|
|
249
245
|
if (options.cacheDir) return options.cacheDir;
|
|
250
|
-
const version = options.version ??
|
|
246
|
+
const version = options.version ?? PACKAGE_IDENTITY.version;
|
|
251
247
|
return defaultMetadataCacheDir(version);
|
|
252
248
|
}
|
|
253
249
|
|
|
@@ -319,4 +315,3 @@ function isCacheMiss(error: unknown): boolean {
|
|
|
319
315
|
(error as { code?: string }).code === "ENOENT",
|
|
320
316
|
);
|
|
321
317
|
}
|
|
322
|
-
|
package/src/metadata.ts
CHANGED
|
@@ -11,12 +11,10 @@
|
|
|
11
11
|
* @module
|
|
12
12
|
*/
|
|
13
13
|
import * as functionUtils from "@dbx-tools/shared-core/function-utils";
|
|
14
|
-
import * as log from "@dbx-tools/shared-core/log";
|
|
15
14
|
import type {
|
|
16
15
|
ModelCapabilities,
|
|
17
16
|
ModelMetadata,
|
|
18
17
|
ModelRateLimits,
|
|
19
|
-
ModelStatus,
|
|
20
18
|
ServingEndpointSummary,
|
|
21
19
|
} from "@dbx-tools/shared-model/contracts";
|
|
22
20
|
|
|
@@ -31,7 +29,6 @@ import {
|
|
|
31
29
|
type ModelCapabilityCatalogue,
|
|
32
30
|
type ModelRateLimitsSnapshot,
|
|
33
31
|
type ReasoningModelsSnapshot,
|
|
34
|
-
type RetiredModelsSnapshot,
|
|
35
32
|
} from "./_metadata-contract.ts";
|
|
36
33
|
import {
|
|
37
34
|
parseModelCapabilities,
|
|
@@ -39,17 +36,22 @@ import {
|
|
|
39
36
|
parseReasoningModels,
|
|
40
37
|
parseRetiredModels,
|
|
41
38
|
} from "./_metadata-generator.ts";
|
|
39
|
+
import {
|
|
40
|
+
COMMITTED_RETIRED_MODELS,
|
|
41
|
+
modelStatusFor,
|
|
42
|
+
replaceRetiredModelNames,
|
|
43
|
+
RETIRED_MODELS_GENERATED_AT,
|
|
44
|
+
} from "./_retirement.ts";
|
|
45
|
+
import capabilitiesSnapshotJson from "./generated/model-capabilities.json" with { type: "json" };
|
|
46
|
+
import rateLimitsSnapshotJson from "./generated/model-rate-limits.json" with { type: "json" };
|
|
47
|
+
import reasoningSnapshotJson from "./generated/model-reasoning.json" with { type: "json" };
|
|
42
48
|
import {
|
|
43
49
|
createMetadataCache,
|
|
44
50
|
mergePreferFreshList,
|
|
45
51
|
mergePreferFreshRecord,
|
|
46
52
|
type MetadataCache,
|
|
47
53
|
} from "./metadata-cache.ts";
|
|
48
|
-
import
|
|
49
|
-
import rateLimitsSnapshotJson from "./generated/model-rate-limits.json" with { type: "json" };
|
|
50
|
-
import reasoningSnapshotJson from "./generated/model-reasoning.json" with { type: "json" };
|
|
51
|
-
import retiredModelsSnapshotJson from "./generated/retired-models.json" with { type: "json" };
|
|
52
|
-
import { modelSearchQuery } from "./policy.ts";
|
|
54
|
+
import { inheritsNativeWebSearch, isFoundationModelIdentity, modelSearchQuery } from "./policy.ts";
|
|
53
55
|
import {
|
|
54
56
|
defaultReasoningLevels,
|
|
55
57
|
documentedReasoningLevels,
|
|
@@ -69,16 +71,11 @@ export {
|
|
|
69
71
|
WEB_SEARCH_MODELS_URL,
|
|
70
72
|
};
|
|
71
73
|
|
|
72
|
-
const logger = log.logger("model/metadata");
|
|
73
|
-
|
|
74
|
-
const retiredModelsSnapshot = retiredModelsSnapshotJson as RetiredModelsSnapshot;
|
|
75
74
|
const capabilitiesSnapshot = capabilitiesSnapshotJson as ModelCapabilitiesSnapshot;
|
|
76
75
|
const rateLimitsSnapshot = rateLimitsSnapshotJson as ModelRateLimitsSnapshot;
|
|
77
76
|
const reasoningSnapshot = reasoningSnapshotJson as ReasoningModelsSnapshot;
|
|
78
77
|
|
|
79
78
|
interface MetadataIndex {
|
|
80
|
-
readonly retiredNames: readonly string[];
|
|
81
|
-
readonly retiredKeys: ReadonlySet<string>;
|
|
82
79
|
readonly capabilities: {
|
|
83
80
|
readonly [Key in keyof ModelCapabilityCatalogue]: ReadonlySet<string>;
|
|
84
81
|
};
|
|
@@ -93,7 +90,7 @@ const USER_AGENT = "dbx-tools-model-metadata/1";
|
|
|
93
90
|
|
|
94
91
|
const retiredCache = createMetadataCache<readonly string[]>({
|
|
95
92
|
key: "retired-models",
|
|
96
|
-
fallback:
|
|
93
|
+
fallback: COMMITTED_RETIRED_MODELS,
|
|
97
94
|
load: async () => parseRetiredModels(await loadPage(RETIRED_MODELS_URL), nowSeconds()).models,
|
|
98
95
|
merge: mergePreferFreshList,
|
|
99
96
|
});
|
|
@@ -148,7 +145,9 @@ const reasoningDocsCache = createMetadataCache<Readonly<Record<string, readonly
|
|
|
148
145
|
merge: mergePreferFreshRecord,
|
|
149
146
|
});
|
|
150
147
|
|
|
151
|
-
const learnedReasoningCache = createMetadataCache<
|
|
148
|
+
const learnedReasoningCache = createMetadataCache<
|
|
149
|
+
Readonly<Record<string, readonly ReasoningLevel[]>>
|
|
150
|
+
>({
|
|
152
151
|
key: "learned-reasoning",
|
|
153
152
|
fallback: {},
|
|
154
153
|
merge: mergePreferFreshRecord,
|
|
@@ -156,11 +155,13 @@ const learnedReasoningCache = createMetadataCache<Readonly<Record<string, readon
|
|
|
156
155
|
|
|
157
156
|
let liveIndex: MetadataIndex | undefined;
|
|
158
157
|
|
|
159
|
-
const metadataIndex = functionUtils.memoize<MetadataIndex>(
|
|
158
|
+
const metadataIndex = functionUtils.memoize<MetadataIndex>(
|
|
159
|
+
() => liveIndex ?? buildIndexFromCommitted(),
|
|
160
|
+
);
|
|
160
161
|
|
|
161
162
|
function buildIndexFromCommitted(): MetadataIndex {
|
|
162
163
|
return buildIndex({
|
|
163
|
-
retiredNames:
|
|
164
|
+
retiredNames: COMMITTED_RETIRED_MODELS,
|
|
164
165
|
capabilities: capabilitiesSnapshot.capabilities,
|
|
165
166
|
rateLimits: rateLimitsSnapshot.catalogue.models,
|
|
166
167
|
reasoning: reasoningSnapshot.catalogue.models,
|
|
@@ -175,9 +176,8 @@ function buildIndex(input: {
|
|
|
175
176
|
readonly reasoning: Readonly<Record<string, readonly string[]>>;
|
|
176
177
|
readonly learnedReasoning: Readonly<Record<string, readonly ReasoningLevel[]>>;
|
|
177
178
|
}): MetadataIndex {
|
|
179
|
+
replaceRetiredModelNames(input.retiredNames);
|
|
178
180
|
return {
|
|
179
|
-
retiredNames: Object.freeze([...input.retiredNames]),
|
|
180
|
-
retiredKeys: new Set(input.retiredNames.map(retiredModelKey).filter(Boolean)),
|
|
181
181
|
capabilities: {
|
|
182
182
|
responses: new Set(input.capabilities.responses),
|
|
183
183
|
imageInput: new Set(input.capabilities.imageInput),
|
|
@@ -228,13 +228,15 @@ let refreshInflight: Promise<MetadataIndex> | undefined;
|
|
|
228
228
|
export async function refreshModelMetadata(): Promise<MetadataIndex> {
|
|
229
229
|
if (refreshInflight) return refreshInflight;
|
|
230
230
|
refreshInflight = (async () => {
|
|
231
|
-
const [retiredNames, capabilities, rateLimits, reasoning, learnedReasoning] = await Promise.all(
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
231
|
+
const [retiredNames, capabilities, rateLimits, reasoning, learnedReasoning] = await Promise.all(
|
|
232
|
+
[
|
|
233
|
+
retiredCache.get(),
|
|
234
|
+
capabilitiesCache.get(),
|
|
235
|
+
rateLimitsCache.get(),
|
|
236
|
+
reasoningDocsCache.get(),
|
|
237
|
+
learnedReasoningCache.get(),
|
|
238
|
+
],
|
|
239
|
+
);
|
|
238
240
|
liveIndex = buildIndex({
|
|
239
241
|
retiredNames,
|
|
240
242
|
capabilities,
|
|
@@ -249,14 +251,10 @@ export async function refreshModelMetadata(): Promise<MetadataIndex> {
|
|
|
249
251
|
return refreshInflight;
|
|
250
252
|
}
|
|
251
253
|
|
|
252
|
-
void hydrateModelMetadata().catch((error) => {
|
|
253
|
-
logger.warn("metadata cache hydrate failed; using committed snapshots", { error });
|
|
254
|
-
});
|
|
255
|
-
|
|
256
254
|
/** Load cacache / memory entries without forcing a network refresh. */
|
|
257
255
|
export async function hydrateModelMetadata(): Promise<MetadataIndex> {
|
|
258
256
|
const [retiredNames, capabilities, rateLimits, reasoning, learnedReasoning] = await Promise.all([
|
|
259
|
-
peekOrFallback(retiredCache,
|
|
257
|
+
peekOrFallback(retiredCache, COMMITTED_RETIRED_MODELS),
|
|
260
258
|
peekOrFallback(capabilitiesCache, capabilitiesSnapshot.capabilities),
|
|
261
259
|
peekOrFallback(rateLimitsCache, rateLimitsSnapshot.catalogue.models),
|
|
262
260
|
peekOrFallback(reasoningDocsCache, reasoningSnapshot.catalogue.models),
|
|
@@ -283,10 +281,7 @@ export function modelMetadataCachePath(): string {
|
|
|
283
281
|
return retiredCache.path;
|
|
284
282
|
}
|
|
285
283
|
|
|
286
|
-
|
|
287
|
-
export function retiredModelNames(): readonly string[] {
|
|
288
|
-
return metadataIndex().retiredNames;
|
|
289
|
-
}
|
|
284
|
+
export { modelStatusFor, retiredModelNames } from "./_retirement.ts";
|
|
290
285
|
|
|
291
286
|
/** Return the committed reasoning-effort catalogue. Cached per process. */
|
|
292
287
|
export function reasoningModelCatalogue(): ReasoningModelCatalogue {
|
|
@@ -338,32 +333,23 @@ export async function learnReasoningLevelsFromError(
|
|
|
338
333
|
return rememberReasoningLevels(model, parseReasoningLevels(body));
|
|
339
334
|
}
|
|
340
335
|
|
|
341
|
-
/** Resolve whether any supplied identity is listed in the retirement snapshot. */
|
|
342
|
-
export function modelStatusFor(
|
|
343
|
-
...models: readonly (string | ServingEndpointSummary)[]
|
|
344
|
-
): ModelStatus {
|
|
345
|
-
const retired = metadataIndex().retiredKeys;
|
|
346
|
-
const deprecated = models
|
|
347
|
-
.flatMap((model) => modelIdentities(model))
|
|
348
|
-
.some((identity) => {
|
|
349
|
-
const candidate = retiredModelKey(identity);
|
|
350
|
-
for (const key of retired) {
|
|
351
|
-
if (candidate === key || candidate.startsWith(`${key}-`)) return true;
|
|
352
|
-
}
|
|
353
|
-
return false;
|
|
354
|
-
});
|
|
355
|
-
return { deprecated };
|
|
356
|
-
}
|
|
357
|
-
|
|
358
336
|
/** Resolve documented model capabilities from a name or endpoint summary. */
|
|
359
337
|
export function modelCapabilitiesFor(model: string | ServingEndpointSummary): ModelCapabilities {
|
|
360
338
|
const capabilities = metadataIndex().capabilities;
|
|
361
|
-
const
|
|
339
|
+
const identities = modelIdentities(model, false);
|
|
340
|
+
const keys = identities.flatMap((identity) => modelKey(identity) ?? []);
|
|
362
341
|
return {
|
|
363
342
|
responses: keys.some((key) => capabilities.responses.has(key)),
|
|
364
343
|
imageInput: keys.some((key) => capabilities.imageInput.has(key)),
|
|
365
344
|
applyPatch: keys.some((key) => capabilities.applyPatch.has(key)),
|
|
366
|
-
|
|
345
|
+
// Version inheritance only applies to hosted foundation identities.
|
|
346
|
+
webSearch:
|
|
347
|
+
keys.some((key) => capabilities.webSearch.has(key)) ||
|
|
348
|
+
identities.some(
|
|
349
|
+
(identity) =>
|
|
350
|
+
isFoundationModelIdentity(identity) &&
|
|
351
|
+
inheritsNativeWebSearch(identity, capabilities.webSearch),
|
|
352
|
+
),
|
|
367
353
|
};
|
|
368
354
|
}
|
|
369
355
|
|
|
@@ -388,9 +374,7 @@ export function modelRateLimitsFor(
|
|
|
388
374
|
* Does not parse error bodies; callers should use {@link parseReasoningLevels}
|
|
389
375
|
* and then {@link rememberReasoningLevels}.
|
|
390
376
|
*/
|
|
391
|
-
export function modelReasoningLevelsFor(
|
|
392
|
-
model: string | ServingEndpointSummary,
|
|
393
|
-
): ReasoningLevel[] {
|
|
377
|
+
export function modelReasoningLevelsFor(model: string | ServingEndpointSummary): ReasoningLevel[] {
|
|
394
378
|
const index = metadataIndex();
|
|
395
379
|
for (const identity of modelIdentities(model)) {
|
|
396
380
|
const learned = documentedReasoningLevels(identity, index.learnedReasoning);
|
|
@@ -400,8 +384,7 @@ export function modelReasoningLevelsFor(
|
|
|
400
384
|
const documented = documentedReasoningLevels(identity, index.reasoning);
|
|
401
385
|
if (documented?.length) return documented;
|
|
402
386
|
}
|
|
403
|
-
const primary =
|
|
404
|
-
typeof model === "string" ? model : (model.modelServiceName ?? model.name);
|
|
387
|
+
const primary = typeof model === "string" ? model : (model.modelServiceName ?? model.name);
|
|
405
388
|
return defaultReasoningLevels(primary);
|
|
406
389
|
}
|
|
407
390
|
|
|
@@ -423,7 +406,7 @@ export function modelMetadataGeneratedAt(): Readonly<{
|
|
|
423
406
|
reasoning: number;
|
|
424
407
|
}> {
|
|
425
408
|
return {
|
|
426
|
-
retiredModels:
|
|
409
|
+
retiredModels: RETIRED_MODELS_GENERATED_AT,
|
|
427
410
|
capabilities: capabilitiesSnapshot.generatedAt,
|
|
428
411
|
rateLimits: rateLimitsSnapshot.generatedAt,
|
|
429
412
|
reasoning: reasoningSnapshot.generatedAt,
|
|
@@ -447,23 +430,6 @@ function modelKey(value: string): string | undefined {
|
|
|
447
430
|
return modelSearchQuery(value)?.replaceAll(" ", "-");
|
|
448
431
|
}
|
|
449
432
|
|
|
450
|
-
const RETIRED_MODEL_PREFIXES = new Set([
|
|
451
|
-
"ai",
|
|
452
|
-
"anthropic",
|
|
453
|
-
"databricks",
|
|
454
|
-
"dbx",
|
|
455
|
-
"google",
|
|
456
|
-
"meta",
|
|
457
|
-
"openai",
|
|
458
|
-
"system",
|
|
459
|
-
]);
|
|
460
|
-
|
|
461
|
-
function retiredModelKey(value: string): string {
|
|
462
|
-
const tokens = value.toLowerCase().match(/[a-z0-9]+/g) ?? [];
|
|
463
|
-
while (tokens[0] && RETIRED_MODEL_PREFIXES.has(tokens[0])) tokens.shift();
|
|
464
|
-
return tokens.join("-");
|
|
465
|
-
}
|
|
466
|
-
|
|
467
433
|
async function loadPage(url: string): Promise<string> {
|
|
468
434
|
const response = await fetch(url, {
|
|
469
435
|
headers: { "user-agent": USER_AGENT },
|
package/src/model-client.ts
CHANGED
|
@@ -3,16 +3,17 @@
|
|
|
3
3
|
*
|
|
4
4
|
* This is the portable entry point used by generated Python bindings. It keeps
|
|
5
5
|
* authentication, endpoint discovery, caching, normalization, ranking, route
|
|
6
|
-
* selection, and published metadata in Node while exposing
|
|
6
|
+
* selection, route construction, and published metadata in Node while exposing
|
|
7
|
+
* one small client.
|
|
7
8
|
*
|
|
8
9
|
* @module
|
|
9
10
|
*/
|
|
10
11
|
import {
|
|
11
|
-
|
|
12
|
-
profile as authProfile,
|
|
12
|
+
createAuthClient,
|
|
13
13
|
type AuthClient,
|
|
14
14
|
type DatabricksAuthDependencies,
|
|
15
|
-
} from "@dbx-tools/auth";
|
|
15
|
+
} from "@dbx-tools/auth/client";
|
|
16
|
+
import { listProfiles as listAuthProfiles } from "@dbx-tools/auth/profile";
|
|
16
17
|
import type { DatabricksProfileSummary } from "@dbx-tools/shared-auth";
|
|
17
18
|
import * as log from "@dbx-tools/shared-core/log";
|
|
18
19
|
import * as object from "@dbx-tools/shared-core/object";
|
|
@@ -29,7 +30,7 @@ import {
|
|
|
29
30
|
import { normalizeEndpoints } from "./_ranking.ts";
|
|
30
31
|
import { lookupModels, resolveModel } from "./_selection.ts";
|
|
31
32
|
import { chatCompletionsUrl, invocationsUrl, responsesUpstreamUrl } from "./invoke.ts";
|
|
32
|
-
import { modelMetadataFor } from "./metadata.ts";
|
|
33
|
+
import { hydrateModelMetadata, modelMetadataFor } from "./metadata.ts";
|
|
33
34
|
import { modelServingApi } from "./policy.ts";
|
|
34
35
|
|
|
35
36
|
const logger = log.logger("model/client");
|
|
@@ -49,6 +50,8 @@ export interface ModelClientOptions {
|
|
|
49
50
|
auth?: ModelAuthOptions;
|
|
50
51
|
/** In-memory endpoint catalogue TTL in milliseconds. */
|
|
51
52
|
cacheTtlMs?: number;
|
|
53
|
+
/** Hydrate Node-only metadata caches before constructing the client. */
|
|
54
|
+
hydrateMetadata?: boolean;
|
|
52
55
|
}
|
|
53
56
|
|
|
54
57
|
/** Secret-free runtime identity and cache configuration. */
|
|
@@ -246,15 +249,16 @@ class DefaultModelClient implements ModelClient {
|
|
|
246
249
|
/** Create an authentication-aware Databricks model client. */
|
|
247
250
|
export async function createModelClient(options: ModelClientOptions = {}): Promise<ModelClient> {
|
|
248
251
|
const cacheTtlMs = validateCacheTtl(options.cacheTtlMs);
|
|
252
|
+
if (options.hydrateMetadata ?? true) await hydrateModelMetadata();
|
|
249
253
|
return new DefaultModelClient(
|
|
250
|
-
new AuthenticatedModelClient(await
|
|
254
|
+
new AuthenticatedModelClient(await createAuthClient(options.auth), globalThis.fetch),
|
|
251
255
|
cacheTtlMs,
|
|
252
256
|
);
|
|
253
257
|
}
|
|
254
258
|
|
|
255
259
|
/** List configured Databricks profiles outside the model client facade. */
|
|
256
260
|
export function listProfiles(refresh = false): DatabricksProfileSummary[] {
|
|
257
|
-
return
|
|
261
|
+
return listAuthProfiles({ refresh });
|
|
258
262
|
}
|
|
259
263
|
|
|
260
264
|
/** @internal Construct a model client around a test or host-owned Databricks client. */
|
|
@@ -270,9 +274,10 @@ export async function createModelClientWithDependencies(
|
|
|
270
274
|
options: ModelClientOptions,
|
|
271
275
|
dependencies: DatabricksAuthDependencies,
|
|
272
276
|
): Promise<ModelClient> {
|
|
277
|
+
if (options.hydrateMetadata ?? true) await hydrateModelMetadata();
|
|
273
278
|
return new DefaultModelClient(
|
|
274
279
|
new AuthenticatedModelClient(
|
|
275
|
-
await
|
|
280
|
+
await createAuthClient(options.auth, dependencies),
|
|
276
281
|
dependencies.fetch ?? globalThis.fetch,
|
|
277
282
|
),
|
|
278
283
|
validateCacheTtl(options.cacheTtlMs),
|
package/src/policy.ts
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
type ServingEndpointSummary,
|
|
7
7
|
} from "@dbx-tools/shared-model/contracts";
|
|
8
8
|
|
|
9
|
-
import { supportsToolsByFamily } from "./classify.ts";
|
|
9
|
+
import { compareVersionTuples, supportsToolsByFamily, versionTuple } from "./classify.ts";
|
|
10
10
|
|
|
11
11
|
const FAMILIES = [
|
|
12
12
|
"claude",
|
|
@@ -54,6 +54,50 @@ export function modelSearchQuery(name: string): string | undefined {
|
|
|
54
54
|
return [parsed.family, ...parsed.version.map(String), ...parsed.model].join(" ");
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Return whether `name` has native web search: an exact documented identity, or
|
|
59
|
+
* a later {@link versionTuple} in the same family as a documented native
|
|
60
|
+
* web-search model. Intra-family floors use the name digit parser so identities
|
|
61
|
+
* like `databricks-claude-opus-4-8` and `system.ai.gpt-6-1-sol` compare.
|
|
62
|
+
* Hosted GPT-OSS weights never inherit.
|
|
63
|
+
*/
|
|
64
|
+
export function inheritsNativeWebSearch(name: string, documented: Iterable<string>): boolean {
|
|
65
|
+
const documentedKeys = documented instanceof Set ? documented : new Set(documented);
|
|
66
|
+
const key = modelSearchQuery(name)?.replaceAll(" ", "-");
|
|
67
|
+
if (key && documentedKeys.has(key)) return true;
|
|
68
|
+
const family = modelFamily(name);
|
|
69
|
+
if (!family || isOpenWeightsGpt(name)) return false;
|
|
70
|
+
const version = versionTuple(name);
|
|
71
|
+
if (isZeroVersion(version)) return false;
|
|
72
|
+
let floor: readonly number[] | undefined;
|
|
73
|
+
for (const entry of documentedKeys) {
|
|
74
|
+
if (modelFamily(entry) !== family || isOpenWeightsGpt(entry)) continue;
|
|
75
|
+
const documentedVersion = versionTuple(entry);
|
|
76
|
+
if (isZeroVersion(documentedVersion)) continue;
|
|
77
|
+
if (!floor || compareVersionTuples(documentedVersion, floor) < 0) {
|
|
78
|
+
floor = documentedVersion;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return floor !== undefined && compareVersionTuples(version, floor) >= 0;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Return whether an identity names a Databricks-hosted foundation model
|
|
86
|
+
* (`databricks-*` endpoint or `system.ai.*` model service). Custom and
|
|
87
|
+
* external endpoints use arbitrary names, so their digits are not versions.
|
|
88
|
+
*/
|
|
89
|
+
export function isFoundationModelIdentity(name: string): boolean {
|
|
90
|
+
return /^(?:databricks-|system\.ai\.)/i.test(name.trim());
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function isOpenWeightsGpt(name: string): boolean {
|
|
94
|
+
return modelFamily(name) === "gpt" && /(?:^|[-_.])oss(?:[-_.]|$)/i.test(name);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function isZeroVersion(version: readonly number[]): boolean {
|
|
98
|
+
return version.every((part) => (part ?? 0) === 0);
|
|
99
|
+
}
|
|
100
|
+
|
|
57
101
|
/** Return the reasoning efforts accepted by a model family. */
|
|
58
102
|
export function modelReasoningEfforts(name: string): ReasoningEffort[] {
|
|
59
103
|
const normalized = name.toLowerCase();
|
|
@@ -129,9 +129,10 @@ interface AliasDocument {
|
|
|
129
129
|
readonly level: ReasoningLevel;
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
-
const aliasDocuments: readonly AliasDocument[] = Object.entries(ALIASES).map(
|
|
133
|
-
|
|
134
|
-
|
|
132
|
+
const aliasDocuments: readonly AliasDocument[] = Object.entries(ALIASES).map(([token, level]) => ({
|
|
133
|
+
token,
|
|
134
|
+
level,
|
|
135
|
+
}));
|
|
135
136
|
|
|
136
137
|
const aliasFuse = new Fuse(aliasDocuments, {
|
|
137
138
|
keys: ["token"],
|
|
@@ -384,7 +385,9 @@ export function adaptRequestReasoning(
|
|
|
384
385
|
}
|
|
385
386
|
}
|
|
386
387
|
|
|
387
|
-
return changed
|
|
388
|
+
return changed
|
|
389
|
+
? { body: next, changed: true, wireEffort: wire }
|
|
390
|
+
: { body, changed: false, wireEffort: wire };
|
|
388
391
|
}
|
|
389
392
|
|
|
390
393
|
/** Normalize labels for alias lookup (`Extra High` → `extra-high`). */
|