@dbx-tools/model 0.3.44 → 0.4.1
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/lib/index.d.ts +13 -0
- package/lib/index.js +14 -0
- package/lib/src/classes.d.ts +54 -0
- package/lib/src/classes.js +70 -0
- package/lib/src/fallback.d.ts +40 -0
- package/lib/src/fallback.js +69 -0
- package/lib/src/invoke.d.ts +86 -0
- package/lib/src/invoke.js +104 -0
- package/lib/src/resolve.d.ts +156 -0
- package/lib/src/resolve.js +227 -0
- package/lib/src/serving.d.ts +130 -0
- package/lib/src/serving.js +286 -0
- package/lib/tsconfig.tsbuildinfo +1 -0
- package/package.json +12 -8
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace-aware model selection.
|
|
3
|
+
*
|
|
4
|
+
* Given a caller's intent - a search string, a capability {@link ModelClass}
|
|
5
|
+
* ceiling, both, or nothing - the toolkit returns matching endpoints ranked by
|
|
6
|
+
* match quality then class, or collapses to the single best id the workspace
|
|
7
|
+
* actually has, degrading from "best in range" down to the static fallback
|
|
8
|
+
* floor. Selection is chat-only: embedding endpoints surface only when
|
|
9
|
+
* `modelClass` is explicitly {@link ModelClass.Embedding}.
|
|
10
|
+
*
|
|
11
|
+
* Two shapes of selection, each in a pure form (over an endpoint list the
|
|
12
|
+
* caller already holds) and an I/O wrapper (that lists `/serving-endpoints`
|
|
13
|
+
* first): ranking, which returns a match- then class-ordered list, and
|
|
14
|
+
* single-selection, which collapses to one id plus how it was reached and
|
|
15
|
+
* layers the operator-pinned fallback / static-floor safety net on top. A chat
|
|
16
|
+
* `modelClass` acts as a ceiling: that band and the less-capable chat bands
|
|
17
|
+
* below it are eligible (see {@link classesAtOrBelow}), so a `chat-balanced`
|
|
18
|
+
* ask can fall to `chat-fast` but never escalate to `chat-thinking`.
|
|
19
|
+
*
|
|
20
|
+
* @module
|
|
21
|
+
*/
|
|
22
|
+
import { model, type ModelQuery, type RankedModel, type ServingEndpointSummary } from "@dbx-tools/shared-model";
|
|
23
|
+
import { type ResolvedModel, type ResolveModelOptions, type WorkspaceClientLike } from "./serving.js";
|
|
24
|
+
type ModelClass = model.ModelClass;
|
|
25
|
+
/** Caller intent passed to {@link resolveModel}. */
|
|
26
|
+
export interface ResolveModelInput {
|
|
27
|
+
/**
|
|
28
|
+
* Explicit model id / loose name (per-request override, agent / plugin
|
|
29
|
+
* default, or env var). When set it wins over `modelClass` and `fallbacks`.
|
|
30
|
+
*/
|
|
31
|
+
explicit?: string;
|
|
32
|
+
/**
|
|
33
|
+
* Fuzzy-match an `explicit` name against the live catalogue so loose names
|
|
34
|
+
* like `"claude sonnet"` resolve. Default `true`. When `false` the explicit
|
|
35
|
+
* input is returned verbatim (Databricks surfaces the canonical 404 if it
|
|
36
|
+
* doesn't exist).
|
|
37
|
+
*/
|
|
38
|
+
fuzzy?: boolean;
|
|
39
|
+
/** Fuse.js threshold forwarded to the fuzzy `search` match ({@link searchServingEndpoints}). */
|
|
40
|
+
threshold?: number;
|
|
41
|
+
/**
|
|
42
|
+
* Chat capability class to resolve when no `explicit` id is given. The live
|
|
43
|
+
* catalogue is classified by its Foundation Model API scores and the top
|
|
44
|
+
* available model in the class (and the chat bands below it) wins, falling
|
|
45
|
+
* back to the class's small static list.
|
|
46
|
+
*/
|
|
47
|
+
modelClass?: ModelClass;
|
|
48
|
+
/**
|
|
49
|
+
* Operator-supplied fallback ids tried *first* in the no-explicit, no-class
|
|
50
|
+
* path (e.g. a regulated workspace pinned to an approved subset), ahead of
|
|
51
|
+
* the auto-classified catalogue.
|
|
52
|
+
*/
|
|
53
|
+
fallbacks?: readonly string[];
|
|
54
|
+
}
|
|
55
|
+
/** Outcome of {@link resolveModel}: the chosen id plus how it was reached. */
|
|
56
|
+
export interface ResolvedModelSelection {
|
|
57
|
+
modelId: string;
|
|
58
|
+
source: "explicit" | "fuzzy-match" | "class" | "fallback";
|
|
59
|
+
}
|
|
60
|
+
/** Intent + catalogue knobs passed to {@link selectModel}. */
|
|
61
|
+
export interface SelectModelInput extends ResolveModelInput {
|
|
62
|
+
/** TTL override for the cached `/serving-endpoints` listing, in ms. */
|
|
63
|
+
ttlMs?: number;
|
|
64
|
+
}
|
|
65
|
+
/** TTL override merged into a {@link ModelQuery} for {@link searchModels}. */
|
|
66
|
+
export interface SearchModelsInput extends ModelQuery {
|
|
67
|
+
/** TTL override for the cached `/serving-endpoints` listing, in ms. */
|
|
68
|
+
ttlMs?: number;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Rank the live catalogue against a {@link ModelQuery}, best-first.
|
|
72
|
+
*
|
|
73
|
+
* Candidates are the classified endpoints in the eligible classes:
|
|
74
|
+
* {@link classesAtOrBelow} the requested `modelClass`, or - when none is given
|
|
75
|
+
* - the chat bands only ({@link CHAT_CLASS_ORDER}), so a general ask never
|
|
76
|
+
* surfaces an embedding endpoint. Each class bucket is already best-first from
|
|
77
|
+
* {@link classify.classifyEndpoints}. Ranking is **match then class**:
|
|
78
|
+
*
|
|
79
|
+
* 1. With a `search`, only endpoints matching it survive, ordered by match
|
|
80
|
+
* distance (bucketed via {@link matchBucket} so near-identical scores tie),
|
|
81
|
+
* then by class (more capable first), then by the stable within-class rank.
|
|
82
|
+
* 2. Without a `search`, the class-then-rank candidate order stands.
|
|
83
|
+
*
|
|
84
|
+
* A `limit` truncates the result. Returns `[]` when nothing is eligible or
|
|
85
|
+
* matches - callers layer their own fallback.
|
|
86
|
+
*/
|
|
87
|
+
export declare function rankModels(endpoints: readonly ServingEndpointSummary[], query?: ModelQuery): RankedModel[];
|
|
88
|
+
/**
|
|
89
|
+
* Collapse {@link rankModels} to a single id: the closest endpoint to `search`
|
|
90
|
+
* in a catalogue snapshot, or the input verbatim when nothing scores within the
|
|
91
|
+
* threshold.
|
|
92
|
+
*
|
|
93
|
+
* The rank-based counterpart to the Fuse-only {@link resolveModelId}: equal
|
|
94
|
+
* match scores are broken by class and then within-class version, so a loose
|
|
95
|
+
* `"opus"` prefers `opus-5` over `opus-4-7` instead of picking whichever
|
|
96
|
+
* sibling Fuse happened to order first. Returning the input unmatched (rather
|
|
97
|
+
* than a near neighbour) is deliberate - a deliberate endpoint id is never
|
|
98
|
+
* silently rewritten, and Databricks surfaces a clean 404.
|
|
99
|
+
*/
|
|
100
|
+
export declare function rankModelId(endpoints: readonly ServingEndpointSummary[], search: string, options?: ResolveModelOptions): ResolvedModel;
|
|
101
|
+
/**
|
|
102
|
+
* {@link rankModelId} against a catalogue the caller may be holding stale:
|
|
103
|
+
* match the loaded snapshot, and on a miss reload once with `force` and match
|
|
104
|
+
* again. That way a model deployed after the catalogue was cached still
|
|
105
|
+
* resolves on first use, without a restart and without giving up caching.
|
|
106
|
+
*
|
|
107
|
+
* The catalogue arrives as a loader rather than a client so the caller keeps
|
|
108
|
+
* ownership of *how* it is cached - {@link listServingEndpoints} and its
|
|
109
|
+
* `CacheManager`, a plain process-lifetime field in a CLI, or a test double.
|
|
110
|
+
* Only one reload is attempted: a genuinely unknown name should fail fast
|
|
111
|
+
* rather than re-list on every request.
|
|
112
|
+
*
|
|
113
|
+
* @param load - Returns the catalogue; `force` asks it to bypass its cache.
|
|
114
|
+
*/
|
|
115
|
+
export declare function rankModelIdLive(load: (force: boolean) => Promise<readonly ServingEndpointSummary[]>, search: string, options?: ResolveModelOptions): Promise<ResolvedModel>;
|
|
116
|
+
/**
|
|
117
|
+
* Rank a workspace's catalogue in one call: list its `/serving-endpoints`
|
|
118
|
+
* (cached) and run {@link rankModels} over the result. The list counterpart to
|
|
119
|
+
* {@link selectModel}, for a consumer that wants the full ranked set (a model
|
|
120
|
+
* picker, a CLI) rather than a single id. Catalogue fetches fail loud: network
|
|
121
|
+
* / auth errors propagate so the caller sees the real SDK message.
|
|
122
|
+
*
|
|
123
|
+
* @param host - Workspace host used as the cache key. Pass the value resolved
|
|
124
|
+
* from `client.config.getHost()`.
|
|
125
|
+
*/
|
|
126
|
+
export declare function searchModels(client: WorkspaceClientLike, host: string, input?: SearchModelsInput): Promise<RankedModel[]>;
|
|
127
|
+
/**
|
|
128
|
+
* Resolve a model id for a workspace in one call: list its `/serving-endpoints`
|
|
129
|
+
* (cached) and run {@link resolveModel} over the result. This is the entry
|
|
130
|
+
* point for any consumer that holds a `WorkspaceClient` and just wants a usable
|
|
131
|
+
* model name - a Lakeflow job, a one-off script, or the Mastra plugin alike.
|
|
132
|
+
*
|
|
133
|
+
* Cheap exit: when an `explicit` name is given and `fuzzy` is off, the
|
|
134
|
+
* catalogue is never fetched - the name is returned verbatim and Databricks
|
|
135
|
+
* surfaces the canonical 404 if it doesn't exist. Catalogue fetches otherwise
|
|
136
|
+
* fail loud: network / auth errors propagate so the caller sees the real SDK
|
|
137
|
+
* message instead of a silent fallback.
|
|
138
|
+
*
|
|
139
|
+
* @param host - Workspace host used as the cache key. Pass the value resolved
|
|
140
|
+
* from `client.config.getHost()`.
|
|
141
|
+
*/
|
|
142
|
+
export declare function selectModel(client: WorkspaceClientLike, host: string, input?: SelectModelInput): Promise<ResolvedModelSelection>;
|
|
143
|
+
/**
|
|
144
|
+
* Resolve a single model id from the live catalogue and caller intent,
|
|
145
|
+
* delegating the live selection to {@link rankModels} with `limit: 1`.
|
|
146
|
+
*
|
|
147
|
+
* 1. **Explicit ask**: with `fuzzy` off, returned verbatim; otherwise
|
|
148
|
+
* fuzzy-ranked within the (optional) class ceiling and the best taken,
|
|
149
|
+
* falling back to the input verbatim when nothing matches.
|
|
150
|
+
* 2. **No explicit ask**: an operator-pinned `fallback` that exists in the live
|
|
151
|
+
* catalogue wins first; then the ranked live catalogue (class ceiling
|
|
152
|
+
* applied); then the static {@link FALLBACK_MODEL_IDS} floor when the
|
|
153
|
+
* catalogue yields nothing in range.
|
|
154
|
+
*/
|
|
155
|
+
export declare function resolveModel(endpoints: readonly ServingEndpointSummary[], input?: ResolveModelInput): ResolvedModelSelection;
|
|
156
|
+
export {};
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace-aware model selection.
|
|
3
|
+
*
|
|
4
|
+
* Given a caller's intent - a search string, a capability {@link ModelClass}
|
|
5
|
+
* ceiling, both, or nothing - the toolkit returns matching endpoints ranked by
|
|
6
|
+
* match quality then class, or collapses to the single best id the workspace
|
|
7
|
+
* actually has, degrading from "best in range" down to the static fallback
|
|
8
|
+
* floor. Selection is chat-only: embedding endpoints surface only when
|
|
9
|
+
* `modelClass` is explicitly {@link ModelClass.Embedding}.
|
|
10
|
+
*
|
|
11
|
+
* Two shapes of selection, each in a pure form (over an endpoint list the
|
|
12
|
+
* caller already holds) and an I/O wrapper (that lists `/serving-endpoints`
|
|
13
|
+
* first): ranking, which returns a match- then class-ordered list, and
|
|
14
|
+
* single-selection, which collapses to one id plus how it was reached and
|
|
15
|
+
* layers the operator-pinned fallback / static-floor safety net on top. A chat
|
|
16
|
+
* `modelClass` acts as a ceiling: that band and the less-capable chat bands
|
|
17
|
+
* below it are eligible (see {@link classesAtOrBelow}), so a `chat-balanced`
|
|
18
|
+
* ask can fall to `chat-fast` but never escalate to `chat-thinking`.
|
|
19
|
+
*
|
|
20
|
+
* @module
|
|
21
|
+
*/
|
|
22
|
+
import { object } from "@dbx-tools/shared-core";
|
|
23
|
+
import { classify, } from "@dbx-tools/shared-model";
|
|
24
|
+
import { CHAT_CLASS_ORDER, classesAtOrBelow, MODEL_CLASS_ORDER } from "./classes.js";
|
|
25
|
+
import { FALLBACK_MODEL_IDS, modelsForClass } from "./fallback.js";
|
|
26
|
+
import { listServingEndpoints, searchServingEndpoints, } from "./serving.js";
|
|
27
|
+
/**
|
|
28
|
+
* Round a Fuse score to the display precision so version siblings that match a
|
|
29
|
+
* token identically (e.g. `opus-4-7` vs `opus-4-8` for the query `"opus"`) tie
|
|
30
|
+
* on match and let the class / within-class rank decide - which is what
|
|
31
|
+
* surfaces the newer, higher-quality sibling.
|
|
32
|
+
*/
|
|
33
|
+
function matchBucket(score) {
|
|
34
|
+
return Math.round((score ?? 0) * 1000);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Rank the live catalogue against a {@link ModelQuery}, best-first.
|
|
38
|
+
*
|
|
39
|
+
* Candidates are the classified endpoints in the eligible classes:
|
|
40
|
+
* {@link classesAtOrBelow} the requested `modelClass`, or - when none is given
|
|
41
|
+
* - the chat bands only ({@link CHAT_CLASS_ORDER}), so a general ask never
|
|
42
|
+
* surfaces an embedding endpoint. Each class bucket is already best-first from
|
|
43
|
+
* {@link classify.classifyEndpoints}. Ranking is **match then class**:
|
|
44
|
+
*
|
|
45
|
+
* 1. With a `search`, only endpoints matching it survive, ordered by match
|
|
46
|
+
* distance (bucketed via {@link matchBucket} so near-identical scores tie),
|
|
47
|
+
* then by class (more capable first), then by the stable within-class rank.
|
|
48
|
+
* 2. Without a `search`, the class-then-rank candidate order stands.
|
|
49
|
+
*
|
|
50
|
+
* A `limit` truncates the result. Returns `[]` when nothing is eligible or
|
|
51
|
+
* matches - callers layer their own fallback.
|
|
52
|
+
*/
|
|
53
|
+
export function rankModels(endpoints, query = {}) {
|
|
54
|
+
const classified = classify.classifyEndpoints(endpoints);
|
|
55
|
+
const eligible = query.modelClass !== undefined ? classesAtOrBelow(query.modelClass) : CHAT_CLASS_ORDER;
|
|
56
|
+
// Flatten eligible classes in capability order, carrying each endpoint's
|
|
57
|
+
// class; bucket order is already best-first.
|
|
58
|
+
const candidates = [];
|
|
59
|
+
for (const modelClass of eligible) {
|
|
60
|
+
for (const endpoint of classified[modelClass])
|
|
61
|
+
candidates.push({ endpoint, modelClass });
|
|
62
|
+
}
|
|
63
|
+
const search = query.search?.trim();
|
|
64
|
+
let ranked;
|
|
65
|
+
if (search) {
|
|
66
|
+
const scores = new Map();
|
|
67
|
+
for (const match of searchServingEndpoints(search, candidates.map((c) => c.endpoint), query.threshold !== undefined ? { threshold: query.threshold } : {})) {
|
|
68
|
+
scores.set(match.endpoint.name, match.score);
|
|
69
|
+
}
|
|
70
|
+
// `Array.prototype.sort` is stable, so endpoints equal on match and class
|
|
71
|
+
// keep their best-first within-class order.
|
|
72
|
+
ranked = candidates
|
|
73
|
+
.filter((c) => scores.has(c.endpoint.name))
|
|
74
|
+
.map((c) => ({ ...c, score: scores.get(c.endpoint.name) }))
|
|
75
|
+
.sort((a, b) => {
|
|
76
|
+
const byMatch = matchBucket(a.score) - matchBucket(b.score);
|
|
77
|
+
if (byMatch !== 0)
|
|
78
|
+
return byMatch;
|
|
79
|
+
return MODEL_CLASS_ORDER.indexOf(a.modelClass) - MODEL_CLASS_ORDER.indexOf(b.modelClass);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
ranked = candidates;
|
|
84
|
+
}
|
|
85
|
+
return query.limit !== undefined ? ranked.slice(0, Math.max(0, query.limit)) : ranked;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Collapse {@link rankModels} to a single id: the closest endpoint to `search`
|
|
89
|
+
* in a catalogue snapshot, or the input verbatim when nothing scores within the
|
|
90
|
+
* threshold.
|
|
91
|
+
*
|
|
92
|
+
* The rank-based counterpart to the Fuse-only {@link resolveModelId}: equal
|
|
93
|
+
* match scores are broken by class and then within-class version, so a loose
|
|
94
|
+
* `"opus"` prefers `opus-5` over `opus-4-7` instead of picking whichever
|
|
95
|
+
* sibling Fuse happened to order first. Returning the input unmatched (rather
|
|
96
|
+
* than a near neighbour) is deliberate - a deliberate endpoint id is never
|
|
97
|
+
* silently rewritten, and Databricks surfaces a clean 404.
|
|
98
|
+
*/
|
|
99
|
+
export function rankModelId(endpoints, search, options = {}) {
|
|
100
|
+
const [top] = rankModels(endpoints, {
|
|
101
|
+
search,
|
|
102
|
+
limit: 1,
|
|
103
|
+
...(options.threshold !== undefined ? { threshold: options.threshold } : {}),
|
|
104
|
+
});
|
|
105
|
+
if (!top)
|
|
106
|
+
return { modelId: search, matched: false };
|
|
107
|
+
return { modelId: top.endpoint.name, matched: true, score: top.score };
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* {@link rankModelId} against a catalogue the caller may be holding stale:
|
|
111
|
+
* match the loaded snapshot, and on a miss reload once with `force` and match
|
|
112
|
+
* again. That way a model deployed after the catalogue was cached still
|
|
113
|
+
* resolves on first use, without a restart and without giving up caching.
|
|
114
|
+
*
|
|
115
|
+
* The catalogue arrives as a loader rather than a client so the caller keeps
|
|
116
|
+
* ownership of *how* it is cached - {@link listServingEndpoints} and its
|
|
117
|
+
* `CacheManager`, a plain process-lifetime field in a CLI, or a test double.
|
|
118
|
+
* Only one reload is attempted: a genuinely unknown name should fail fast
|
|
119
|
+
* rather than re-list on every request.
|
|
120
|
+
*
|
|
121
|
+
* @param load - Returns the catalogue; `force` asks it to bypass its cache.
|
|
122
|
+
*/
|
|
123
|
+
export async function rankModelIdLive(load, search, options = {}) {
|
|
124
|
+
const resolved = rankModelId(await load(false), search, options);
|
|
125
|
+
if (resolved.matched)
|
|
126
|
+
return resolved;
|
|
127
|
+
return rankModelId(await load(true), search, options);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Rank a workspace's catalogue in one call: list its `/serving-endpoints`
|
|
131
|
+
* (cached) and run {@link rankModels} over the result. The list counterpart to
|
|
132
|
+
* {@link selectModel}, for a consumer that wants the full ranked set (a model
|
|
133
|
+
* picker, a CLI) rather than a single id. Catalogue fetches fail loud: network
|
|
134
|
+
* / auth errors propagate so the caller sees the real SDK message.
|
|
135
|
+
*
|
|
136
|
+
* @param host - Workspace host used as the cache key. Pass the value resolved
|
|
137
|
+
* from `client.config.getHost()`.
|
|
138
|
+
*/
|
|
139
|
+
export async function searchModels(client, host, input = {}) {
|
|
140
|
+
const endpoints = await listServingEndpoints(client, host, input.ttlMs !== undefined ? { ttlMs: input.ttlMs } : {});
|
|
141
|
+
return rankModels(endpoints, input);
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Resolve a model id for a workspace in one call: list its `/serving-endpoints`
|
|
145
|
+
* (cached) and run {@link resolveModel} over the result. This is the entry
|
|
146
|
+
* point for any consumer that holds a `WorkspaceClient` and just wants a usable
|
|
147
|
+
* model name - a Lakeflow job, a one-off script, or the Mastra plugin alike.
|
|
148
|
+
*
|
|
149
|
+
* Cheap exit: when an `explicit` name is given and `fuzzy` is off, the
|
|
150
|
+
* catalogue is never fetched - the name is returned verbatim and Databricks
|
|
151
|
+
* surfaces the canonical 404 if it doesn't exist. Catalogue fetches otherwise
|
|
152
|
+
* fail loud: network / auth errors propagate so the caller sees the real SDK
|
|
153
|
+
* message instead of a silent fallback.
|
|
154
|
+
*
|
|
155
|
+
* @param host - Workspace host used as the cache key. Pass the value resolved
|
|
156
|
+
* from `client.config.getHost()`.
|
|
157
|
+
*/
|
|
158
|
+
export async function selectModel(client, host, input = {}) {
|
|
159
|
+
if (input.explicit !== undefined && input.fuzzy === false) {
|
|
160
|
+
return { modelId: input.explicit, source: "explicit" };
|
|
161
|
+
}
|
|
162
|
+
const endpoints = await listServingEndpoints(client, host, {
|
|
163
|
+
...(input.ttlMs !== undefined ? { ttlMs: input.ttlMs } : {}),
|
|
164
|
+
});
|
|
165
|
+
return resolveModel(endpoints, input);
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Resolve a single model id from the live catalogue and caller intent,
|
|
169
|
+
* delegating the live selection to {@link rankModels} with `limit: 1`.
|
|
170
|
+
*
|
|
171
|
+
* 1. **Explicit ask**: with `fuzzy` off, returned verbatim; otherwise
|
|
172
|
+
* fuzzy-ranked within the (optional) class ceiling and the best taken,
|
|
173
|
+
* falling back to the input verbatim when nothing matches.
|
|
174
|
+
* 2. **No explicit ask**: an operator-pinned `fallback` that exists in the live
|
|
175
|
+
* catalogue wins first; then the ranked live catalogue (class ceiling
|
|
176
|
+
* applied); then the static {@link FALLBACK_MODEL_IDS} floor when the
|
|
177
|
+
* catalogue yields nothing in range.
|
|
178
|
+
*/
|
|
179
|
+
export function resolveModel(endpoints, input = {}) {
|
|
180
|
+
if (input.explicit !== undefined) {
|
|
181
|
+
if (input.fuzzy === false) {
|
|
182
|
+
return { modelId: input.explicit, source: "explicit" };
|
|
183
|
+
}
|
|
184
|
+
const [top] = rankModels(endpoints, buildQuery(input, input.explicit));
|
|
185
|
+
return { modelId: top?.endpoint.name ?? input.explicit, source: "fuzzy-match" };
|
|
186
|
+
}
|
|
187
|
+
// Operator-pinned fallbacks win when present and live (e.g. a regulated
|
|
188
|
+
// workspace restricted to an approved subset).
|
|
189
|
+
if (input.modelClass === undefined && input.fallbacks && input.fallbacks.length > 0) {
|
|
190
|
+
const present = new Set(endpoints.map((e) => e.name));
|
|
191
|
+
const pinned = input.fallbacks.find((id) => present.has(id));
|
|
192
|
+
if (pinned)
|
|
193
|
+
return { modelId: pinned, source: "fallback" };
|
|
194
|
+
}
|
|
195
|
+
const source = input.modelClass !== undefined ? "class" : "fallback";
|
|
196
|
+
const [top] = rankModels(endpoints, buildQuery(input, undefined));
|
|
197
|
+
if (top)
|
|
198
|
+
return { modelId: top.endpoint.name, source };
|
|
199
|
+
// Live catalogue yielded nothing in range: walk the static floor.
|
|
200
|
+
const floorSource = input.modelClass !== undefined ? modelsForClass(input.modelClass) : (input.fallbacks ?? []);
|
|
201
|
+
const floor = object.sequence(floorSource).concat(FALLBACK_MODEL_IDS).distinct().toArray();
|
|
202
|
+
return { modelId: pickFirstAvailable(floor, endpoints), source };
|
|
203
|
+
}
|
|
204
|
+
/** Build a {@link ModelQuery} from {@link ResolveModelInput} for the `limit: 1` delegation. */
|
|
205
|
+
function buildQuery(input, search) {
|
|
206
|
+
return {
|
|
207
|
+
...(search !== undefined ? { search } : {}),
|
|
208
|
+
...(input.modelClass !== undefined ? { modelClass: input.modelClass } : {}),
|
|
209
|
+
...(input.threshold !== undefined ? { threshold: input.threshold } : {}),
|
|
210
|
+
limit: 1,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Find the first id in `candidates` whose endpoint is present in `endpoints`.
|
|
215
|
+
* Returns the top candidate when the workspace has none of them so callers
|
|
216
|
+
* always get a string; an offline workspace then receives a clean 404 from
|
|
217
|
+
* Databricks instead of a malformed config.
|
|
218
|
+
*/
|
|
219
|
+
function pickFirstAvailable(candidates, endpoints) {
|
|
220
|
+
const present = new Set(endpoints.map((e) => e.name));
|
|
221
|
+
for (const candidate of candidates) {
|
|
222
|
+
if (present.has(candidate))
|
|
223
|
+
return candidate;
|
|
224
|
+
}
|
|
225
|
+
return candidates[0] ?? FALLBACK_MODEL_IDS[0];
|
|
226
|
+
}
|
|
227
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVzb2x2ZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9yZXNvbHZlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQW9CRztBQUVILE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSx3QkFBd0IsQ0FBQztBQUNoRCxPQUFPLEVBQ0wsUUFBUSxHQUtULE1BQU0seUJBQXlCLENBQUM7QUFFakMsT0FBTyxFQUFFLGdCQUFnQixFQUFFLGdCQUFnQixFQUFFLGlCQUFpQixFQUFFLE1BQU0sV0FBVyxDQUFDO0FBQ2xGLE9BQU8sRUFBRSxrQkFBa0IsRUFBRSxjQUFjLEVBQUUsTUFBTSxZQUFZLENBQUM7QUFDaEUsT0FBTyxFQUNMLG9CQUFvQixFQUNwQixzQkFBc0IsR0FJdkIsTUFBTSxXQUFXLENBQUM7QUFxRG5COzs7OztHQUtHO0FBQ0gsU0FBUyxXQUFXLENBQUMsS0FBeUI7SUFDNUMsT0FBTyxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUMsS0FBSyxJQUFJLENBQUMsQ0FBQyxHQUFHLElBQUksQ0FBQyxDQUFDO0FBQ3pDLENBQUM7QUFFRDs7Ozs7Ozs7Ozs7Ozs7OztHQWdCRztBQUNILE1BQU0sVUFBVSxVQUFVLENBQ3hCLFNBQTRDLEVBQzVDLFFBQW9CLEVBQUU7SUFFdEIsTUFBTSxVQUFVLEdBQUcsUUFBUSxDQUFDLGlCQUFpQixDQUFDLFNBQVMsQ0FBQyxDQUFDO0lBQ3pELE1BQU0sUUFBUSxHQUNaLEtBQUssQ0FBQyxVQUFVLEtBQUssU0FBUyxDQUFDLENBQUMsQ0FBQyxnQkFBZ0IsQ0FBQyxLQUFLLENBQUMsVUFBVSxDQUFDLENBQUMsQ0FBQyxDQUFDLGdCQUFnQixDQUFDO0lBRXpGLHlFQUF5RTtJQUN6RSw2Q0FBNkM7SUFDN0MsTUFBTSxVQUFVLEdBQWtCLEVBQUUsQ0FBQztJQUNyQyxLQUFLLE1BQU0sVUFBVSxJQUFJLFFBQVEsRUFBRSxDQUFDO1FBQ2xDLEtBQUssTUFBTSxRQUFR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LENBQUM7QUFDbkUsQ0FBQztBQUVELCtGQUErRjtBQUMvRixTQUFTLFVBQVUsQ0FBQyxLQUF3QixFQUFFLE1BQTBCO0lBQ3RFLE9BQU87UUFDTCxHQUFHLENBQUMsTUFBTSxLQUFLLFNBQVMsQ0FBQyxDQUFDLENBQUMsRUFBRSxNQUFNLEVBQUUsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDO1FBQzNDLEdBQUcsQ0FBQyxLQUFLLENBQUMsVUFBVSxLQUFLLFNBQVMsQ0FBQyxDQUFDLENBQUMsRUFBRSxVQUFVLEVBQUUsS0FBSyxDQUFDLFVBQVUsRUFBRSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUM7UUFDM0UsR0FBRyxDQUFDLEtBQUssQ0FBQyxTQUFTLEtBQUssU0FBUyxDQUFDLENBQUMsQ0FBQyxFQUFFLFNBQVMsRUFBRSxLQUFLLENBQUMsU0FBUyxFQUFFLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQztRQUN4RSxLQUFLLEVBQUUsQ0FBQztLQUNULENBQUM7QUFDSixDQUFDO0FBRUQ7Ozs7O0dBS0c7QUFDSCxTQUFTLGtCQUFrQixDQUN6QixVQUE2QixFQUM3QixTQUE0QztJQUU1QyxNQUFNLE9BQU8sR0FBRyxJQUFJLEdBQUcsQ0FBQyxTQUFTLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQztJQUN0RCxLQUFLLE1BQU0sU0FBUyxJQUFJLFVBQVUsRUFBRSxDQUFDO1FBQ25DLElBQUksT0FBTyxDQUFDLEdBQUcsQ0FBQyxTQUFTLENBQUM7WUFBRSxPQUFPLFNBQVMsQ0FBQztJQUMvQyxDQUFDO0lBQ0QsT0FBTyxVQUFVLENBQUMsQ0FBQyxDQUFDLElBQUksa0JBQWtCLENBQUMsQ0FBQyxDQUFFLENBQUM7QUFDakQsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogV29ya3NwYWNlLWF3YXJlIG1vZGVsIHNlbGVjdGlvbi5cbiAqXG4gKiBHaXZlbiBhIGNhbGxlcidzIGludGVudCAtIGEgc2VhcmNoIHN0cmluZywgYSBjYXBhYmlsaXR5IHtAbGluayBNb2RlbENsYXNzfVxuICogY2VpbGluZywgYm90aCwgb3Igbm90aGluZyAtIHRoZSB0b29sa2l0IHJldHVybnMgbWF0Y2hpbmcgZW5kcG9pbnRzIHJhbmtlZCBieVxuICogbWF0Y2ggcXVhbGl0eSB0aGVuIGNsYXNzLCBvciBjb2xsYXBzZXMgdG8gdGhlIHNpbmdsZSBiZXN0IGlkIHRoZSB3b3Jrc3BhY2VcbiAqIGFjdHVhbGx5IGhhcywgZGVncmFkaW5nIGZyb20gXCJiZXN0IGluIHJhbmdlXCIgZG93biB0byB0aGUgc3RhdGljIGZhbGxiYWNrXG4gKiBmbG9vci4gU2VsZWN0aW9uIGlzIGNoYXQtb25seTogZW1iZWRkaW5nIGVuZHBvaW50cyBzdXJmYWNlIG9ubHkgd2hlblxuICogYG1vZGVsQ2xhc3NgIGlzIGV4cGxpY2l0bHkge0BsaW5rIE1vZGVsQ2xhc3MuRW1iZWRkaW5nfS5cbiAqXG4gKiBUd28gc2hhcGVzIG9mIHNlbGVjdGlvbiwgZWFjaCBpbiBhIHB1cmUgZm9ybSAob3ZlciBhbiBlbmRwb2ludCBsaXN0IHRoZVxuICogY2FsbGVyIGFscmVhZHkgaG9sZHMpIGFuZCBhbiBJL08gd3JhcHBlciAodGhhdCBsaXN0cyBgL3NlcnZpbmctZW5kcG9pbnRzYFxuICogZmlyc3QpOiByYW5raW5nLCB3aGljaCByZXR1cm5zIGEgbWF0Y2gtIHRoZW4gY2xhc3Mtb3JkZXJlZCBsaXN0LCBhbmRcbiAqIHNpbmdsZS1zZWxlY3Rpb24sIHdoaWNoIGNvbGxhcHNlcyB0byBvbmUgaWQgcGx1cyBob3cgaXQgd2FzIHJlYWNoZWQgYW5kXG4gKiBsYXllcnMgdGhlIG9wZXJhdG9yLXBpbm5lZCBmYWxsYmFjayAvIHN0YXRpYy1mbG9vciBzYWZldHkgbmV0IG9uIHRvcC4gQSBjaGF0XG4gKiBgbW9kZWxDbGFzc2AgYWN0cyBhcyBhIGNlaWxpbmc6IHRoYXQgYmFuZCBhbmQgdGhlIGxlc3MtY2FwYWJsZSBjaGF0IGJhbmRzXG4gKiBiZWxvdyBpdCBhcmUgZWxpZ2libGUgKHNlZSB7QGxpbmsgY2xhc3Nlc0F0T3JCZWxvd30pLCBzbyBhIGBjaGF0LWJhbGFuY2VkYFxuICogYXNrIGNhbiBmYWxsIHRvIGBjaGF0LWZhc3RgIGJ1dCBuZXZlciBlc2NhbGF0ZSB0byBgY2hhdC10aGlua2luZ2AuXG4gKlxuICogQG1vZHVsZVxuICovXG5cbmltcG9ydCB7IG9iamVjdCB9IGZyb20gXCJAZGJ4LXRvb2xzL3NoYXJlZC1jb3JlXCI7XG5pbXBvcnQge1xuICBjbGFzc2lmeSxcbiAgbW9kZWwsXG4gIHR5cGUgTW9kZWxRdWVyeSxcbiAgdHlwZSBSYW5rZWRNb2RlbCxcbiAgdHlwZSBTZXJ2aW5nRW5kcG9pbnRTdW1tYXJ5LFxufSBmcm9tIFwiQGRieC10b29scy9zaGFyZWQtbW9kZWxcIjtcblxuaW1wb3J0IHsgQ0hBVF9DTEFTU19PUkRFUiwgY2xhc3Nlc0F0T3JCZWxvdywgTU9ERUxfQ0xBU1NfT1JERVIgfSBmcm9tIFwiLi9jbGFzc2VzXCI7XG5pbXBvcnQgeyBGQUxMQkFDS19NT0RFTF9JRFMsIG1vZGVsc0ZvckNsYXNzIH0gZnJvbSBcIi4vZmFsbGJhY2tcIjtcbmltcG9ydCB7XG4gIGxpc3RTZXJ2aW5nRW5kcG9pbnRzLFxuICBzZWFyY2hTZXJ2aW5nRW5kcG9pbnRzLFxuICB0eXBlIFJlc29sdmVkTW9kZWwsXG4gIHR5cGUgUmVzb2x2ZU1vZGVsT3B0aW9ucyxcbiAgdHlwZSBXb3Jrc3BhY2VDbGllbnRMaWtlLFxufSBmcm9tIFwiLi9zZXJ2aW5nXCI7XG5cbnR5cGUgTW9kZWxDbGFzcyA9IG1vZGVsLk1vZGVsQ2xhc3M7XG5cbi8qKiBDYWxsZXIgaW50ZW50IHBhc3NlZCB0byB7QGxpbmsgcmVzb2x2ZU1vZGVsfS4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgUmVzb2x2ZU1vZGVsSW5wdXQge1xuICAvKipcbiAgICogRXhwbGljaXQgbW9kZWwgaWQgLyBsb29zZSBuYW1lIChwZXItcmVxdWVzdCBvdmVycmlkZSwgYWdlbnQgLyBwbHVnaW5cbiAgICogZGVmYXVsdCwgb3IgZW52IHZhcikuIFdoZW4gc2V0IGl0IHdpbnMgb3ZlciBgbW9kZWxDbGFzc2AgYW5kIGBmYWxsYmFja3NgLlxuICAgKi9cbiAgZXhwbGljaXQ/OiBzdHJpbmc7XG4gIC8qKlxuICAgKiBGdXp6eS1tYXRjaCBhbiBgZXhwbGljaXRgIG5hbWUgYWdhaW5zdCB0aGUgbGl2ZSBjYXRhbG9ndWUgc28gbG9vc2UgbmFtZXNcbiAgICogbGlrZSBgXCJjbGF1ZGUgc29ubmV0XCJgIHJlc29sdmUuIERlZmF1bHQgYHRydWVgLiBXaGVuIGBmYWxzZWAgdGhlIGV4cGxpY2l0XG4gICAqIGlucHV0IGlzIHJldHVybmVkIHZlcmJhdGltIChEYXRhYnJpY2tzIHN1cmZhY2VzIHRoZSBjYW5vbmljYWwgNDA0IGlmIGl0XG4gICAqIGRvZXNuJ3QgZXhpc3QpLlxuICAgKi9cbiAgZnV6enk/OiBib29sZWFuO1xuICAvKiogRnVzZS5qcyB0aHJlc2hvbGQgZm9yd2FyZGVkIHRvIHRoZSBmdXp6eSBgc2VhcmNoYCBtYXRjaCAoe0BsaW5rIHNlYXJjaFNlcnZpbmdFbmRwb2ludHN9KS4gKi9cbiAgdGhyZXNob2xkPzogbnVtYmVyO1xuICAvKipcbiAgICogQ2hhdCBjYXBhYmlsaXR5IGNsYXNzIHRvIHJlc29sdmUgd2hlbiBubyBgZXhwbGljaXRgIGlkIGlzIGdpdmVuLiBUaGUgbGl2ZVxuICAgKiBjYXRhbG9ndWUgaXMgY2xhc3NpZmllZCBieSBpdHMgRm91bmRhdGlvbiBNb2RlbCBBUEkgc2NvcmVzIGFuZCB0aGUgdG9wXG4gICAqIGF2YWlsYWJsZSBtb2RlbCBpbiB0aGUgY2xhc3MgKGFuZCB0aGUgY2hhdCBiYW5kcyBiZWxvdyBpdCkgd2lucywgZmFsbGluZ1xuICAgKiBiYWNrIHRvIHRoZSBjbGFzcydzIHNtYWxsIHN0YXRpYyBsaXN0LlxuICAgKi9cbiAgbW9kZWxDbGFzcz86IE1vZGVsQ2xhc3M7XG4gIC8qKlxuICAgKiBPcGVyYXRvci1zdXBwbGllZCBmYWxsYmFjayBpZHMgdHJpZWQgKmZpcnN0KiBpbiB0aGUgbm8tZXhwbGljaXQsIG5vLWNsYXNzXG4gICAqIHBhdGggKGUuZy4gYSByZWd1bGF0ZWQgd29ya3NwYWNlIHBpbm5lZCB0byBhbiBhcHByb3ZlZCBzdWJzZXQpLCBhaGVhZCBvZlxuICAgKiB0aGUgYXV0by1jbGFzc2lmaWVkIGNhdGFsb2d1ZS5cbiAgICovXG4gIGZhbGxiYWNrcz86IHJlYWRvbmx5IHN0cmluZ1tdO1xufVxuXG4vKiogT3V0Y29tZSBvZiB7QGxpbmsgcmVzb2x2ZU1vZGVsfTogdGhlIGNob3NlbiBpZCBwbHVzIGhvdyBpdCB3YXMgcmVhY2hlZC4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgUmVzb2x2ZWRNb2RlbFNlbGVjdGlvbiB7XG4gIG1vZGVsSWQ6IHN0cmluZztcbiAgc291cmNlOiBcImV4cGxpY2l0XCIgfCBcImZ1enp5LW1hdGNoXCIgfCBcImNsYXNzXCIgfCBcImZhbGxiYWNrXCI7XG59XG5cbi8qKiBJbnRlbnQgKyBjYXRhbG9ndWUga25vYnMgcGFzc2VkIHRvIHtAbGluayBzZWxlY3RNb2RlbH0uICovXG5leHBvcnQgaW50ZXJmYWNlIFNlbGVjdE1vZGVsSW5wdXQgZXh0ZW5kcyBSZXNvbHZlTW9kZWxJbnB1dCB7XG4gIC8qKiBUVEwgb3ZlcnJpZGUgZm9yIHRoZSBjYWNoZWQgYC9zZXJ2aW5nLWVuZHBvaW50c2AgbGlzdGluZywgaW4gbXMuICovXG4gIHR0bE1zPzogbnVtYmVyO1xufVxuXG4vKiogVFRMIG92ZXJyaWRlIG1lcmdlZCBpbnRvIGEge0BsaW5rIE1vZGVsUXVlcnl9IGZvciB7QGxpbmsgc2VhcmNoTW9kZWxzfS4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgU2VhcmNoTW9kZWxzSW5wdXQgZXh0ZW5kcyBNb2RlbFF1ZXJ5IHtcbiAgLyoqIFRUTCBvdmVycmlkZSBmb3IgdGhlIGNhY2hlZCBgL3NlcnZpbmctZW5kcG9pbnRzYCBsaXN0aW5nLCBpbiBtcy4gKi9cbiAgdHRsTXM/OiBudW1iZXI7XG59XG5cbi8qKlxuICogUm91bmQgYSBGdXNlIHNjb3JlIHRvIHRoZSBkaXNwbGF5IHByZWNpc2lvbiBzbyB2ZXJzaW9uIHNpYmxpbmdzIHRoYXQgbWF0Y2ggYVxuICogdG9rZW4gaWRlbnRpY2FsbHkgKGUuZy4gYG9wdXMtNC03YCB2cyBgb3B1cy00LThgIGZvciB0aGUgcXVlcnkgYFwib3B1c1wiYCkgdGllXG4gKiBvbiBtYXRjaCBhbmQgbGV0IHRoZSBjbGFzcyAvIHdpdGhpbi1jbGFzcyByYW5rIGRlY2lkZSAtIHdoaWNoIGlzIHdoYXRcbiAqIHN1cmZhY2VzIHRoZSBuZXdlciwgaGlnaGVyLXF1YWxpdHkgc2libGluZy5cbiAqL1xuZnVuY3Rpb24gbWF0Y2hCdWNrZXQoc2NvcmU6IG51bWJlciB8IHVuZGVmaW5lZCk6IG51bWJlciB7XG4gIHJldHVybiBNYXRoLnJvdW5kKChzY29yZSA/PyAwKSAqIDEwMDApO1xufVxuXG4vKipcbiAqIFJhbmsgdGhlIGxpdmUgY2F0YWxvZ3VlIGFnYWluc3QgYSB7QGxpbmsgTW9kZWxRdWVyeX0sIGJlc3QtZmlyc3QuXG4gKlxuICogQ2FuZGlkYXRlcyBhcmUgdGhlIGNsYXNzaWZpZWQgZW5kcG9pbnRzIGluIHRoZSBlbGlnaWJsZSBjbGFzc2VzOlxuICoge0BsaW5rIGNsYXNzZXNBdE9yQmVsb3d9IHRoZSByZXF1ZXN0ZWQgYG1vZGVsQ2xhc3NgLCBvciAtIHdoZW4gbm9uZSBpcyBnaXZlblxuICogLSB0aGUgY2hhdCBiYW5kcyBvbmx5ICh7QGxpbmsgQ0hBVF9DTEFTU19PUkRFUn0pLCBzbyBhIGdlbmVyYWwgYXNrIG5ldmVyXG4gKiBzdXJmYWNlcyBhbiBlbWJlZGRpbmcgZW5kcG9pbnQuIEVhY2ggY2xhc3MgYnVja2V0IGlzIGFscmVhZHkgYmVzdC1maXJzdCBmcm9tXG4gKiB7QGxpbmsgY2xhc3NpZnkuY2xhc3NpZnlFbmRwb2ludHN9LiBSYW5raW5nIGlzICoqbWF0Y2ggdGhlbiBjbGFzcyoqOlxuICpcbiAqIDEuIFdpdGggYSBgc2VhcmNoYCwgb25seSBlbmRwb2ludHMgbWF0Y2hpbmcgaXQgc3Vydml2ZSwgb3JkZXJlZCBieSBtYXRjaFxuICogICAgZGlzdGFuY2UgKGJ1Y2tldGVkIHZpYSB7QGxpbmsgbWF0Y2hCdWNrZXR9IHNvIG5lYXItaWRlbnRpY2FsIHNjb3JlcyB0aWUpLFxuICogICAgdGhlbiBieSBjbGFzcyAobW9yZSBjYXBhYmxlIGZpcnN0KSwgdGhlbiBieSB0aGUgc3RhYmxlIHdpdGhpbi1jbGFzcyByYW5rLlxuICogMi4gV2l0aG91dCBhIGBzZWFyY2hgLCB0aGUgY2xhc3MtdGhlbi1yYW5rIGNhbmRpZGF0ZSBvcmRlciBzdGFuZHMuXG4gKlxuICogQSBgbGltaXRgIHRydW5jYXRlcyB0aGUgcmVzdWx0LiBSZXR1cm5zIGBbXWAgd2hlbiBub3RoaW5nIGlzIGVsaWdpYmxlIG9yXG4gKiBtYXRjaGVzIC0gY2FsbGVycyBsYXllciB0aGVpciBvd24gZmFsbGJhY2suXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiByYW5rTW9kZWxzKFxuICBlbmRwb2ludHM6IHJlYWRvbmx5IFNlcnZpbmdFbmRwb2ludFN1bW1hcnlbXSxcbiAgcXVlcnk6IE1vZGVsUXVlcnkgPSB7fSxcbik6IFJhbmtlZE1vZGVsW10ge1xuICBjb25zdCBjbGFzc2lmaWVkID0gY2xhc3NpZnkuY2xhc3NpZnlFbmRwb2ludHMoZW5kcG9pbnRzKTtcbiAgY29uc3QgZWxpZ2libGUgPVxuICAgIHF1ZXJ5Lm1vZGVsQ2xhc3MgIT09IHVuZGVmaW5lZCA/IGNsYXNzZXNBdE9yQmVsb3cocXVlcnkubW9kZWxDbGFzcykgOiBDSEFUX0NMQVNTX09SREVSO1xuXG4gIC8vIEZsYXR0ZW4gZWxpZ2libGUgY2xhc3NlcyBpbiBjYXBhYmlsaXR5IG9yZGVyLCBjYXJyeWluZyBlYWNoIGVuZHBvaW50J3NcbiAgLy8gY2xhc3M7IGJ1Y2tldCBvcmRlciBpcyBhbHJlYWR5IGJlc3QtZmlyc3QuXG4gIGNvbnN0IGNhbmRpZGF0ZXM6IFJhbmtlZE1vZGVsW10gPSBbXTtcbiAgZm9yIChjb25zdCBtb2RlbENsYXNzIG9mIGVsaWdpYmxlKSB7XG4gICAgZm9yIChjb25zdCBlbmRwb2ludCBvZiBjbGFzc2lmaWVkW21vZGVsQ2xhc3NdKSBjYW5kaWRhdGVzLnB1c2goeyBlbmRwb2ludCwgbW9kZWxDbGFzcyB9KTtcbiAgfVxuXG4gIGNvbnN0IHNlYXJjaCA9IHF1ZXJ5LnNlYXJjaD8udHJpbSgpO1xuICBsZXQgcmFua2VkOiBSYW5rZWRNb2RlbFtdO1xuICBpZiAoc2VhcmNoKSB7XG4gICAgY29uc3Qgc2NvcmVzID0gbmV3IE1hcDxzdHJpbmcsIG51bWJlcj4oKTtcbiAgICBmb3IgKGNvbnN0IG1hdGNoIG9mIHNlYXJjaFNlcnZpbmdFbmRwb2ludHMoXG4gICAgICBzZWFyY2gsXG4gICAgICBjYW5kaWRhdGVzLm1hcCgoYykgPT4gYy5lbmRwb2ludCksXG4gICAgICBxdWVyeS50aHJlc2hvbGQgIT09IHVuZGVmaW5lZCA/IHsgdGhyZXNob2xkOiBxdWVyeS50aHJlc2hvbGQgfSA6IHt9LFxuICAgICkpIHtcbiAgICAgIHNjb3Jlcy5zZXQobWF0Y2guZW5kcG9pbnQubmFtZSwgbWF0Y2guc2NvcmUpO1xuICAgIH1cbiAgICAvLyBgQXJyYXkucHJvdG90eXBlLnNvcnRgIGlzIHN0YWJsZSwgc28gZW5kcG9pbnRzIGVxdWFsIG9uIG1hdGNoIGFuZCBjbGFzc1xuICAgIC8vIGtlZXAgdGhlaXIgYmVzdC1maXJzdCB3aXRoaW4tY2xhc3Mgb3JkZXIuXG4gICAgcmFua2VkID0gY2FuZGlkYXRlc1xuICAgICAgLmZpbHRlcigoYykgPT4gc2NvcmVzLmhhcyhjLmVuZHBvaW50Lm5hbWUpKVxuICAgICAgLm1hcCgoYykgPT4gKHsgLi4uYywgc2NvcmU6IHNjb3Jlcy5nZXQoYy5lbmRwb2ludC5uYW1lKSB9KSlcbiAgICAgIC5zb3J0KChhLCBiKSA9PiB7XG4gICAgICAgIGNvbnN0IGJ5TWF0Y2ggPSBtYXRjaEJ1Y2tldChhLnNjb3JlKSAtIG1hdGNoQnVja2V0KGIuc2NvcmUpO1xuICAgICAgICBpZiAoYnlNYXRjaCAhPT0gMCkgcmV0dXJuIGJ5TWF0Y2g7XG4gICAgICAgIHJldHVybiBNT0RFTF9DTEFTU19PUkRFUi5pbmRleE9mKGEubW9kZWxDbGFzcykgLSBNT0RFTF9DTEFTU19PUkRFUi5pbmRleE9mKGIubW9kZWxDbGFzcyk7XG4gICAgICB9KTtcbiAgfSBlbHNlIHtcbiAgICByYW5rZWQgPSBjYW5kaWRhdGVzO1xuICB9XG5cbiAgcmV0dXJuIHF1ZXJ5LmxpbWl0ICE9PSB1bmRlZmluZWQgPyByYW5rZWQuc2xpY2UoMCwgTWF0aC5tYXgoMCwgcXVlcnkubGltaXQpKSA6IHJhbmtlZDtcbn1cblxuLyoqXG4gKiBDb2xsYXBzZSB7QGxpbmsgcmFua01vZGVsc30gdG8gYSBzaW5nbGUgaWQ6IHRoZSBjbG9zZXN0IGVuZHBvaW50IHRvIGBzZWFyY2hgXG4gKiBpbiBhIGNhdGFsb2d1ZSBzbmFwc2hvdCwgb3IgdGhlIGlucHV0IHZlcmJhdGltIHdoZW4gbm90aGluZyBzY29yZXMgd2l0aGluIHRoZVxuICogdGhyZXNob2xkLlxuICpcbiAqIFRoZSByYW5rLWJhc2VkIGNvdW50ZXJwYXJ0IHRvIHRoZSBGdXNlLW9ubHkge0BsaW5rIHJlc29sdmVNb2RlbElkfTogZXF1YWxcbiAqIG1hdGNoIHNjb3JlcyBhcmUgYnJva2VuIGJ5IGNsYXNzIGFuZCB0aGVuIHdpdGhpbi1jbGFzcyB2ZXJzaW9uLCBzbyBhIGxvb3NlXG4gKiBgXCJvcHVzXCJgIHByZWZlcnMgYG9wdXMtNWAgb3ZlciBgb3B1cy00LTdgIGluc3RlYWQgb2YgcGlja2luZyB3aGljaGV2ZXJcbiAqIHNpYmxpbmcgRnVzZSBoYXBwZW5lZCB0byBvcmRlciBmaXJzdC4gUmV0dXJuaW5nIHRoZSBpbnB1dCB1bm1hdGNoZWQgKHJhdGhlclxuICogdGhhbiBhIG5lYXIgbmVpZ2hib3VyKSBpcyBkZWxpYmVyYXRlIC0gYSBkZWxpYmVyYXRlIGVuZHBvaW50IGlkIGlzIG5ldmVyXG4gKiBzaWxlbnRseSByZXdyaXR0ZW4sIGFuZCBEYXRhYnJpY2tzIHN1cmZhY2VzIGEgY2xlYW4gNDA0LlxuICovXG5leHBvcnQgZnVuY3Rpb24gcmFua01vZGVsSWQoXG4gIGVuZHBvaW50czogcmVhZG9ubHkgU2VydmluZ0VuZHBvaW50U3VtbWFyeVtdLFxuICBzZWFyY2g6IHN0cmluZyxcbiAgb3B0aW9uczogUmVzb2x2ZU1vZGVsT3B0aW9ucyA9IHt9LFxuKTogUmVzb2x2ZWRNb2RlbCB7XG4gIGNvbnN0IFt0b3BdID0gcmFua01vZGVscyhlbmRwb2ludHMsIHtcbiAgICBzZWFyY2gsXG4gICAgbGltaXQ6IDEsXG4gICAgLi4uKG9wdGlvbnMudGhyZXNob2xkICE9PSB1bmRlZmluZWQgPyB7IHRocmVzaG9sZDogb3B0aW9ucy50aHJlc2hvbGQgfSA6IHt9KSxcbiAgfSk7XG4gIGlmICghdG9wKSByZXR1cm4geyBtb2RlbElkOiBzZWFyY2gsIG1hdGNoZWQ6IGZhbHNlIH07XG4gIHJldHVybiB7IG1vZGVsSWQ6IHRvcC5lbmRwb2ludC5uYW1lLCBtYXRjaGVkOiB0cnVlLCBzY29yZTogdG9wLnNjb3JlIH07XG59XG5cbi8qKlxuICoge0BsaW5rIHJhbmtNb2RlbElkfSBhZ2FpbnN0IGEgY2F0YWxvZ3VlIHRoZSBjYWxsZXIgbWF5IGJlIGhvbGRpbmcgc3RhbGU6XG4gKiBtYXRjaCB0aGUgbG9hZGVkIHNuYXBzaG90LCBhbmQgb24gYSBtaXNzIHJlbG9hZCBvbmNlIHdpdGggYGZvcmNlYCBhbmQgbWF0Y2hcbiAqIGFnYWluLiBUaGF0IHdheSBhIG1vZGVsIGRlcGxveWVkIGFmdGVyIHRoZSBjYXRhbG9ndWUgd2FzIGNhY2hlZCBzdGlsbFxuICogcmVzb2x2ZXMgb24gZmlyc3QgdXNlLCB3aXRob3V0IGEgcmVzdGFydCBhbmQgd2l0aG91dCBnaXZpbmcgdXAgY2FjaGluZy5cbiAqXG4gKiBUaGUgY2F0YWxvZ3VlIGFycml2ZXMgYXMgYSBsb2FkZXIgcmF0aGVyIHRoYW4gYSBjbGllbnQgc28gdGhlIGNhbGxlciBrZWVwc1xuICogb3duZXJzaGlwIG9mICpob3cqIGl0IGlzIGNhY2hlZCAtIHtAbGluayBsaXN0U2VydmluZ0VuZHBvaW50c30gYW5kIGl0c1xuICogYENhY2hlTWFuYWdlcmAsIGEgcGxhaW4gcHJvY2Vzcy1saWZldGltZSBmaWVsZCBpbiBhIENMSSwgb3IgYSB0ZXN0IGRvdWJsZS5cbiAqIE9ubHkgb25lIHJlbG9hZCBpcyBhdHRlbXB0ZWQ6IGEgZ2VudWluZWx5IHVua25vd24gbmFtZSBzaG91bGQgZmFpbCBmYXN0XG4gKiByYXRoZXIgdGhhbiByZS1saXN0IG9uIGV2ZXJ5IHJlcXVlc3QuXG4gKlxuICogQHBhcmFtIGxvYWQgLSBSZXR1cm5zIHRoZSBjYXRhbG9ndWU7IGBmb3JjZWAgYXNrcyBpdCB0byBieXBhc3MgaXRzIGNhY2hlLlxuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gcmFua01vZGVsSWRMaXZlKFxuICBsb2FkOiAoZm9yY2U6IGJvb2xlYW4pID0+IFByb21pc2U8cmVhZG9ubHkgU2VydmluZ0VuZHBvaW50U3VtbWFyeVtdPixcbiAgc2VhcmNoOiBzdHJpbmcsXG4gIG9wdGlvbnM6IFJlc29sdmVNb2RlbE9wdGlvbnMgPSB7fSxcbik6IFByb21pc2U8UmVzb2x2ZWRNb2RlbD4ge1xuICBjb25zdCByZXNvbHZlZCA9IHJhbmtNb2RlbElkKGF3YWl0IGxvYWQoZmFsc2UpLCBzZWFyY2gsIG9wdGlvbnMpO1xuICBpZiAocmVzb2x2ZWQubWF0Y2hlZCkgcmV0dXJuIHJlc29sdmVkO1xuICByZXR1cm4gcmFua01vZGVsSWQoYXdhaXQgbG9hZCh0cnVlKSwgc2VhcmNoLCBvcHRpb25zKTtcbn1cblxuLyoqXG4gKiBSYW5rIGEgd29ya3NwYWNlJ3MgY2F0YWxvZ3VlIGluIG9uZSBjYWxsOiBsaXN0IGl0cyBgL3NlcnZpbmctZW5kcG9pbnRzYFxuICogKGNhY2hlZCkgYW5kIHJ1biB7QGxpbmsgcmFua01vZGVsc30gb3ZlciB0aGUgcmVzdWx0LiBUaGUgbGlzdCBjb3VudGVycGFydCB0b1xuICoge0BsaW5rIHNlbGVjdE1vZGVsfSwgZm9yIGEgY29uc3VtZXIgdGhhdCB3YW50cyB0aGUgZnVsbCByYW5rZWQgc2V0IChhIG1vZGVsXG4gKiBwaWNrZXIsIGEgQ0xJKSByYXRoZXIgdGhhbiBhIHNpbmdsZSBpZC4gQ2F0YWxvZ3VlIGZldGNoZXMgZmFpbCBsb3VkOiBuZXR3b3JrXG4gKiAvIGF1dGggZXJyb3JzIHByb3BhZ2F0ZSBzbyB0aGUgY2FsbGVyIHNlZXMgdGhlIHJlYWwgU0RLIG1lc3NhZ2UuXG4gKlxuICogQHBhcmFtIGhvc3QgLSBXb3Jrc3BhY2UgaG9zdCB1c2VkIGFzIHRoZSBjYWNoZSBrZXkuIFBhc3MgdGhlIHZhbHVlIHJlc29sdmVkXG4gKiAgIGZyb20gYGNsaWVudC5jb25maWcuZ2V0SG9zdCgpYC5cbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIHNlYXJjaE1vZGVscyhcbiAgY2xpZW50OiBXb3Jrc3BhY2VDbGllbnRMaWtlLFxuICBob3N0OiBzdHJpbmcsXG4gIGlucHV0OiBTZWFyY2hNb2RlbHNJbnB1dCA9IHt9LFxuKTogUHJvbWlzZTxSYW5rZWRNb2RlbFtdPiB7XG4gIGNvbnN0IGVuZHBvaW50cyA9IGF3YWl0IGxpc3RTZXJ2aW5nRW5kcG9pbnRzKFxuICAgIGNsaWVudCxcbiAgICBob3N0LFxuICAgIGlucHV0LnR0bE1zICE9PSB1bmRlZmluZWQgPyB7IHR0bE1zOiBpbnB1dC50dGxNcyB9IDoge30sXG4gICk7XG4gIHJldHVybiByYW5rTW9kZWxzKGVuZHBvaW50cywgaW5wdXQpO1xufVxuXG4vKipcbiAqIFJlc29sdmUgYSBtb2RlbCBpZCBmb3IgYSB3b3Jrc3BhY2UgaW4gb25lIGNhbGw6IGxpc3QgaXRzIGAvc2VydmluZy1lbmRwb2ludHNgXG4gKiAoY2FjaGVkKSBhbmQgcnVuIHtAbGluayByZXNvbHZlTW9kZWx9IG92ZXIgdGhlIHJlc3VsdC4gVGhpcyBpcyB0aGUgZW50cnlcbiAqIHBvaW50IGZvciBhbnkgY29uc3VtZXIgdGhhdCBob2xkcyBhIGBXb3Jrc3BhY2VDbGllbnRgIGFuZCBqdXN0IHdhbnRzIGEgdXNhYmxlXG4gKiBtb2RlbCBuYW1lIC0gYSBMYWtlZmxvdyBqb2IsIGEgb25lLW9mZiBzY3JpcHQsIG9yIHRoZSBNYXN0cmEgcGx1Z2luIGFsaWtlLlxuICpcbiAqIENoZWFwIGV4aXQ6IHdoZW4gYW4gYGV4cGxpY2l0YCBuYW1lIGlzIGdpdmVuIGFuZCBgZnV6enlgIGlzIG9mZiwgdGhlXG4gKiBjYXRhbG9ndWUgaXMgbmV2ZXIgZmV0Y2hlZCAtIHRoZSBuYW1lIGlzIHJldHVybmVkIHZlcmJhdGltIGFuZCBEYXRhYnJpY2tzXG4gKiBzdXJmYWNlcyB0aGUgY2Fub25pY2FsIDQwNCBpZiBpdCBkb2Vzbid0IGV4aXN0LiBDYXRhbG9ndWUgZmV0Y2hlcyBvdGhlcndpc2VcbiAqIGZhaWwgbG91ZDogbmV0d29yayAvIGF1dGggZXJyb3JzIHByb3BhZ2F0ZSBzbyB0aGUgY2FsbGVyIHNlZXMgdGhlIHJlYWwgU0RLXG4gKiBtZXNzYWdlIGluc3RlYWQgb2YgYSBzaWxlbnQgZmFsbGJhY2suXG4gKlxuICogQHBhcmFtIGhvc3QgLSBXb3Jrc3BhY2UgaG9zdCB1c2VkIGFzIHRoZSBjYWNoZSBrZXkuIFBhc3MgdGhlIHZhbHVlIHJlc29sdmVkXG4gKiAgIGZyb20gYGNsaWVudC5jb25maWcuZ2V0SG9zdCgpYC5cbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIHNlbGVjdE1vZGVsKFxuICBjbGllbnQ6IFdvcmtzcGFjZUNsaWVudExpa2UsXG4gIGhvc3Q6IHN0cmluZyxcbiAgaW5wdXQ6IFNlbGVjdE1vZGVsSW5wdXQgPSB7fSxcbik6IFByb21pc2U8UmVzb2x2ZWRNb2RlbFNlbGVjdGlvbj4ge1xuICBpZiAoaW5wdXQuZXhwbGljaXQgIT09IHVuZGVmaW5lZCAmJiBpbnB1dC5mdXp6eSA9PT0gZmFsc2UpIHtcbiAgICByZXR1cm4geyBtb2RlbElkOiBpbnB1dC5leHBsaWNpdCwgc291cmNlOiBcImV4cGxpY2l0XCIgfTtcbiAgfVxuICBjb25zdCBlbmRwb2ludHMgPSBhd2FpdCBsaXN0U2VydmluZ0VuZHBvaW50cyhjbGllbnQsIGhvc3QsIHtcbiAgICAuLi4oaW5wdXQudHRsTXMgIT09IHVuZGVmaW5lZCA/IHsgdHRsTXM6IGlucHV0LnR0bE1zIH0gOiB7fSksXG4gIH0pO1xuICByZXR1cm4gcmVzb2x2ZU1vZGVsKGVuZHBvaW50cywgaW5wdXQpO1xufVxuXG4vKipcbiAqIFJlc29sdmUgYSBzaW5nbGUgbW9kZWwgaWQgZnJvbSB0aGUgbGl2ZSBjYXRhbG9ndWUgYW5kIGNhbGxlciBpbnRlbnQsXG4gKiBkZWxlZ2F0aW5nIHRoZSBsaXZlIHNlbGVjdGlvbiB0byB7QGxpbmsgcmFua01vZGVsc30gd2l0aCBgbGltaXQ6IDFgLlxuICpcbiAqIDEuICoqRXhwbGljaXQgYXNrKio6IHdpdGggYGZ1enp5YCBvZmYsIHJldHVybmVkIHZlcmJhdGltOyBvdGhlcndpc2VcbiAqICAgIGZ1enp5LXJhbmtlZCB3aXRoaW4gdGhlIChvcHRpb25hbCkgY2xhc3MgY2VpbGluZyBhbmQgdGhlIGJlc3QgdGFrZW4sXG4gKiAgICBmYWxsaW5nIGJhY2sgdG8gdGhlIGlucHV0IHZlcmJhdGltIHdoZW4gbm90aGluZyBtYXRjaGVzLlxuICogMi4gKipObyBleHBsaWNpdCBhc2sqKjogYW4gb3BlcmF0b3ItcGlubmVkIGBmYWxsYmFja2AgdGhhdCBleGlzdHMgaW4gdGhlIGxpdmVcbiAqICAgIGNhdGFsb2d1ZSB3aW5zIGZpcnN0OyB0aGVuIHRoZSByYW5rZWQgbGl2ZSBjYXRhbG9ndWUgKGNsYXNzIGNlaWxpbmdcbiAqICAgIGFwcGxpZWQpOyB0aGVuIHRoZSBzdGF0aWMge0BsaW5rIEZBTExCQUNLX01PREVMX0lEU30gZmxvb3Igd2hlbiB0aGVcbiAqICAgIGNhdGFsb2d1ZSB5aWVsZHMgbm90aGluZyBpbiByYW5nZS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHJlc29sdmVNb2RlbChcbiAgZW5kcG9pbnRzOiByZWFkb25seSBTZXJ2aW5nRW5kcG9pbnRTdW1tYXJ5W10sXG4gIGlucHV0OiBSZXNvbHZlTW9kZWxJbnB1dCA9IHt9LFxuKTogUmVzb2x2ZWRNb2RlbFNlbGVjdGlvbiB7XG4gIGlmIChpbnB1dC5leHBsaWNpdCAhPT0gdW5kZWZpbmVkKSB7XG4gICAgaWYgKGlucHV0LmZ1enp5ID09PSBmYWxzZSkge1xuICAgICAgcmV0dXJuIHsgbW9kZWxJZDogaW5wdXQuZXhwbGljaXQsIHNvdXJjZTogXCJleHBsaWNpdFwiIH07XG4gICAgfVxuICAgIGNvbnN0IFt0b3BdID0gcmFua01vZGVscyhlbmRwb2ludHMsIGJ1aWxkUXVlcnkoaW5wdXQsIGlucHV0LmV4cGxpY2l0KSk7XG4gICAgcmV0dXJuIHsgbW9kZWxJZDogdG9wPy5lbmRwb2ludC5uYW1lID8/IGlucHV0LmV4cGxpY2l0LCBzb3VyY2U6IFwiZnV6enktbWF0Y2hcIiB9O1xuICB9XG5cbiAgLy8gT3BlcmF0b3ItcGlubmVkIGZhbGxiYWNrcyB3aW4gd2hlbiBwcmVzZW50IGFuZCBsaXZlIChlLmcuIGEgcmVndWxhdGVkXG4gIC8vIHdvcmtzcGFjZSByZXN0cmljdGVkIHRvIGFuIGFwcHJvdmVkIHN1YnNldCkuXG4gIGlmIChpbnB1dC5tb2RlbENsYXNzID09PSB1bmRlZmluZWQgJiYgaW5wdXQuZmFsbGJhY2tzICYmIGlucHV0LmZhbGxiYWNrcy5sZW5ndGggPiAwKSB7XG4gICAgY29uc3QgcHJlc2VudCA9IG5ldyBTZXQoZW5kcG9pbnRzLm1hcCgoZSkgPT4gZS5uYW1lKSk7XG4gICAgY29uc3QgcGlubmVkID0gaW5wdXQuZmFsbGJhY2tzLmZpbmQoKGlkKSA9PiBwcmVzZW50LmhhcyhpZCkpO1xuICAgIGlmIChwaW5uZWQpIHJldHVybiB7IG1vZGVsSWQ6IHBpbm5lZCwgc291cmNlOiBcImZhbGxiYWNrXCIgfTtcbiAgfVxuXG4gIGNvbnN0IHNvdXJjZSA9IGlucHV0Lm1vZGVsQ2xhc3MgIT09IHVuZGVmaW5lZCA/IFwiY2xhc3NcIiA6IFwiZmFsbGJhY2tcIjtcbiAgY29uc3QgW3RvcF0gPSByYW5rTW9kZWxzKGVuZHBvaW50cywgYnVpbGRRdWVyeShpbnB1dCwgdW5kZWZpbmVkKSk7XG4gIGlmICh0b3ApIHJldHVybiB7IG1vZGVsSWQ6IHRvcC5lbmRwb2ludC5uYW1lLCBzb3VyY2UgfTtcblxuICAvLyBMaXZlIGNhdGFsb2d1ZSB5aWVsZGVkIG5vdGhpbmcgaW4gcmFuZ2U6IHdhbGsgdGhlIHN0YXRpYyBmbG9vci5cbiAgY29uc3QgZmxvb3JTb3VyY2UgPVxuICAgIGlucHV0Lm1vZGVsQ2xhc3MgIT09IHVuZGVmaW5lZCA/IG1vZGVsc0ZvckNsYXNzKGlucHV0Lm1vZGVsQ2xhc3MpIDogKGlucHV0LmZhbGxiYWNrcyA/PyBbXSk7XG4gIGNvbnN0IGZsb29yID0gb2JqZWN0LnNlcXVlbmNlKGZsb29yU291cmNlKS5jb25jYXQoRkFMTEJBQ0tfTU9ERUxfSURTKS5kaXN0aW5jdCgpLnRvQXJyYXkoKTtcbiAgcmV0dXJuIHsgbW9kZWxJZDogcGlja0ZpcnN0QXZhaWxhYmxlKGZsb29yLCBlbmRwb2ludHMpLCBzb3VyY2UgfTtcbn1cblxuLyoqIEJ1aWxkIGEge0BsaW5rIE1vZGVsUXVlcnl9IGZyb20ge0BsaW5rIFJlc29sdmVNb2RlbElucHV0fSBmb3IgdGhlIGBsaW1pdDogMWAgZGVsZWdhdGlvbi4gKi9cbmZ1bmN0aW9uIGJ1aWxkUXVlcnkoaW5wdXQ6IFJlc29sdmVNb2RlbElucHV0LCBzZWFyY2g6IHN0cmluZyB8IHVuZGVmaW5lZCk6IE1vZGVsUXVlcnkge1xuICByZXR1cm4ge1xuICAgIC4uLihzZWFyY2ggIT09IHVuZGVmaW5lZCA/IHsgc2VhcmNoIH0gOiB7fSksXG4gICAgLi4uKGlucHV0Lm1vZGVsQ2xhc3MgIT09IHVuZGVmaW5lZCA/IHsgbW9kZWxDbGFzczogaW5wdXQubW9kZWxDbGFzcyB9IDoge30pLFxuICAgIC4uLihpbnB1dC50aHJlc2hvbGQgIT09IHVuZGVmaW5lZCA/IHsgdGhyZXNob2xkOiBpbnB1dC50aHJlc2hvbGQgfSA6IHt9KSxcbiAgICBsaW1pdDogMSxcbiAgfTtcbn1cblxuLyoqXG4gKiBGaW5kIHRoZSBmaXJzdCBpZCBpbiBgY2FuZGlkYXRlc2Agd2hvc2UgZW5kcG9pbnQgaXMgcHJlc2VudCBpbiBgZW5kcG9pbnRzYC5cbiAqIFJldHVybnMgdGhlIHRvcCBjYW5kaWRhdGUgd2hlbiB0aGUgd29ya3NwYWNlIGhhcyBub25lIG9mIHRoZW0gc28gY2FsbGVyc1xuICogYWx3YXlzIGdldCBhIHN0cmluZzsgYW4gb2ZmbGluZSB3b3Jrc3BhY2UgdGhlbiByZWNlaXZlcyBhIGNsZWFuIDQwNCBmcm9tXG4gKiBEYXRhYnJpY2tzIGluc3RlYWQgb2YgYSBtYWxmb3JtZWQgY29uZmlnLlxuICovXG5mdW5jdGlvbiBwaWNrRmlyc3RBdmFpbGFibGUoXG4gIGNhbmRpZGF0ZXM6IHJlYWRvbmx5IHN0cmluZ1tdLFxuICBlbmRwb2ludHM6IHJlYWRvbmx5IFNlcnZpbmdFbmRwb2ludFN1bW1hcnlbXSxcbik6IHN0cmluZyB7XG4gIGNvbnN0IHByZXNlbnQgPSBuZXcgU2V0KGVuZHBvaW50cy5tYXAoKGUpID0+IGUubmFtZSkpO1xuICBmb3IgKGNvbnN0IGNhbmRpZGF0ZSBvZiBjYW5kaWRhdGVzKSB7XG4gICAgaWYgKHByZXNlbnQuaGFzKGNhbmRpZGF0ZSkpIHJldHVybiBjYW5kaWRhdGU7XG4gIH1cbiAgcmV0dXJuIGNhbmRpZGF0ZXNbMF0gPz8gRkFMTEJBQ0tfTU9ERUxfSURTWzBdITtcbn1cbiJdfQ==
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live Databricks Model Serving catalogue access.
|
|
3
|
+
*
|
|
4
|
+
* Lists the workspace's `/serving-endpoints` once per host and caches the
|
|
5
|
+
* result with a TTL via AppKit's `CacheManager`, with concurrent callers
|
|
6
|
+
* sharing one in-flight promise (the coalescing pattern of Python's
|
|
7
|
+
* `cachetools-async`). Surfaces each endpoint as a stable
|
|
8
|
+
* {@link ServingEndpointSummary} - including the Foundation Model API
|
|
9
|
+
* `quality` / `speed` / `cost` profile when present, the classified
|
|
10
|
+
* {@link ModelClass}, and (for embedding endpoints) the measured vector
|
|
11
|
+
* `dimension` - and snaps loose, human-typed names to real endpoint ids
|
|
12
|
+
* through `fuse.js` extended search so tokens like `"claude sonnet"` resolve to
|
|
13
|
+
* `databricks-claude-sonnet-4-6`.
|
|
14
|
+
*
|
|
15
|
+
* The class stamp and embedding dimension are computed once per cache load:
|
|
16
|
+
* every embedding endpoint is "pinged" in parallel and the resulting vector
|
|
17
|
+
* length recorded, so the cost is paid on a cache miss, not per read. The ping
|
|
18
|
+
* is best-effort - a failure logs at debug and leaves `dimension` unset rather
|
|
19
|
+
* than failing the whole listing.
|
|
20
|
+
*
|
|
21
|
+
* @module
|
|
22
|
+
*/
|
|
23
|
+
import { appkit } from "@dbx-tools/appkit";
|
|
24
|
+
import { type ServingEndpointSummary } from "@dbx-tools/shared-model";
|
|
25
|
+
/**
|
|
26
|
+
* Structural type for the Databricks workspace client, re-exported so the rest
|
|
27
|
+
* of this package can keep importing it from here. See
|
|
28
|
+
* `appkit.WorkspaceClientLike` (node-appkit) for the canonical definition.
|
|
29
|
+
*/
|
|
30
|
+
export type WorkspaceClientLike = appkit.WorkspaceClientLike;
|
|
31
|
+
/** Default TTL for the in-memory endpoint cache. Matches the Databricks SDK's session lifetime budget. */
|
|
32
|
+
export declare const DEFAULT_MODEL_CACHE_TTL_MS: number;
|
|
33
|
+
/** Default Fuse.js score threshold below which a fuzzy match is accepted. */
|
|
34
|
+
export declare const DEFAULT_FUZZY_THRESHOLD = 0.4;
|
|
35
|
+
/** Options for {@link listServingEndpoints}. */
|
|
36
|
+
export interface ListServingEndpointsOptions {
|
|
37
|
+
/**
|
|
38
|
+
* Override the default cache TTL for this call, in milliseconds. Forwarded to
|
|
39
|
+
* `CacheManager` as seconds.
|
|
40
|
+
*/
|
|
41
|
+
ttlMs?: number;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* List Model Serving endpoints for the workspace owning `client`, routed
|
|
45
|
+
* through AppKit's `CacheManager`. The manager gives us everything
|
|
46
|
+
* `cachetools.TTLCache` provides plus what `cachetools-async` adds on top:
|
|
47
|
+
* per-entry TTL, in-flight request coalescing (concurrent callers share one
|
|
48
|
+
* fetch via the manager's internal `inFlightRequests` map), bounded size,
|
|
49
|
+
* telemetry spans (`cache.getOrExecute`), and optional Lakebase persistence so
|
|
50
|
+
* the catalogue survives restarts when the lakebase plugin is wired up.
|
|
51
|
+
*
|
|
52
|
+
* Returns plain {@link ServingEndpointSummary} objects (a stable subset of the
|
|
53
|
+
* SDK type) so cache hits never expose stale SDK internals. Errors from
|
|
54
|
+
* `CacheManager` or the SDK fetch propagate to the caller - we don't swallow
|
|
55
|
+
* them so users see the real auth / network issue.
|
|
56
|
+
*
|
|
57
|
+
* @param host - Workspace host used as the cache key. Pass the value resolved
|
|
58
|
+
* from `client.config.getHost()` so multi-host apps share one entry per
|
|
59
|
+
* workspace.
|
|
60
|
+
* @param options.ttlMs - Override the default TTL just for this call. Forwarded
|
|
61
|
+
* to `CacheManager` as seconds.
|
|
62
|
+
*/
|
|
63
|
+
export declare function listServingEndpoints(client: WorkspaceClientLike, host: string, options?: ListServingEndpointsOptions): Promise<ServingEndpointSummary[]>;
|
|
64
|
+
/**
|
|
65
|
+
* List the workspace's serving endpoints as minimal
|
|
66
|
+
* {@link ServingEndpointSummary} objects straight from the SDK: no caching, and
|
|
67
|
+
* none of the cache-load enrichment ({@link listServingEndpoints} adds the
|
|
68
|
+
* {@link ModelClass} stamp and the embedding-dimension probe). Use this for a
|
|
69
|
+
* one-shot, dependency-light listing - e.g. a CLI that only needs names/tasks
|
|
70
|
+
* for fuzzy resolution and doesn't want AppKit's `CacheManager` or the
|
|
71
|
+
* per-embedding ping cost. Prefer {@link listServingEndpoints} for the cached,
|
|
72
|
+
* enriched view.
|
|
73
|
+
*/
|
|
74
|
+
export declare function listServingEndpointsUncached(client: WorkspaceClientLike): Promise<ServingEndpointSummary[]>;
|
|
75
|
+
/**
|
|
76
|
+
* Force-evict cached endpoint listings via AppKit's `CacheManager`. With a
|
|
77
|
+
* `host` deletes that one workspace's entry; without one clears every cache
|
|
78
|
+
* entry on the manager (since `CacheManager` doesn't expose a namespace-scoped
|
|
79
|
+
* clear, this is the brute-force path - fine for tests, avoid in steady-state
|
|
80
|
+
* code).
|
|
81
|
+
*/
|
|
82
|
+
export declare function clearServingEndpointsCache(host?: string): Promise<void>;
|
|
83
|
+
/**
|
|
84
|
+
* Result of fuzzy-resolving a user-supplied model name against the live
|
|
85
|
+
* endpoint list. `score` is Fuse.js's distance (`0` is exact, `1` is no match);
|
|
86
|
+
* `matched` is `false` when the score exceeds the configured threshold so
|
|
87
|
+
* callers can fall back to the original input (Databricks will then return a
|
|
88
|
+
* clean 404).
|
|
89
|
+
*/
|
|
90
|
+
export interface ResolvedModel {
|
|
91
|
+
modelId: string;
|
|
92
|
+
matched: boolean;
|
|
93
|
+
score?: number;
|
|
94
|
+
}
|
|
95
|
+
/** Options accepted by {@link resolveModelId} / {@link searchServingEndpoints}. */
|
|
96
|
+
export interface ResolveModelOptions {
|
|
97
|
+
/** Fuse.js threshold (0 = exact, 1 = anything). Default `0.4`. */
|
|
98
|
+
threshold?: number;
|
|
99
|
+
}
|
|
100
|
+
/** A serving endpoint paired with its fuzzy-match distance for a query. */
|
|
101
|
+
export interface ScoredEndpoint {
|
|
102
|
+
endpoint: ServingEndpointSummary;
|
|
103
|
+
/** Fuse.js distance: `0` is exact, `1` is no match. */
|
|
104
|
+
score: number;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Fuzzy-rank endpoints by how closely their `name` matches `input`, best
|
|
108
|
+
* (lowest score) first, keeping only those within `threshold`:
|
|
109
|
+
*
|
|
110
|
+
* 1. An exact name match short-circuits to a single `score: 0` result.
|
|
111
|
+
* 2. Otherwise the input is tokenized (dashes / underscores / spaces become
|
|
112
|
+
* separators) and fed through Fuse.js extended search, which AND-s each
|
|
113
|
+
* token with fuzzy matching enabled - the "tokenized fuzzy match" a caller
|
|
114
|
+
* reaches for when they type `"claude sonnet"` instead of the full endpoint
|
|
115
|
+
* name.
|
|
116
|
+
*
|
|
117
|
+
* Returns `[]` for an empty endpoint list or when `input` tokenizes to nothing,
|
|
118
|
+
* so callers fall back to the raw input and let Databricks surface a clean 404.
|
|
119
|
+
* This multi-result core is shared by {@link resolveModelId} (single best) and
|
|
120
|
+
* the ranked `rankModels` selector.
|
|
121
|
+
*/
|
|
122
|
+
export declare function searchServingEndpoints(input: string, endpoints: readonly ServingEndpointSummary[], options?: ResolveModelOptions): ScoredEndpoint[];
|
|
123
|
+
/**
|
|
124
|
+
* Snap a user-supplied model name to the single closest configured serving
|
|
125
|
+
* endpoint via {@link searchServingEndpoints}. Returns the input unchanged with
|
|
126
|
+
* `matched: false` when nothing scores within the threshold (or the catalogue
|
|
127
|
+
* is empty), so a deliberate model id is never silently rewritten to a
|
|
128
|
+
* similar-looking neighbour and the upstream call surfaces the canonical 404.
|
|
129
|
+
*/
|
|
130
|
+
export declare function resolveModelId(input: string, endpoints: readonly ServingEndpointSummary[], options?: ResolveModelOptions): ResolvedModel;
|