@dbx-tools/model 0.9.5 → 0.9.7
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/README.md +7 -7
- package/index.ts +4 -4
- package/lib/index.d.ts +4 -4
- package/lib/index.js +4 -4
- package/lib/src/_native.d.ts +14 -0
- package/lib/src/_native.js +124 -0
- package/lib/src/classes.d.ts +6 -1
- package/lib/src/fallback.d.ts +9 -6
- package/lib/src/fallback.js +14 -12
- package/lib/src/{serving.d.ts → model-catalog.d.ts} +25 -20
- package/lib/src/model-catalog.js +198 -0
- package/lib/src/policy.d.ts +5 -1
- package/lib/src/policy.js +14 -3
- package/lib/src/resolve.d.ts +8 -4
- package/lib/src/resolve.js +15 -71
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -6
- package/src/_native.ts +174 -0
- package/src/fallback.ts +15 -11
- package/src/{serving.ts → model-catalog.ts} +60 -156
- package/src/policy.ts +21 -2
- package/src/resolve.ts +19 -87
- package/lib/src/serving.js +0 -292
package/src/resolve.ts
CHANGED
|
@@ -22,40 +22,27 @@
|
|
|
22
22
|
|
|
23
23
|
import { object } from "@dbx-tools/shared-core";
|
|
24
24
|
import {
|
|
25
|
-
ModelClass as ModelRsModelClass,
|
|
26
|
-
rankModels as rankModelsWithRust,
|
|
27
|
-
type ModelQuery as ModelRsModelQuery,
|
|
28
|
-
type ServingEndpointSummary as ModelRsServingEndpointSummary,
|
|
29
|
-
} from "@dbx-tools/model-rs";
|
|
30
|
-
import {
|
|
31
|
-
classify,
|
|
32
25
|
model,
|
|
33
26
|
type ModelQuery,
|
|
34
27
|
type RankedModel,
|
|
35
28
|
type ServingEndpointSummary,
|
|
36
29
|
} from "@dbx-tools/shared-model";
|
|
37
30
|
|
|
31
|
+
import { rankEndpoints } from "./_native.ts";
|
|
38
32
|
import { FALLBACK_MODEL_IDS, modelsForClass } from "./fallback.ts";
|
|
39
33
|
import {
|
|
40
34
|
listServingEndpoints,
|
|
41
|
-
searchServingEndpoints,
|
|
42
35
|
type ResolvedModel,
|
|
43
36
|
type ResolveModelOptions,
|
|
44
37
|
type WorkspaceClientLike,
|
|
45
|
-
} from "./
|
|
38
|
+
} from "./model-catalog.ts";
|
|
39
|
+
import { endpointSupportsTools } from "./policy.ts";
|
|
46
40
|
|
|
47
41
|
type ModelClass = model.ModelClass;
|
|
48
42
|
|
|
49
43
|
/** Preferred live family for an unconfigured general-purpose chat default. */
|
|
50
44
|
const DEFAULT_MODEL_FAMILY_SEARCH = "gpt";
|
|
51
45
|
|
|
52
|
-
const MODEL_CLASS_TO_RS: Readonly<Record<ModelClass, ModelRsModelClass>> = {
|
|
53
|
-
[model.ModelClass.ChatThinking]: ModelRsModelClass.ChatThinking,
|
|
54
|
-
[model.ModelClass.ChatBalanced]: ModelRsModelClass.ChatBalanced,
|
|
55
|
-
[model.ModelClass.ChatFast]: ModelRsModelClass.ChatFast,
|
|
56
|
-
[model.ModelClass.Embedding]: ModelRsModelClass.Embedding,
|
|
57
|
-
};
|
|
58
|
-
|
|
59
46
|
/** Caller intent passed to {@link resolveModel}. */
|
|
60
47
|
export interface ResolveModelInput {
|
|
61
48
|
/**
|
|
@@ -70,7 +57,7 @@ export interface ResolveModelInput {
|
|
|
70
57
|
* doesn't exist).
|
|
71
58
|
*/
|
|
72
59
|
fuzzy?: boolean;
|
|
73
|
-
/**
|
|
60
|
+
/** Rust fuzzy-distance threshold forwarded to the search match. */
|
|
74
61
|
threshold?: number;
|
|
75
62
|
/** Require a model that supports a complete function-tool round-trip. */
|
|
76
63
|
requiresTools?: boolean;
|
|
@@ -105,12 +92,16 @@ export interface ResolvedModelSelection {
|
|
|
105
92
|
export interface SelectModelInput extends ResolveModelInput {
|
|
106
93
|
/** TTL override for the cached `/serving-endpoints` listing, in ms. */
|
|
107
94
|
ttlMs?: number;
|
|
95
|
+
/** Trusted opaque identity of the credential used by the workspace client. */
|
|
96
|
+
cacheIdentity?: string;
|
|
108
97
|
}
|
|
109
98
|
|
|
110
99
|
/** TTL override merged into a {@link ModelQuery} for {@link searchModels}. */
|
|
111
100
|
export interface SearchModelsInput extends ModelQuery {
|
|
112
101
|
/** TTL override for the cached `/serving-endpoints` listing, in ms. */
|
|
113
102
|
ttlMs?: number;
|
|
103
|
+
/** Trusted opaque identity of the credential used by the workspace client. */
|
|
104
|
+
cacheIdentity?: string;
|
|
114
105
|
}
|
|
115
106
|
|
|
116
107
|
/**
|
|
@@ -125,64 +116,7 @@ export function lookupModels(
|
|
|
125
116
|
endpoints: readonly ServingEndpointSummary[],
|
|
126
117
|
query: ModelQuery = {},
|
|
127
118
|
): RankedModel[] {
|
|
128
|
-
|
|
129
|
-
return rankModelsWithRust(endpoints.map(toModelRsEndpoint), toModelRsQuery(query)).map(
|
|
130
|
-
(ranked) => {
|
|
131
|
-
const endpoint = endpointsByName.get(ranked.endpoint.name);
|
|
132
|
-
if (!endpoint) {
|
|
133
|
-
throw new Error(`Rust model ranking returned unknown endpoint "${ranked.endpoint.name}"`);
|
|
134
|
-
}
|
|
135
|
-
return {
|
|
136
|
-
endpoint,
|
|
137
|
-
modelClass: fromModelRsClass(ranked.modelClass),
|
|
138
|
-
...(ranked.score !== undefined ? { score: ranked.score } : {}),
|
|
139
|
-
};
|
|
140
|
-
},
|
|
141
|
-
);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/** Convert a public model query to the generated Rust ranking contract. */
|
|
145
|
-
function toModelRsQuery(query: ModelQuery): ModelRsModelQuery {
|
|
146
|
-
return {
|
|
147
|
-
search: query.search,
|
|
148
|
-
modelClass: query.modelClass === undefined ? undefined : MODEL_CLASS_TO_RS[query.modelClass],
|
|
149
|
-
requiresTools: query.requiresTools ?? false,
|
|
150
|
-
includeDeprecated: false,
|
|
151
|
-
limit: query.limit,
|
|
152
|
-
threshold: query.threshold,
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/** Convert a discovered endpoint to the generated Rust ranking contract. */
|
|
157
|
-
function toModelRsEndpoint(endpoint: ServingEndpointSummary): ModelRsServingEndpointSummary {
|
|
158
|
-
return {
|
|
159
|
-
name: endpoint.name,
|
|
160
|
-
displayName: endpoint.displayName,
|
|
161
|
-
task: endpoint.task,
|
|
162
|
-
state: endpoint.state,
|
|
163
|
-
description: endpoint.description,
|
|
164
|
-
supportsTools: endpoint.supportsTools,
|
|
165
|
-
profile: endpoint.profile,
|
|
166
|
-
modelClass: endpoint.class === undefined ? undefined : MODEL_CLASS_TO_RS[endpoint.class],
|
|
167
|
-
serviceNames: new Map(Object.entries(endpoint.serviceNames ?? {})),
|
|
168
|
-
modelServiceName: endpoint.modelServiceName,
|
|
169
|
-
reasoningEfforts: [],
|
|
170
|
-
status: { deprecated: endpoint.status?.deprecated ?? false },
|
|
171
|
-
};
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/** Convert a generated Rust class to the browser-safe public enum. */
|
|
175
|
-
function fromModelRsClass(modelClass: ModelRsModelClass): ModelClass {
|
|
176
|
-
switch (modelClass) {
|
|
177
|
-
case ModelRsModelClass.ChatThinking:
|
|
178
|
-
return model.ModelClass.ChatThinking;
|
|
179
|
-
case ModelRsModelClass.ChatBalanced:
|
|
180
|
-
return model.ModelClass.ChatBalanced;
|
|
181
|
-
case ModelRsModelClass.ChatFast:
|
|
182
|
-
return model.ModelClass.ChatFast;
|
|
183
|
-
case ModelRsModelClass.Embedding:
|
|
184
|
-
return model.ModelClass.Embedding;
|
|
185
|
-
}
|
|
119
|
+
return rankEndpoints(endpoints, query);
|
|
186
120
|
}
|
|
187
121
|
|
|
188
122
|
/**
|
|
@@ -190,10 +124,10 @@ function fromModelRsClass(modelClass: ModelRsModelClass): ModelClass {
|
|
|
190
124
|
* in a catalogue snapshot, or the input verbatim when nothing scores within the
|
|
191
125
|
* threshold.
|
|
192
126
|
*
|
|
193
|
-
* The
|
|
127
|
+
* The ranked counterpart to {@link resolveModelId}: equal
|
|
194
128
|
* match scores are broken by class and then within-class version, so a loose
|
|
195
129
|
* `"opus"` prefers `opus-5` over `opus-4-7` instead of picking whichever
|
|
196
|
-
* sibling
|
|
130
|
+
* sibling appeared first. Returning the input unmatched (rather
|
|
197
131
|
* than a near neighbour) is deliberate - a deliberate endpoint id is never
|
|
198
132
|
* silently rewritten, and Databricks surfaces a clean 404.
|
|
199
133
|
*/
|
|
@@ -251,11 +185,10 @@ export async function searchModels(
|
|
|
251
185
|
host: string,
|
|
252
186
|
input: SearchModelsInput = {},
|
|
253
187
|
): Promise<RankedModel[]> {
|
|
254
|
-
const endpoints = await listServingEndpoints(
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
);
|
|
188
|
+
const endpoints = await listServingEndpoints(client, host, {
|
|
189
|
+
...(input.ttlMs !== undefined ? { ttlMs: input.ttlMs } : {}),
|
|
190
|
+
...(input.cacheIdentity !== undefined ? { cacheIdentity: input.cacheIdentity } : {}),
|
|
191
|
+
});
|
|
259
192
|
return lookupModels(endpoints, input);
|
|
260
193
|
}
|
|
261
194
|
|
|
@@ -283,6 +216,7 @@ export async function selectModel(
|
|
|
283
216
|
}
|
|
284
217
|
const endpoints = await listServingEndpoints(client, host, {
|
|
285
218
|
...(input.ttlMs !== undefined ? { ttlMs: input.ttlMs } : {}),
|
|
219
|
+
...(input.cacheIdentity !== undefined ? { cacheIdentity: input.cacheIdentity } : {}),
|
|
286
220
|
});
|
|
287
221
|
return resolveModel(endpoints, input);
|
|
288
222
|
}
|
|
@@ -321,7 +255,7 @@ export function resolveModel(
|
|
|
321
255
|
if (input.modelClass === undefined && input.fallbacks && input.fallbacks.length > 0) {
|
|
322
256
|
const present = new Set(
|
|
323
257
|
endpoints
|
|
324
|
-
.filter((endpoint) => !input.requiresTools ||
|
|
258
|
+
.filter((endpoint) => !input.requiresTools || endpointSupportsTools(endpoint))
|
|
325
259
|
.map((endpoint) => endpoint.name),
|
|
326
260
|
);
|
|
327
261
|
const pinned = input.fallbacks.find((id) => present.has(id));
|
|
@@ -346,9 +280,7 @@ export function resolveModel(
|
|
|
346
280
|
const floor = object.sequence(floorSource).concat(FALLBACK_MODEL_IDS).distinct().toArray();
|
|
347
281
|
if (input.requiresTools) {
|
|
348
282
|
const available = new Set(
|
|
349
|
-
endpoints
|
|
350
|
-
.filter((endpoint) => classify.endpointCapabilities(endpoint).tools)
|
|
351
|
-
.map((endpoint) => endpoint.name),
|
|
283
|
+
endpoints.filter(endpointSupportsTools).map((endpoint) => endpoint.name),
|
|
352
284
|
);
|
|
353
285
|
const selected = floor.find((id) => available.has(id));
|
|
354
286
|
if (!selected) throw new Error("No tool-capable model is available");
|
|
@@ -371,7 +303,7 @@ function buildQuery(input: ResolveModelInput, search: string | undefined): Model
|
|
|
371
303
|
/** Throw when an explicit id is absent or not verified for function tools. */
|
|
372
304
|
function assertToolSupport(endpoints: readonly ServingEndpointSummary[], modelId: string): void {
|
|
373
305
|
const endpoint = endpoints.find((candidate) => candidate.name === modelId);
|
|
374
|
-
if (!endpoint || !
|
|
306
|
+
if (!endpoint || !endpointSupportsTools(endpoint)) {
|
|
375
307
|
throw new Error(`Model "${modelId}" does not support function tools`);
|
|
376
308
|
}
|
|
377
309
|
}
|
package/lib/src/serving.js
DELETED
|
@@ -1,292 +0,0 @@
|
|
|
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 { CacheManager } from "@databricks/appkit";
|
|
24
|
-
import { error, log, string } from "@dbx-tools/shared-core";
|
|
25
|
-
import { classify, display, model, } from "@dbx-tools/shared-model";
|
|
26
|
-
import Fuse from "fuse.js";
|
|
27
|
-
import { MODEL_CLASS_ORDER } from "./classes.js";
|
|
28
|
-
import { modelFamily, modelReasoningEfforts } from "./policy.js";
|
|
29
|
-
const { ModelClass } = model;
|
|
30
|
-
const logger = log.logger("model/serving");
|
|
31
|
-
/** Default TTL for the in-memory endpoint cache. Matches the Databricks SDK's session lifetime budget. */
|
|
32
|
-
export const DEFAULT_MODEL_CACHE_TTL_MS = 5 * 60 * 1000;
|
|
33
|
-
/** Default Fuse.js score threshold below which a fuzzy match is accepted. */
|
|
34
|
-
export const DEFAULT_FUZZY_THRESHOLD = 0.4;
|
|
35
|
-
/** Cache key parts under which endpoint listings are stored. */
|
|
36
|
-
const CACHE_KEY_NAMESPACE = "serving-endpoints";
|
|
37
|
-
/**
|
|
38
|
-
* Stable `userKey` arg for AppKit's `CacheManager.getOrExecute`. Endpoint
|
|
39
|
-
* visibility is effectively workspace-scoped (we cache by host in the key
|
|
40
|
-
* parts), so a single shared key lets every user of the same workspace share
|
|
41
|
-
* one cached fetch and coalesce on the in-flight promise. Permissions can
|
|
42
|
-
* differ in theory, but the Foundation Model API catalogue is the same view for
|
|
43
|
-
* every caller.
|
|
44
|
-
*/
|
|
45
|
-
const SHARED_USER_KEY = "model-shared";
|
|
46
|
-
/**
|
|
47
|
-
* List Model Serving endpoints for the workspace owning `client`, routed
|
|
48
|
-
* through AppKit's `CacheManager`. The manager gives us everything
|
|
49
|
-
* `cachetools.TTLCache` provides plus what `cachetools-async` adds on top:
|
|
50
|
-
* per-entry TTL, in-flight request coalescing (concurrent callers share one
|
|
51
|
-
* fetch via the manager's internal `inFlightRequests` map), bounded size,
|
|
52
|
-
* telemetry spans (`cache.getOrExecute`), and optional Lakebase persistence so
|
|
53
|
-
* the catalogue survives restarts when the lakebase plugin is wired up.
|
|
54
|
-
*
|
|
55
|
-
* Returns plain {@link ServingEndpointSummary} objects (a stable subset of the
|
|
56
|
-
* SDK type) so cache hits never expose stale SDK internals. Errors from
|
|
57
|
-
* `CacheManager` or the SDK fetch propagate to the caller - we don't swallow
|
|
58
|
-
* them so users see the real auth / network issue.
|
|
59
|
-
*
|
|
60
|
-
* @param host - Workspace host used as the cache key. Pass the value resolved
|
|
61
|
-
* from `client.config.getHost()` so multi-host apps share one entry per
|
|
62
|
-
* workspace.
|
|
63
|
-
* @param options.ttlMs - Override the default TTL just for this call. Forwarded
|
|
64
|
-
* to `CacheManager` as seconds.
|
|
65
|
-
*/
|
|
66
|
-
export async function listServingEndpoints(client, host, options = {}) {
|
|
67
|
-
const ttlSec = Math.max(1, Math.round((options.ttlMs ?? DEFAULT_MODEL_CACHE_TTL_MS) / 1000));
|
|
68
|
-
return CacheManager.getInstanceSync().getOrExecute([CACHE_KEY_NAMESPACE, host], () => fetchEndpoints(client), SHARED_USER_KEY, { ttl: ttlSec });
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* List the workspace's serving endpoints as minimal
|
|
72
|
-
* {@link ServingEndpointSummary} objects straight from the SDK: no caching, and
|
|
73
|
-
* none of the cache-load enrichment ({@link listServingEndpoints} adds the
|
|
74
|
-
* {@link ModelClass} stamp and the embedding-dimension probe). Use this for a
|
|
75
|
-
* one-shot, dependency-light listing - e.g. a CLI that only needs names/tasks
|
|
76
|
-
* for fuzzy resolution and doesn't want AppKit's `CacheManager` or the
|
|
77
|
-
* per-embedding ping cost. Prefer {@link listServingEndpoints} for the cached,
|
|
78
|
-
* enriched view.
|
|
79
|
-
*/
|
|
80
|
-
export async function listServingEndpointsUncached(client) {
|
|
81
|
-
const out = [];
|
|
82
|
-
for await (const ep of client.servingEndpoints.list()) {
|
|
83
|
-
if (!ep.name)
|
|
84
|
-
continue;
|
|
85
|
-
const profile = extractProfile(ep);
|
|
86
|
-
const family = modelFamily(ep.name);
|
|
87
|
-
const reasoningEfforts = modelReasoningEfforts(ep.name);
|
|
88
|
-
out.push({
|
|
89
|
-
name: ep.name,
|
|
90
|
-
// Prefer a Databricks-provided human name (a display-name tag or an
|
|
91
|
-
// external-model name); else derive a title-cased label from the id.
|
|
92
|
-
displayName: display.toModelDisplayName(ep.name, providedDisplayName(ep)),
|
|
93
|
-
...(family !== undefined ? { family } : {}),
|
|
94
|
-
...(ep.task !== undefined ? { task: ep.task } : {}),
|
|
95
|
-
...(ep.state?.ready !== undefined ? { state: String(ep.state.ready) } : {}),
|
|
96
|
-
...(ep.description !== undefined ? { description: ep.description } : {}),
|
|
97
|
-
supportsTools: classify.supportsToolsByFamily(ep.name),
|
|
98
|
-
...(profile ? { profile } : {}),
|
|
99
|
-
...(reasoningEfforts.length > 0 ? { reasoningEfforts } : {}),
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
return out;
|
|
103
|
-
}
|
|
104
|
-
/**
|
|
105
|
-
* Pull a Databricks-provided human name off a serving endpoint, if any:
|
|
106
|
-
* a `display_name` / `displayName` / `name` tag first, then an
|
|
107
|
-
* external-model `name` from the served-entity config. Returns `null`
|
|
108
|
-
* when the endpoint carries no explicit name (the common case for
|
|
109
|
-
* Foundation Model API endpoints), so the caller falls back to deriving
|
|
110
|
-
* one from the id.
|
|
111
|
-
*/
|
|
112
|
-
function providedDisplayName(ep) {
|
|
113
|
-
const tag = ep.tags?.find((t) => t.key === "display_name" || t.key === "displayName" || t.key === "name");
|
|
114
|
-
const fromTag = string.trimToNull(tag?.value);
|
|
115
|
-
if (fromTag)
|
|
116
|
-
return fromTag;
|
|
117
|
-
for (const entity of ep.config?.served_entities ?? []) {
|
|
118
|
-
const external = string.trimToNull(entity.external_model?.name);
|
|
119
|
-
if (external)
|
|
120
|
-
return external;
|
|
121
|
-
}
|
|
122
|
-
return null;
|
|
123
|
-
}
|
|
124
|
-
async function fetchEndpoints(client) {
|
|
125
|
-
const startedAt = Date.now();
|
|
126
|
-
const out = await listServingEndpointsUncached(client);
|
|
127
|
-
stampModelClasses(out);
|
|
128
|
-
await measureEmbeddingDimensions(client, out);
|
|
129
|
-
logger.debug("listed", { count: out.length, elapsedMs: Date.now() - startedAt });
|
|
130
|
-
return out;
|
|
131
|
-
}
|
|
132
|
-
/**
|
|
133
|
-
* Stamp each summary's {@link ServingEndpointSummary.class} from the relative
|
|
134
|
-
* classification of the whole set. Mutates `summaries` in place. Endpoints the
|
|
135
|
-
* classifier doesn't recognize (custom, unscored, non-LLM) are left without a
|
|
136
|
-
* class.
|
|
137
|
-
*/
|
|
138
|
-
function stampModelClasses(summaries) {
|
|
139
|
-
const buckets = classify.classifyEndpoints(summaries);
|
|
140
|
-
const classOf = new Map();
|
|
141
|
-
for (const cls of MODEL_CLASS_ORDER) {
|
|
142
|
-
for (const ep of buckets[cls])
|
|
143
|
-
classOf.set(ep.name, cls);
|
|
144
|
-
}
|
|
145
|
-
for (const summary of summaries) {
|
|
146
|
-
const cls = classOf.get(summary.name);
|
|
147
|
-
if (cls !== undefined)
|
|
148
|
-
summary.class = cls;
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
/**
|
|
152
|
-
* Measure the embedding vector dimension of every {@link ModelClass.Embedding}
|
|
153
|
-
* endpoint by pinging it once, all in parallel. Mutates the matching summaries
|
|
154
|
-
* in place with the resulting `dimension`. Runs only on a cache miss (it's
|
|
155
|
-
* called from {@link fetchEndpoints}), so the probe cost is amortized across the
|
|
156
|
-
* cached TTL window. Per-endpoint failures are swallowed (logged at warn) so
|
|
157
|
-
* one unreachable embedding model never fails the listing.
|
|
158
|
-
*/
|
|
159
|
-
async function measureEmbeddingDimensions(client, summaries) {
|
|
160
|
-
await Promise.all(summaries
|
|
161
|
-
.filter((s) => s.class === ModelClass.Embedding)
|
|
162
|
-
.map(async (summary) => {
|
|
163
|
-
const dimension = await pingEmbeddingDimension(client, summary.name);
|
|
164
|
-
if (dimension !== undefined)
|
|
165
|
-
summary.dimension = dimension;
|
|
166
|
-
}));
|
|
167
|
-
}
|
|
168
|
-
/**
|
|
169
|
-
* Best-effort embedding dimension probe: query `name` with a tiny `"ping"`
|
|
170
|
-
* input and return the length of the returned vector. Returns `undefined` (and
|
|
171
|
-
* logs at warn) when the endpoint can't be queried or returns no vector - the
|
|
172
|
-
* dimension is informational, never required.
|
|
173
|
-
*/
|
|
174
|
-
async function pingEmbeddingDimension(client, name) {
|
|
175
|
-
try {
|
|
176
|
-
const response = await client.servingEndpoints.query({ name, input: "ping" });
|
|
177
|
-
const dimension = response.data?.[0]?.embedding?.length;
|
|
178
|
-
if (typeof dimension === "number" && dimension > 0)
|
|
179
|
-
return dimension;
|
|
180
|
-
logger.warn("embedding ping returned no vector", { name });
|
|
181
|
-
return undefined;
|
|
182
|
-
}
|
|
183
|
-
catch (err) {
|
|
184
|
-
logger.warn("embedding ping failed", { name, error: error.errorMessage(err) });
|
|
185
|
-
return undefined;
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
/**
|
|
189
|
-
* Pull the Foundation Model API `quality` / `speed` / `cost` scores off a
|
|
190
|
-
* serving-endpoint listing entry. Databricks returns these under
|
|
191
|
-
* `config.served_entities[].foundation_model.ai_gateway_model_profile`
|
|
192
|
-
* (snake_case at the wire level, preserved verbatim by the SDK), but the field
|
|
193
|
-
* is newer than the typed `FoundationModel` interface, so we read it through a
|
|
194
|
-
* structural cast. Returns `undefined` when no served entity carries a profile
|
|
195
|
-
* (custom models, embeddings, and brand-new endpoints that Databricks has not
|
|
196
|
-
* scored yet).
|
|
197
|
-
*/
|
|
198
|
-
function extractProfile(ep) {
|
|
199
|
-
const entities = ep.config?.served_entities;
|
|
200
|
-
if (!entities)
|
|
201
|
-
return undefined;
|
|
202
|
-
for (const entity of entities) {
|
|
203
|
-
const raw = entity.foundation_model?.ai_gateway_model_profile;
|
|
204
|
-
if (!raw)
|
|
205
|
-
continue;
|
|
206
|
-
const profile = {};
|
|
207
|
-
if (Number.isFinite(raw.quality))
|
|
208
|
-
profile.quality = raw.quality;
|
|
209
|
-
if (Number.isFinite(raw.speed))
|
|
210
|
-
profile.speed = raw.speed;
|
|
211
|
-
if (Number.isFinite(raw.cost))
|
|
212
|
-
profile.cost = raw.cost;
|
|
213
|
-
if (Object.keys(profile).length > 0)
|
|
214
|
-
return profile;
|
|
215
|
-
}
|
|
216
|
-
return undefined;
|
|
217
|
-
}
|
|
218
|
-
/**
|
|
219
|
-
* Force-evict cached endpoint listings via AppKit's `CacheManager`. With a
|
|
220
|
-
* `host` deletes that one workspace's entry; without one clears every cache
|
|
221
|
-
* entry on the manager (since `CacheManager` doesn't expose a namespace-scoped
|
|
222
|
-
* clear, this is the brute-force path - fine for tests, avoid in steady-state
|
|
223
|
-
* code).
|
|
224
|
-
*/
|
|
225
|
-
export async function clearServingEndpointsCache(host) {
|
|
226
|
-
const cache = CacheManager.getInstanceSync();
|
|
227
|
-
if (host) {
|
|
228
|
-
const key = cache.generateKey([CACHE_KEY_NAMESPACE, host], SHARED_USER_KEY);
|
|
229
|
-
await cache.delete(key);
|
|
230
|
-
}
|
|
231
|
-
else {
|
|
232
|
-
await cache.clear();
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
/**
|
|
236
|
-
* Fuzzy-rank endpoints by how closely their `name` matches `input`, best
|
|
237
|
-
* (lowest score) first, keeping only those within `threshold`:
|
|
238
|
-
*
|
|
239
|
-
* 1. An exact name match short-circuits to a single `score: 0` result.
|
|
240
|
-
* 2. Otherwise the input is tokenized (dashes / underscores / spaces become
|
|
241
|
-
* separators) and fed through Fuse.js extended search, which AND-s each
|
|
242
|
-
* token with fuzzy matching enabled - the "tokenized fuzzy match" a caller
|
|
243
|
-
* reaches for when they type `"claude sonnet"` instead of the full endpoint
|
|
244
|
-
* name.
|
|
245
|
-
*
|
|
246
|
-
* Returns `[]` for an empty endpoint list or when `input` tokenizes to nothing,
|
|
247
|
-
* so callers fall back to the raw input and let Databricks surface a clean 404.
|
|
248
|
-
* This multi-result core is shared by {@link resolveModelId} (single best) and
|
|
249
|
-
* the ranked `lookupModels` selector.
|
|
250
|
-
*/
|
|
251
|
-
export function searchServingEndpoints(input, endpoints, options = {}) {
|
|
252
|
-
if (endpoints.length === 0)
|
|
253
|
-
return [];
|
|
254
|
-
for (const ep of endpoints) {
|
|
255
|
-
if (ep.name === input)
|
|
256
|
-
return [{ endpoint: ep, score: 0 }];
|
|
257
|
-
}
|
|
258
|
-
const threshold = options.threshold ?? DEFAULT_FUZZY_THRESHOLD;
|
|
259
|
-
// Fuse 7.3 has no built-in tokenize hook; in extended search, space-separated
|
|
260
|
-
// tokens are AND-ed with fuzzy matching enabled. We lean on the shared
|
|
261
|
-
// tokenizer so the splitting rules stay consistent with the rest of the toolkit.
|
|
262
|
-
const query = Array.from(string.tokenizeWithOptions({ lowerCase: true, camelCase: false }, input)).join(" ");
|
|
263
|
-
if (!query)
|
|
264
|
-
return [];
|
|
265
|
-
const fuse = new Fuse(endpoints, {
|
|
266
|
-
keys: ["name"],
|
|
267
|
-
threshold,
|
|
268
|
-
ignoreLocation: true,
|
|
269
|
-
includeScore: true,
|
|
270
|
-
useExtendedSearch: true,
|
|
271
|
-
isCaseSensitive: false,
|
|
272
|
-
});
|
|
273
|
-
return fuse
|
|
274
|
-
.search(query)
|
|
275
|
-
.filter((r) => (r.score ?? 0) <= threshold)
|
|
276
|
-
.map((r) => ({ endpoint: r.item, score: r.score ?? 0 }));
|
|
277
|
-
}
|
|
278
|
-
/**
|
|
279
|
-
* Snap a user-supplied model name to the single closest configured serving
|
|
280
|
-
* endpoint via {@link searchServingEndpoints}. Returns the input unchanged with
|
|
281
|
-
* `matched: false` when nothing scores within the threshold (or the catalogue
|
|
282
|
-
* is empty), so a deliberate model id is never silently rewritten to a
|
|
283
|
-
* similar-looking neighbour and the upstream call surfaces the canonical 404.
|
|
284
|
-
*/
|
|
285
|
-
export function resolveModelId(input, endpoints, options = {}) {
|
|
286
|
-
const [best] = searchServingEndpoints(input, endpoints, options);
|
|
287
|
-
if (best) {
|
|
288
|
-
return { modelId: best.endpoint.name, matched: true, score: best.score };
|
|
289
|
-
}
|
|
290
|
-
return { modelId: input, matched: false };
|
|
291
|
-
}
|
|
292
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VydmluZy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9zZXJ2aW5nLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0FxQkc7QUFFSCxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFFbEQsT0FBTyxFQUFFLEtBQUssRUFBRSxHQUFHLEVBQUUsTUFBTSxFQUFFLE1BQU0sd0JBQXdCLENBQUM7QUFDNUQsT0FBTyxFQUNMLFFBQVEsRUFDUixPQUFPLEVBQ1AsS0FBSyxHQUdOLE1BQU0seUJBQXlCLENBQUM7QUFDakMsT0FBTyxJQUFJLE1BQU0sU0FBUyxDQUFDO0FBRTNCLE9BQU8sRUFBRSxpQkFBaUIsRUFBRSxNQUFNLGNBQWMsQ0FBQztBQUNqRCxPQUFPLEVBQUUsV0FBVyxFQUFFLHFCQUFxQixFQUFFLE1BQU0sYUFBYSxDQUFDO0FBRWpFLE1BQU0sRUFBRSxVQUFVLEVBQUUsR0FBRyxLQUFLLENBQUM7QUFFN0IsTUFBTSxNQUFNLEdBQUcsR0FBRyxDQUFDLE1BQU0sQ0FBQyxlQUFlLENBQUMsQ0FBQztBQVMzQywwR0FBMEc7QUFDMUcsTUFBTSxDQUFDLE1BQU0sMEJBQTBCLEdBQUcsQ0FBQyxHQUFHLEVBQUUsR0FBRyxJQUFJLENBQUM7QUFFeEQsNkVBQTZFO0FBQzdFLE1BQU0sQ0FBQyxNQUFNLHVCQUF1QixHQUFHLEdBQUcsQ0FBQztBQUUzQyxnRUFBZ0U7QUFDaEUsTUFBTSxtQkFBbUIsR0FBRyxtQkFBbUIsQ0FBQztBQUVoRDs7Ozs7OztHQU9HO0FBQ0gsTUFBTSxlQUFlLEdBQUcsY0FBYyxDQUFDO0FBV3ZDOzs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBbUJHO0FBQ0gsTUFBTSxDQUFDLEtBQUssVUFBVSxvQkFBb0IsQ0FDeEMsTUFBMkIsRUFDM0IsSUFBWSxFQUNaLFVBQXVDLEVBQUU7SUFFekMsTUFBTSxNQUFNLEdBQUcsSUFBSSxDQUFDLEdBQUcsQ0FBQyxD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LEVBQUUsS0FBSyxFQUFFLElBQUksQ0FBQyxLQUFLLEVBQUUsQ0FBQztJQUMzRSxDQUFDO0lBQ0QsT0FBTyxFQUFFLE9BQU8sRUFBRSxLQUFLLEVBQUUsT0FBTyxFQUFFLEtBQUssRUFBRSxDQUFDO0FBQzVDLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIExpdmUgRGF0YWJyaWNrcyBNb2RlbCBTZXJ2aW5nIGNhdGFsb2d1ZSBhY2Nlc3MuXG4gKlxuICogTGlzdHMgdGhlIHdvcmtzcGFjZSdzIGAvc2VydmluZy1lbmRwb2ludHNgIG9uY2UgcGVyIGhvc3QgYW5kIGNhY2hlcyB0aGVcbiAqIHJlc3VsdCB3aXRoIGEgVFRMIHZpYSBBcHBLaXQncyBgQ2FjaGVNYW5hZ2VyYCwgd2l0aCBjb25jdXJyZW50IGNhbGxlcnNcbiAqIHNoYXJpbmcgb25lIGluLWZsaWdodCBwcm9taXNlICh0aGUgY29hbGVzY2luZyBwYXR0ZXJuIG9mIFB5dGhvbidzXG4gKiBgY2FjaGV0b29scy1hc3luY2ApLiBTdXJmYWNlcyBlYWNoIGVuZHBvaW50IGFzIGEgc3RhYmxlXG4gKiB7QGxpbmsgU2VydmluZ0VuZHBvaW50U3VtbWFyeX0gLSBpbmNsdWRpbmcgdGhlIEZvdW5kYXRpb24gTW9kZWwgQVBJXG4gKiBgcXVhbGl0eWAgLyBgc3BlZWRgIC8gYGNvc3RgIHByb2ZpbGUgd2hlbiBwcmVzZW50LCB0aGUgY2xhc3NpZmllZFxuICoge0BsaW5rIE1vZGVsQ2xhc3N9LCBhbmQgKGZvciBlbWJlZGRpbmcgZW5kcG9pbnRzKSB0aGUgbWVhc3VyZWQgdmVjdG9yXG4gKiBgZGltZW5zaW9uYCAtIGFuZCBzbmFwcyBsb29zZSwgaHVtYW4tdHlwZWQgbmFtZXMgdG8gcmVhbCBlbmRwb2ludCBpZHNcbiAqIHRocm91Z2ggYGZ1c2UuanNgIGV4dGVuZGVkIHNlYXJjaCBzbyB0b2tlbnMgbGlrZSBgXCJjbGF1ZGUgc29ubmV0XCJgIHJlc29sdmUgdG9cbiAqIGBkYXRhYnJpY2tzLWNsYXVkZS1zb25uZXQtNC02YC5cbiAqXG4gKiBUaGUgY2xhc3Mgc3RhbXAgYW5kIGVtYmVkZGluZyBkaW1lbnNpb24gYXJlIGNvbXB1dGVkIG9uY2UgcGVyIGNhY2hlIGxvYWQ6XG4gKiBldmVyeSBlbWJlZGRpbmcgZW5kcG9pbnQgaXMgXCJwaW5nZWRcIiBpbiBwYXJhbGxlbCBhbmQgdGhlIHJlc3VsdGluZyB2ZWN0b3JcbiAqIGxlbmd0aCByZWNvcmRlZCwgc28gdGhlIGNvc3QgaXMgcGFpZCBvbiBhIGNhY2hlIG1pc3MsIG5vdCBwZXIgcmVhZC4gVGhlIHBpbmdcbiAqIGlzIGJlc3QtZWZmb3J0IC0gYSBmYWlsdXJlIGxvZ3MgYXQgZGVidWcgYW5kIGxlYXZlcyBgZGltZW5zaW9uYCB1bnNldCByYXRoZXJcbiAqIHRoYW4gZmFpbGluZyB0aGUgd2hvbGUgbGlzdGluZy5cbiAqXG4gKiBAbW9kdWxlXG4gKi9cblxuaW1wb3J0IHsgQ2FjaGVNYW5hZ2VyIH0gZnJvbSBcIkBkYXRhYnJpY2tzL2FwcGtpdFwiO1xuaW1wb3J0IHsgYXBwa2l0IH0gZnJvbSBcIkBkYngtdG9vbHMvYXBwa2l0XCI7XG5pbXBvcnQgeyBlcnJvciwgbG9nLCBzdHJpbmcgfSBmcm9tIFwiQGRieC10b29scy9zaGFyZWQtY29yZVwiO1xuaW1wb3J0IHtcbiAgY2xhc3NpZnksXG4gIGRpc3BsYXksXG4gIG1vZGVsLFxuICB0eXBlIE1vZGVsUHJvZmlsZSxcbiAgdHlwZSBTZXJ2aW5nRW5kcG9pbnRTdW1tYXJ5LFxufSBmcm9tIFwiQGRieC10b29scy9zaGFyZWQtbW9kZWxcIjtcbmltcG9ydCBGdXNlIGZyb20gXCJmdXNlLmpzXCI7XG5cbmltcG9ydCB7IE1PREVMX0NMQVNTX09SREVSIH0gZnJvbSBcIi4vY2xhc3Nlcy50c1wiO1xuaW1wb3J0IHsgbW9kZWxGYW1pbHksIG1vZGVsUmVhc29uaW5nRWZmb3J0cyB9IGZyb20gXCIuL3BvbGljeS50c1wiO1xuXG5jb25zdCB7IE1vZGVsQ2xhc3MgfSA9IG1vZGVsO1xuXG5jb25zdCBsb2dnZXIgPSBsb2cubG9nZ2VyKFwibW9kZWwvc2VydmluZ1wiKTtcblxuLyoqXG4gKiBTdHJ1Y3R1cmFsIHR5cGUgZm9yIHRoZSBEYXRhYnJpY2tzIHdvcmtzcGFjZSBjbGllbnQsIHJlLWV4cG9ydGVkIHNvIHRoZSByZXN0XG4gKiBvZiB0aGlzIHBhY2thZ2UgY2FuIGtlZXAgaW1wb3J0aW5nIGl0IGZyb20gaGVyZS4gU2VlXG4gKiBgYXBwa2l0LldvcmtzcGFjZUNsaWVudExpa2VgIChub2RlLWFwcGtpdCkgZm9yIHRoZSBjYW5vbmljYWwgZGVmaW5pdGlvbi5cbiAqL1xuZXhwb3J0IHR5cGUgV29ya3NwYWNlQ2xpZW50TGlrZSA9IFBpY2s8YXBwa2l0LldvcmtzcGFjZUNsaWVudExpa2UsIFwic2VydmluZ0VuZHBvaW50c1wiPjtcblxuLyoqIERlZmF1bHQgVFRMIGZvciB0aGUgaW4tbWVtb3J5IGVuZHBvaW50IGNhY2hlLiBNYXRjaGVzIHRoZSBEYXRhYnJpY2tzIFNESydzIHNlc3Npb24gbGlmZXRpbWUgYnVkZ2V0LiAqL1xuZXhwb3J0IGNvbnN0IERFRkFVTFRfTU9ERUxfQ0FDSEVfVFRMX01TID0gNSAqIDYwICogMTAwMDtcblxuLyoqIERlZmF1bHQgRnVzZS5qcyBzY29yZSB0aHJlc2hvbGQgYmVsb3cgd2hpY2ggYSBmdXp6eSBtYXRjaCBpcyBhY2NlcHRlZC4gKi9cbmV4cG9ydCBjb25zdCBERUZBVUxUX0ZVWlpZX1RIUkVTSE9MRCA9IDAuNDtcblxuLyoqIENhY2hlIGtleSBwYXJ0cyB1bmRlciB3aGljaCBlbmRwb2ludCBsaXN0aW5ncyBhcmUgc3RvcmVkLiAqL1xuY29uc3QgQ0FDSEVfS0VZX05BTUVTUEFDRSA9IFwic2VydmluZy1lbmRwb2ludHNcIjtcblxuLyoqXG4gKiBTdGFibGUgYHVzZXJLZXlgIGFyZyBmb3IgQXBwS2l0J3MgYENhY2hlTWFuYWdlci5nZXRPckV4ZWN1dGVgLiBFbmRwb2ludFxuICogdmlzaWJpbGl0eSBpcyBlZmZlY3RpdmVseSB3b3Jrc3BhY2Utc2NvcGVkICh3ZSBjYWNoZSBieSBob3N0IGluIHRoZSBrZXlcbiAqIHBhcnRzKSwgc28gYSBzaW5nbGUgc2hhcmVkIGtleSBsZXRzIGV2ZXJ5IHVzZXIgb2YgdGhlIHNhbWUgd29ya3NwYWNlIHNoYXJlXG4gKiBvbmUgY2FjaGVkIGZldGNoIGFuZCBjb2FsZXNjZSBvbiB0aGUgaW4tZmxpZ2h0IHByb21pc2UuIFBlcm1pc3Npb25zIGNhblxuICogZGlmZmVyIGluIHRoZW9yeSwgYnV0IHRoZSBGb3VuZGF0aW9uIE1vZGVsIEFQSSBjYXRhbG9ndWUgaXMgdGhlIHNhbWUgdmlldyBmb3JcbiAqIGV2ZXJ5IGNhbGxlci5cbiAqL1xuY29uc3QgU0hBUkVEX1VTRVJfS0VZID0gXCJtb2RlbC1zaGFyZWRcIjtcblxuLyoqIE9wdGlvbnMgZm9yIHtAbGluayBsaXN0U2VydmluZ0VuZHBvaW50c30uICovXG5leHBvcnQgaW50ZXJmYWNlIExpc3RTZXJ2aW5nRW5kcG9pbnRzT3B0aW9ucyB7XG4gIC8qKlxuICAgKiBPdmVycmlkZSB0aGUgZGVmYXVsdCBjYWNoZSBUVEwgZm9yIHRoaXMgY2FsbCwgaW4gbWlsbGlzZWNvbmRzLiBGb3J3YXJkZWQgdG9cbiAgICogYENhY2hlTWFuYWdlcmAgYXMgc2Vjb25kcy5cbiAgICovXG4gIHR0bE1zPzogbnVtYmVyO1xufVxuXG4vKipcbiAqIExpc3QgTW9kZWwgU2VydmluZyBlbmRwb2ludHMgZm9yIHRoZSB3b3Jrc3BhY2Ugb3duaW5nIGBjbGllbnRgLCByb3V0ZWRcbiAqIHRocm91Z2ggQXBwS2l0J3MgYENhY2hlTWFuYWdlcmAuIFRoZSBtYW5hZ2VyIGdpdmVzIHVzIGV2ZXJ5dGhpbmdcbiAqIGBjYWNoZXRvb2xzLlRUTENhY2hlYCBwcm92aWRlcyBwbHVzIHdoYXQgYGNhY2hldG9vbHMtYXN5bmNgIGFkZHMgb24gdG9wOlxuICogcGVyLWVudHJ5IFRUTCwgaW4tZmxpZ2h0IHJlcXVlc3QgY29hbGVzY2luZyAoY29uY3VycmVudCBjYWxsZXJzIHNoYXJlIG9uZVxuICogZmV0Y2ggdmlhIHRoZSBtYW5hZ2VyJ3MgaW50ZXJuYWwgYGluRmxpZ2h0UmVxdWVzdHNgIG1hcCksIGJvdW5kZWQgc2l6ZSxcbiAqIHRlbGVtZXRyeSBzcGFucyAoYGNhY2hlLmdldE9yRXhlY3V0ZWApLCBhbmQgb3B0aW9uYWwgTGFrZWJhc2UgcGVyc2lzdGVuY2Ugc29cbiAqIHRoZSBjYXRhbG9ndWUgc3Vydml2ZXMgcmVzdGFydHMgd2hlbiB0aGUgbGFrZWJhc2UgcGx1Z2luIGlzIHdpcmVkIHVwLlxuICpcbiAqIFJldHVybnMgcGxhaW4ge0BsaW5rIFNlcnZpbmdFbmRwb2ludFN1bW1hcnl9IG9iamVjdHMgKGEgc3RhYmxlIHN1YnNldCBvZiB0aGVcbiAqIFNESyB0eXBlKSBzbyBjYWNoZSBoaXRzIG5ldmVyIGV4cG9zZSBzdGFsZSBTREsgaW50ZXJuYWxzLiBFcnJvcnMgZnJvbVxuICogYENhY2hlTWFuYWdlcmAgb3IgdGhlIFNESyBmZXRjaCBwcm9wYWdhdGUgdG8gdGhlIGNhbGxlciAtIHdlIGRvbid0IHN3YWxsb3dcbiAqIHRoZW0gc28gdXNlcnMgc2VlIHRoZSByZWFsIGF1dGggLyBuZXR3b3JrIGlzc3VlLlxuICpcbiAqIEBwYXJhbSBob3N0IC0gV29ya3NwYWNlIGhvc3QgdXNlZCBhcyB0aGUgY2FjaGUga2V5LiBQYXNzIHRoZSB2YWx1ZSByZXNvbHZlZFxuICogICBmcm9tIGBjbGllbnQuY29uZmlnLmdldEhvc3QoKWAgc28gbXVsdGktaG9zdCBhcHBzIHNoYXJlIG9uZSBlbnRyeSBwZXJcbiAqICAgd29ya3NwYWNlLlxuICogQHBhcmFtIG9wdGlvbnMudHRsTXMgLSBPdmVycmlkZSB0aGUgZGVmYXVsdCBUVEwganVzdCBmb3IgdGhpcyBjYWxsLiBGb3J3YXJkZWRcbiAqICAgdG8gYENhY2hlTWFuYWdlcmAgYXMgc2Vjb25kcy5cbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGxpc3RTZXJ2aW5nRW5kcG9pbnRzKFxuICBjbGllbnQ6IFdvcmtzcGFjZUNsaWVudExpa2UsXG4gIGhvc3Q6IHN0cmluZyxcbiAgb3B0aW9uczogTGlzdFNlcnZpbmdFbmRwb2ludHNPcHRpb25zID0ge30sXG4pOiBQcm9taXNlPFNlcnZpbmdFbmRwb2ludFN1bW1hcnlbXT4ge1xuICBjb25zdCB0dGxTZWMgPSBNYXRoLm1heCgxLCBNYXRoLnJvdW5kKChvcHRpb25zLnR0bE1zID8/IERFRkFVTFRfTU9ERUxfQ0FDSEVfVFRMX01TKSAvIDEwMDApKTtcbiAgcmV0dXJuIENhY2hlTWFuYWdlci5nZXRJbnN0YW5jZVN5bmMoKS5nZXRPckV4ZWN1dGUoXG4gICAgW0NBQ0hFX0tFWV9OQU1FU1BBQ0UsIGhvc3RdLFxuICAgICgpID0+IGZldGNoRW5kcG9pbnRzKGNsaWVudCksXG4gICAgU0hBUkVEX1VTRVJfS0VZLFxuICAgIHsgdHRsOiB0dGxTZWMgfSxcbiAgKTtcbn1cblxuLyoqXG4gKiBMaXN0IHRoZSB3b3Jrc3BhY2UncyBzZXJ2aW5nIGVuZHBvaW50cyBhcyBtaW5pbWFsXG4gKiB7QGxpbmsgU2VydmluZ0VuZHBvaW50U3VtbWFyeX0gb2JqZWN0cyBzdHJhaWdodCBmcm9tIHRoZSBTREs6IG5vIGNhY2hpbmcsIGFuZFxuICogbm9uZSBvZiB0aGUgY2FjaGUtbG9hZCBlbnJpY2htZW50ICh7QGxpbmsgbGlzdFNlcnZpbmdFbmRwb2ludHN9IGFkZHMgdGhlXG4gKiB7QGxpbmsgTW9kZWxDbGFzc30gc3RhbXAgYW5kIHRoZSBlbWJlZGRpbmctZGltZW5zaW9uIHByb2JlKS4gVXNlIHRoaXMgZm9yIGFcbiAqIG9uZS1zaG90LCBkZXBlbmRlbmN5LWxpZ2h0IGxpc3RpbmcgLSBlLmcuIGEgQ0xJIHRoYXQgb25seSBuZWVkcyBuYW1lcy90YXNrc1xuICogZm9yIGZ1enp5IHJlc29sdXRpb24gYW5kIGRvZXNuJ3Qgd2FudCBBcHBLaXQncyBgQ2FjaGVNYW5hZ2VyYCBvciB0aGVcbiAqIHBlci1lbWJlZGRpbmcgcGluZyBjb3N0LiBQcmVmZXIge0BsaW5rIGxpc3RTZXJ2aW5nRW5kcG9pbnRzfSBmb3IgdGhlIGNhY2hlZCxcbiAqIGVucmljaGVkIHZpZXcuXG4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBsaXN0U2VydmluZ0VuZHBvaW50c1VuY2FjaGVkKFxuICBjbGllbnQ6IFdvcmtzcGFjZUNsaWVudExpa2UsXG4pOiBQcm9taXNlPFNlcnZpbmdFbmRwb2ludFN1bW1hcnlbXT4ge1xuICBjb25zdCBvdXQ6IFNlcnZpbmdFbmRwb2ludFN1bW1hcnlbXSA9IFtdO1xuICBmb3IgYXdhaXQgKGNvbnN0IGVwIG9mIGNsaWVudC5zZXJ2aW5nRW5kcG9pbnRzLmxpc3QoKSkge1xuICAgIGlmICghZXAubmFtZSkgY29udGludWU7XG4gICAgY29uc3QgcHJvZmlsZSA9IGV4dHJhY3RQcm9maWxlKGVwKTtcbiAgICBjb25zdCBmYW1pbHkgPSBtb2RlbEZhbWlseShlcC5uYW1lKTtcbiAgICBjb25zdCByZWFzb25pbmdFZmZvcnRzID0gbW9kZWxSZWFzb25pbmdFZmZvcnRzKGVwLm5hbWUpO1xuICAgIG91dC5wdXNoKHtcbiAgICAgIG5hbWU6IGVwLm5hbWUsXG4gICAgICAvLyBQcmVmZXIgYSBEYXRhYnJpY2tzLXByb3ZpZGVkIGh1bWFuIG5hbWUgKGEgZGlzcGxheS1uYW1lIHRhZyBvciBhblxuICAgICAgLy8gZXh0ZXJuYWwtbW9kZWwgbmFtZSk7IGVsc2UgZGVyaXZlIGEgdGl0bGUtY2FzZWQgbGFiZWwgZnJvbSB0aGUgaWQuXG4gICAgICBkaXNwbGF5TmFtZTogZGlzcGxheS50b01vZGVsRGlzcGxheU5hbWUoZXAubmFtZSwgcHJvdmlkZWREaXNwbGF5TmFtZShlcCkpLFxuICAgICAgLi4uKGZhbWlseSAhPT0gdW5kZWZpbmVkID8geyBmYW1pbHkgfSA6IHt9KSxcbiAgICAgIC4uLihlcC50YXNrICE9PSB1bmRlZmluZWQgPyB7IHRhc2s6IGVwLnRhc2sgfSA6IHt9KSxcbiAgICAgIC4uLihlcC5zdGF0ZT8ucmVhZHkgIT09IHVuZGVmaW5lZCA/IHsgc3RhdGU6IFN0cmluZyhlcC5zdGF0ZS5yZWFkeSkgfSA6IHt9KSxcbiAgICAgIC4uLihlcC5kZXNjcmlwdGlvbiAhPT0gdW5kZWZpbmVkID8geyBkZXNjcmlwdGlvbjogZXAuZGVzY3JpcHRpb24gfSA6IHt9KSxcbiAgICAgIHN1cHBvcnRzVG9vbHM6IGNsYXNzaWZ5LnN1cHBvcnRzVG9vbHNCeUZhbWlseShlcC5uYW1lKSxcbiAgICAgIC4uLihwcm9maWxlID8geyBwcm9maWxlIH0gOiB7fSksXG4gICAgICAuLi4ocmVhc29uaW5nRWZmb3J0cy5sZW5ndGggPiAwID8geyByZWFzb25pbmdFZmZvcnRzIH0gOiB7fSksXG4gICAgfSk7XG4gIH1cbiAgcmV0dXJuIG91dDtcbn1cblxuLyoqXG4gKiBQdWxsIGEgRGF0YWJyaWNrcy1wcm92aWRlZCBodW1hbiBuYW1lIG9mZiBhIHNlcnZpbmcgZW5kcG9pbnQsIGlmIGFueTpcbiAqIGEgYGRpc3BsYXlfbmFtZWAgLyBgZGlzcGxheU5hbWVgIC8gYG5hbWVgIHRhZyBmaXJzdCwgdGhlbiBhblxuICogZXh0ZXJuYWwtbW9kZWwgYG5hbWVgIGZyb20gdGhlIHNlcnZlZC1lbnRpdHkgY29uZmlnLiBSZXR1cm5zIGBudWxsYFxuICogd2hlbiB0aGUgZW5kcG9pbnQgY2FycmllcyBubyBleHBsaWNpdCBuYW1lICh0aGUgY29tbW9uIGNhc2UgZm9yXG4gKiBGb3VuZGF0aW9uIE1vZGVsIEFQSSBlbmRwb2ludHMpLCBzbyB0aGUgY2FsbGVyIGZhbGxzIGJhY2sgdG8gZGVyaXZpbmdcbiAqIG9uZSBmcm9tIHRoZSBpZC5cbiAqL1xuZnVuY3Rpb24gcHJvdmlkZWREaXNwbGF5TmFtZShlcDoge1xuICB0YWdzPzogQXJyYXk8eyBrZXk6IHN0cmluZzsgdmFsdWU/OiBzdHJpbmcgfT47XG4gIGNvbmZpZz86IHsgc2VydmVkX2VudGl0aWVzPzogQXJyYXk8eyBleHRlcm5hbF9tb2RlbD86IHsgbmFtZT86IHN0cmluZyB9IH0+IH07XG59KTogc3RyaW5nIHwgbnVsbCB7XG4gIGNvbnN0IHRhZyA9IGVwLnRhZ3M/LmZpbmQoXG4gICAgKHQpID0+IHQua2V5ID09PSBcImRpc3BsYXlfbmFtZVwiIHx8IHQua2V5ID09PSBcImRpc3BsYXlOYW1lXCIgfHwgdC5rZXkgPT09IFwibmFtZVwiLFxuICApO1xuICBjb25zdCBmcm9tVGFnID0gc3RyaW5nLnRyaW1Ub051bGwodGFnPy52YWx1ZSk7XG4gIGlmIChmcm9tVGFnKSByZXR1cm4gZnJvbVRhZztcbiAgZm9yIChjb25zdCBlbnRpdHkgb2YgZXAuY29uZmlnPy5zZXJ2ZWRfZW50aXRpZXMgPz8gW10pIHtcbiAgICBjb25zdCBleHRlcm5hbCA9IHN0cmluZy50cmltVG9OdWxsKGVudGl0eS5leHRlcm5hbF9tb2RlbD8ubmFtZSk7XG4gICAgaWYgKGV4dGVybmFsKSByZXR1cm4gZXh0ZXJuYWw7XG4gIH1cbiAgcmV0dXJuIG51bGw7XG59XG5cbmFzeW5jIGZ1bmN0aW9uIGZldGNoRW5kcG9pbnRzKGNsaWVudDogV29ya3NwYWNlQ2xpZW50TGlrZSk6IFByb21pc2U8U2VydmluZ0VuZHBvaW50U3VtbWFyeVtdPiB7XG4gIGNvbnN0IHN0YXJ0ZWRBdCA9IERhdGUubm93KCk7XG4gIGNvbnN0IG91dCA9IGF3YWl0IGxpc3RTZXJ2aW5nRW5kcG9pbnRzVW5jYWNoZWQoY2xpZW50KTtcbiAgc3RhbXBNb2RlbENsYXNzZXMob3V0KTtcbiAgYXdhaXQgbWVhc3VyZUVtYmVkZGluZ0RpbWVuc2lvbnMoY2xpZW50LCBvdXQpO1xuICBsb2dnZXIuZGVidWcoXCJsaXN0ZWRcIiwgeyBjb3VudDogb3V0Lmxlbmd0aCwgZWxhcHNlZE1zOiBEYXRlLm5vdygpIC0gc3RhcnRlZEF0IH0pO1xuICByZXR1cm4gb3V0O1xufVxuXG4vKipcbiAqIFN0YW1wIGVhY2ggc3VtbWFyeSdzIHtAbGluayBTZXJ2aW5nRW5kcG9pbnRTdW1tYXJ5LmNsYXNzfSBmcm9tIHRoZSByZWxhdGl2ZVxuICogY2xhc3NpZmljYXRpb24gb2YgdGhlIHdob2xlIHNldC4gTXV0YXRlcyBgc3VtbWFyaWVzYCBpbiBwbGFjZS4gRW5kcG9pbnRzIHRoZVxuICogY2xhc3NpZmllciBkb2Vzbid0IHJlY29nbml6ZSAoY3VzdG9tLCB1bnNjb3JlZCwgbm9uLUxMTSkgYXJlIGxlZnQgd2l0aG91dCBhXG4gKiBjbGFzcy5cbiAqL1xuZnVuY3Rpb24gc3RhbXBNb2RlbENsYXNzZXMoc3VtbWFyaWVzOiBTZXJ2aW5nRW5kcG9pbnRTdW1tYXJ5W10pOiB2b2lkIHtcbiAgY29uc3QgYnVja2V0cyA9IGNsYXNzaWZ5LmNsYXNzaWZ5RW5kcG9pbnRzKHN1bW1hcmllcyk7XG4gIGNvbnN0IGNsYXNzT2YgPSBuZXcgTWFwPHN0cmluZywgbW9kZWwuTW9kZWxDbGFzcz4oKTtcbiAgZm9yIChjb25zdCBjbHMgb2YgTU9ERUxfQ0xBU1NfT1JERVIpIHtcbiAgICBmb3IgKGNvbnN0IGVwIG9mIGJ1Y2tldHNbY2xzXSkgY2xhc3NPZi5zZXQoZXAubmFtZSwgY2xzKTtcbiAgfVxuICBmb3IgKGNvbnN0IHN1bW1hcnkgb2Ygc3VtbWFyaWVzKSB7XG4gICAgY29uc3QgY2xzID0gY2xhc3NPZi5nZXQoc3VtbWFyeS5uYW1lKTtcbiAgICBpZiAoY2xzICE9PSB1bmRlZmluZWQpIHN1bW1hcnkuY2xhc3MgPSBjbHM7XG4gIH1cbn1cblxuLyoqXG4gKiBNZWFzdXJlIHRoZSBlbWJlZGRpbmcgdmVjdG9yIGRpbWVuc2lvbiBvZiBldmVyeSB7QGxpbmsgTW9kZWxDbGFzcy5FbWJlZGRpbmd9XG4gKiBlbmRwb2ludCBieSBwaW5naW5nIGl0IG9uY2UsIGFsbCBpbiBwYXJhbGxlbC4gTXV0YXRlcyB0aGUgbWF0Y2hpbmcgc3VtbWFyaWVzXG4gKiBpbiBwbGFjZSB3aXRoIHRoZSByZXN1bHRpbmcgYGRpbWVuc2lvbmAuIFJ1bnMgb25seSBvbiBhIGNhY2hlIG1pc3MgKGl0J3NcbiAqIGNhbGxlZCBmcm9tIHtAbGluayBmZXRjaEVuZHBvaW50c30pLCBzbyB0aGUgcHJvYmUgY29zdCBpcyBhbW9ydGl6ZWQgYWNyb3NzIHRoZVxuICogY2FjaGVkIFRUTCB3aW5kb3cuIFBlci1lbmRwb2ludCBmYWlsdXJlcyBhcmUgc3dhbGxvd2VkIChsb2dnZWQgYXQgd2Fybikgc29cbiAqIG9uZSB1bnJlYWNoYWJsZSBlbWJlZGRpbmcgbW9kZWwgbmV2ZXIgZmFpbHMgdGhlIGxpc3RpbmcuXG4gKi9cbmFzeW5jIGZ1bmN0aW9uIG1lYXN1cmVFbWJlZGRpbmdEaW1lbnNpb25zKFxuICBjbGllbnQ6IFdvcmtzcGFjZUNsaWVudExpa2UsXG4gIHN1bW1hcmllczogU2VydmluZ0VuZHBvaW50U3VtbWFyeVtdLFxuKTogUHJvbWlzZTx2b2lkPiB7XG4gIGF3YWl0IFByb21pc2UuYWxsKFxuICAgIHN1bW1hcmllc1xuICAgICAgLmZpbHRlcigocykgPT4gcy5jbGFzcyA9PT0gTW9kZWxDbGFzcy5FbWJlZGRpbmcpXG4gICAgICAubWFwKGFzeW5jIChzdW1tYXJ5KSA9PiB7XG4gICAgICAgIGNvbnN0IGRpbWVuc2lvbiA9IGF3YWl0IHBpbmdFbWJlZGRpbmdEaW1lbnNpb24oY2xpZW50LCBzdW1tYXJ5Lm5hbWUpO1xuICAgICAgICBpZiAoZGltZW5zaW9uICE9PSB1bmRlZmluZWQpIHN1bW1hcnkuZGltZW5zaW9uID0gZGltZW5zaW9uO1xuICAgICAgfSksXG4gICk7XG59XG5cbi8qKlxuICogQmVzdC1lZmZvcnQgZW1iZWRkaW5nIGRpbWVuc2lvbiBwcm9iZTogcXVlcnkgYG5hbWVgIHdpdGggYSB0aW55IGBcInBpbmdcImBcbiAqIGlucHV0IGFuZCByZXR1cm4gdGhlIGxlbmd0aCBvZiB0aGUgcmV0dXJuZWQgdmVjdG9yLiBSZXR1cm5zIGB1bmRlZmluZWRgIChhbmRcbiAqIGxvZ3MgYXQgd2Fybikgd2hlbiB0aGUgZW5kcG9pbnQgY2FuJ3QgYmUgcXVlcmllZCBvciByZXR1cm5zIG5vIHZlY3RvciAtIHRoZVxuICogZGltZW5zaW9uIGlzIGluZm9ybWF0aW9uYWwsIG5ldmVyIHJlcXVpcmVkLlxuICovXG5hc3luYyBmdW5jdGlvbiBwaW5nRW1iZWRkaW5nRGltZW5zaW9uKFxuICBjbGllbnQ6IFdvcmtzcGFjZUNsaWVudExpa2UsXG4gIG5hbWU6IHN0cmluZyxcbik6IFByb21pc2U8bnVtYmVyIHwgdW5kZWZpbmVkPiB7XG4gIHRyeSB7XG4gICAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCBjbGllbnQuc2VydmluZ0VuZHBvaW50cy5xdWVyeSh7IG5hbWUsIGlucHV0OiBcInBpbmdcIiB9KTtcbiAgICBjb25zdCBkaW1lbnNpb24gPSByZXNwb25zZS5kYXRhPy5bMF0/LmVtYmVkZGluZz8ubGVuZ3RoO1xuICAgIGlmICh0eXBlb2YgZGltZW5zaW9uID09PSBcIm51bWJlclwiICYmIGRpbWVuc2lvbiA+IDApIHJldHVybiBkaW1lbnNpb247XG4gICAgbG9nZ2VyLndhcm4oXCJlbWJlZGRpbmcgcGluZyByZXR1cm5lZCBubyB2ZWN0b3JcIiwgeyBuYW1lIH0pO1xuICAgIHJldHVybiB1bmRlZmluZWQ7XG4gIH0gY2F0Y2ggKGVycikge1xuICAgIGxvZ2dlci53YXJuKFwiZW1iZWRkaW5nIHBpbmcgZmFpbGVkXCIsIHsgbmFtZSwgZXJyb3I6IGVycm9yLmVycm9yTWVzc2FnZShlcnIpIH0pO1xuICAgIHJldHVybiB1bmRlZmluZWQ7XG4gIH1cbn1cblxuLyoqXG4gKiBQdWxsIHRoZSBGb3VuZGF0aW9uIE1vZGVsIEFQSSBgcXVhbGl0eWAgLyBgc3BlZWRgIC8gYGNvc3RgIHNjb3JlcyBvZmYgYVxuICogc2VydmluZy1lbmRwb2ludCBsaXN0aW5nIGVudHJ5LiBEYXRhYnJpY2tzIHJldHVybnMgdGhlc2UgdW5kZXJcbiAqIGBjb25maWcuc2VydmVkX2VudGl0aWVzW10uZm91bmRhdGlvbl9tb2RlbC5haV9nYXRld2F5X21vZGVsX3Byb2ZpbGVgXG4gKiAoc25ha2VfY2FzZSBhdCB0aGUgd2lyZSBsZXZlbCwgcHJlc2VydmVkIHZlcmJhdGltIGJ5IHRoZSBTREspLCBidXQgdGhlIGZpZWxkXG4gKiBpcyBuZXdlciB0aGFuIHRoZSB0eXBlZCBgRm91bmRhdGlvbk1vZGVsYCBpbnRlcmZhY2UsIHNvIHdlIHJlYWQgaXQgdGhyb3VnaCBhXG4gKiBzdHJ1Y3R1cmFsIGNhc3QuIFJldHVybnMgYHVuZGVmaW5lZGAgd2hlbiBubyBzZXJ2ZWQgZW50aXR5IGNhcnJpZXMgYSBwcm9maWxlXG4gKiAoY3VzdG9tIG1vZGVscywgZW1iZWRkaW5ncywgYW5kIGJyYW5kLW5ldyBlbmRwb2ludHMgdGhhdCBEYXRhYnJpY2tzIGhhcyBub3RcbiAqIHNjb3JlZCB5ZXQpLlxuICovXG5mdW5jdGlvbiBleHRyYWN0UHJvZmlsZShlcDogdW5rbm93bik6IE1vZGVsUHJvZmlsZSB8IHVuZGVmaW5lZCB7XG4gIGNvbnN0IGVudGl0aWVzID0gKFxuICAgIGVwIGFzIHtcbiAgICAgIGNvbmZpZz86IHtcbiAgICAgICAgc2VydmVkX2VudGl0aWVzPzogQXJyYXk8e1xuICAgICAgICAgIGZvdW5kYXRpb25fbW9kZWw/OiB7XG4gICAgICAgICAgICBhaV9nYXRld2F5X21vZGVsX3Byb2ZpbGU/OiB7XG4gICAgICAgICAgICAgIHF1YWxpdHk/OiBudW1iZXI7XG4gICAgICAgICAgICAgIHNwZWVkPzogbnVtYmVyO1xuICAgICAgICAgICAgICBjb3N0PzogbnVtYmVyO1xuICAgICAgICAgICAgfTtcbiAgICAgICAgICB9O1xuICAgICAgICB9PjtcbiAgICAgIH07XG4gICAgfVxuICApLmNvbmZpZz8uc2VydmVkX2VudGl0aWVzO1xuICBpZiAoIWVudGl0aWVzKSByZXR1cm4gdW5kZWZpbmVkO1xuICBmb3IgKGNvbnN0IGVudGl0eSBvZiBlbnRpdGllcykge1xuICAgIGNvbnN0IHJhdyA9IGVudGl0eS5mb3VuZGF0aW9uX21vZGVsPy5haV9nYXRld2F5X21vZGVsX3Byb2ZpbGU7XG4gICAgaWYgKCFyYXcpIGNvbnRpbnVlO1xuICAgIGNvbnN0IHByb2ZpbGU6IE1vZGVsUHJvZmlsZSA9IHt9O1xuICAgIGlmIChOdW1iZXIuaXNGaW5pdGUocmF3LnF1YWxpdHkpKSBwcm9maWxlLnF1YWxpdHkgPSByYXcucXVhbGl0eTtcbiAgICBpZiAoTnVtYmVyLmlzRmluaXRlKHJhdy5zcGVlZCkpIHByb2ZpbGUuc3BlZWQgPSByYXcuc3BlZWQ7XG4gICAgaWYgKE51bWJlci5pc0Zpbml0ZShyYXcuY29zdCkpIHByb2ZpbGUuY29zdCA9IHJhdy5jb3N0O1xuICAgIGlmIChPYmplY3Qua2V5cyhwcm9maWxlKS5sZW5ndGggPiAwKSByZXR1cm4gcHJvZmlsZTtcbiAgfVxuICByZXR1cm4gdW5kZWZpbmVkO1xufVxuXG4vKipcbiAqIEZvcmNlLWV2aWN0IGNhY2hlZCBlbmRwb2ludCBsaXN0aW5ncyB2aWEgQXBwS2l0J3MgYENhY2hlTWFuYWdlcmAuIFdpdGggYVxuICogYGhvc3RgIGRlbGV0ZXMgdGhhdCBvbmUgd29ya3NwYWNlJ3MgZW50cnk7IHdpdGhvdXQgb25lIGNsZWFycyBldmVyeSBjYWNoZVxuICogZW50cnkgb24gdGhlIG1hbmFnZXIgKHNpbmNlIGBDYWNoZU1hbmFnZXJgIGRvZXNuJ3QgZXhwb3NlIGEgbmFtZXNwYWNlLXNjb3BlZFxuICogY2xlYXIsIHRoaXMgaXMgdGhlIGJydXRlLWZvcmNlIHBhdGggLSBmaW5lIGZvciB0ZXN0cywgYXZvaWQgaW4gc3RlYWR5LXN0YXRlXG4gKiBjb2RlKS5cbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGNsZWFyU2VydmluZ0VuZHBvaW50c0NhY2hlKGhvc3Q/OiBzdHJpbmcpOiBQcm9taXNlPHZvaWQ+IHtcbiAgY29uc3QgY2FjaGUgPSBDYWNoZU1hbmFnZXIuZ2V0SW5zdGFuY2VTeW5jKCk7XG4gIGlmIChob3N0KSB7XG4gICAgY29uc3Qga2V5ID0gY2FjaGUuZ2VuZXJhdGVLZXkoW0NBQ0hFX0tFWV9OQU1FU1BBQ0UsIGhvc3RdLCBTSEFSRURfVVNFUl9LRVkpO1xuICAgIGF3YWl0IGNhY2hlLmRlbGV0ZShrZXkpO1xuICB9IGVsc2Uge1xuICAgIGF3YWl0IGNhY2hlLmNsZWFyKCk7XG4gIH1cbn1cblxuLyoqXG4gKiBSZXN1bHQgb2YgZnV6enktcmVzb2x2aW5nIGEgdXNlci1zdXBwbGllZCBtb2RlbCBuYW1lIGFnYWluc3QgdGhlIGxpdmVcbiAqIGVuZHBvaW50IGxpc3QuIGBzY29yZWAgaXMgRnVzZS5qcydzIGRpc3RhbmNlIChgMGAgaXMgZXhhY3QsIGAxYCBpcyBubyBtYXRjaCk7XG4gKiBgbWF0Y2hlZGAgaXMgYGZhbHNlYCB3aGVuIHRoZSBzY29yZSBleGNlZWRzIHRoZSBjb25maWd1cmVkIHRocmVzaG9sZCBzb1xuICogY2FsbGVycyBjYW4gZmFsbCBiYWNrIHRvIHRoZSBvcmlnaW5hbCBpbnB1dCAoRGF0YWJyaWNrcyB3aWxsIHRoZW4gcmV0dXJuIGFcbiAqIGNsZWFuIDQwNCkuXG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgUmVzb2x2ZWRNb2RlbCB7XG4gIG1vZGVsSWQ6IHN0cmluZztcbiAgbWF0Y2hlZDogYm9vbGVhbjtcbiAgc2NvcmU/OiBudW1iZXI7XG59XG5cbi8qKiBPcHRpb25zIGFjY2VwdGVkIGJ5IHtAbGluayByZXNvbHZlTW9kZWxJZH0gLyB7QGxpbmsgc2VhcmNoU2VydmluZ0VuZHBvaW50c30uICovXG5leHBvcnQgaW50ZXJmYWNlIFJlc29sdmVNb2RlbE9wdGlvbnMge1xuICAvKiogRnVzZS5qcyB0aHJlc2hvbGQgKDAgPSBleGFjdCwgMSA9IGFueXRoaW5nKS4gRGVmYXVsdCBgMC40YC4gKi9cbiAgdGhyZXNob2xkPzogbnVtYmVyO1xuICAvKiogUmVxdWlyZSBhIG1vZGVsIHZlcmlmaWVkIGZvciBhIGNvbXBsZXRlIGZ1bmN0aW9uLXRvb2wgcm91bmQtdHJpcC4gKi9cbiAgcmVxdWlyZXNUb29scz86IGJvb2xlYW47XG59XG5cbi8qKiBBIHNlcnZpbmcgZW5kcG9pbnQgcGFpcmVkIHdpdGggaXRzIGZ1enp5LW1hdGNoIGRpc3RhbmNlIGZvciBhIHF1ZXJ5LiAqL1xuZXhwb3J0IGludGVyZmFjZSBTY29yZWRFbmRwb2ludCB7XG4gIGVuZHBvaW50OiBTZXJ2aW5nRW5kcG9pbnRTdW1tYXJ5O1xuICAvKiogRnVzZS5qcyBkaXN0YW5jZTogYDBgIGlzIGV4YWN0LCBgMWAgaXMgbm8gbWF0Y2guICovXG4gIHNjb3JlOiBudW1iZXI7XG59XG5cbi8qKlxuICogRnV6enktcmFuayBlbmRwb2ludHMgYnkgaG93IGNsb3NlbHkgdGhlaXIgYG5hbWVgIG1hdGNoZXMgYGlucHV0YCwgYmVzdFxuICogKGxvd2VzdCBzY29yZSkgZmlyc3QsIGtlZXBpbmcgb25seSB0aG9zZSB3aXRoaW4gYHRocmVzaG9sZGA6XG4gKlxuICogMS4gQW4gZXhhY3QgbmFtZSBtYXRjaCBzaG9ydC1jaXJjdWl0cyB0byBhIHNpbmdsZSBgc2NvcmU6IDBgIHJlc3VsdC5cbiAqIDIuIE90aGVyd2lzZSB0aGUgaW5wdXQgaXMgdG9rZW5pemVkIChkYXNoZXMgLyB1bmRlcnNjb3JlcyAvIHNwYWNlcyBiZWNvbWVcbiAqICAgIHNlcGFyYXRvcnMpIGFuZCBmZWQgdGhyb3VnaCBGdXNlLmpzIGV4dGVuZGVkIHNlYXJjaCwgd2hpY2ggQU5ELXMgZWFjaFxuICogICAgdG9rZW4gd2l0aCBmdXp6eSBtYXRjaGluZyBlbmFibGVkIC0gdGhlIFwidG9rZW5pemVkIGZ1enp5IG1hdGNoXCIgYSBjYWxsZXJcbiAqICAgIHJlYWNoZXMgZm9yIHdoZW4gdGhleSB0eXBlIGBcImNsYXVkZSBzb25uZXRcImAgaW5zdGVhZCBvZiB0aGUgZnVsbCBlbmRwb2ludFxuICogICAgbmFtZS5cbiAqXG4gKiBSZXR1cm5zIGBbXWAgZm9yIGFuIGVtcHR5IGVuZHBvaW50IGxpc3Qgb3Igd2hlbiBgaW5wdXRgIHRva2VuaXplcyB0byBub3RoaW5nLFxuICogc28gY2FsbGVycyBmYWxsIGJhY2sgdG8gdGhlIHJhdyBpbnB1dCBhbmQgbGV0IERhdGFicmlja3Mgc3VyZmFjZSBhIGNsZWFuIDQwNC5cbiAqIFRoaXMgbXVsdGktcmVzdWx0IGNvcmUgaXMgc2hhcmVkIGJ5IHtAbGluayByZXNvbHZlTW9kZWxJZH0gKHNpbmdsZSBiZXN0KSBhbmRcbiAqIHRoZSByYW5rZWQgYGxvb2t1cE1vZGVsc2Agc2VsZWN0b3IuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBzZWFyY2hTZXJ2aW5nRW5kcG9pbnRzKFxuICBpbnB1dDogc3RyaW5nLFxuICBlbmRwb2ludHM6IHJlYWRvbmx5IFNlcnZpbmdFbmRwb2ludFN1bW1hcnlbXSxcbiAgb3B0aW9uczogUmVzb2x2ZU1vZGVsT3B0aW9ucyA9IHt9LFxuKTogU2NvcmVkRW5kcG9pbnRbXSB7XG4gIGlmIChlbmRwb2ludHMubGVuZ3RoID09PSAwKSByZXR1cm4gW107XG4gIGZvciAoY29uc3QgZXAgb2YgZW5kcG9pbnRzKSB7XG4gICAgaWYgKGVwLm5hbWUgPT09IGlucHV0KSByZXR1cm4gW3sgZW5kcG9pbnQ6IGVwLCBzY29yZTogMCB9XTtcbiAgfVxuICBjb25zdCB0aHJlc2hvbGQgPSBvcHRpb25zLnRocmVzaG9sZCA/PyBERUZBVUxUX0ZVWlpZX1RIUkVTSE9MRDtcbiAgLy8gRnVzZSA3LjMgaGFzIG5vIGJ1aWx0LWluIHRva2VuaXplIGhvb2s7IGluIGV4dGVuZGVkIHNlYXJjaCwgc3BhY2Utc2VwYXJhdGVkXG4gIC8vIHRva2VucyBhcmUgQU5ELWVkIHdpdGggZnV6enkgbWF0Y2hpbmcgZW5hYmxlZC4gV2UgbGVhbiBvbiB0aGUgc2hhcmVkXG4gIC8vIHRva2VuaXplciBzbyB0aGUgc3BsaXR0aW5nIHJ1bGVzIHN0YXkgY29uc2lzdGVudCB3aXRoIHRoZSByZXN0IG9mIHRoZSB0b29sa2l0LlxuICBjb25zdCBxdWVyeSA9IEFycmF5LmZyb20oXG4gICAgc3RyaW5nLnRva2VuaXplV2l0aE9wdGlvbnMoeyBsb3dlckNhc2U6IHRydWUsIGNhbWVsQ2FzZTogZmFsc2UgfSwgaW5wdXQpLFxuICApLmpvaW4oXCIgXCIpO1xuICBpZiAoIXF1ZXJ5KSByZXR1cm4gW107XG4gIGNvbnN0IGZ1c2UgPSBuZXcgRnVzZShlbmRwb2ludHMsIHtcbiAgICBrZXlzOiBbXCJuYW1lXCJdLFxuICAgIHRocmVzaG9sZCxcbiAgICBpZ25vcmVMb2NhdGlvbjogdHJ1ZSxcbiAgICBpbmNsdWRlU2NvcmU6IHRydWUsXG4gICAgdXNlRXh0ZW5kZWRTZWFyY2g6IHRydWUsXG4gICAgaXNDYXNlU2Vuc2l0aXZlOiBmYWxzZSxcbiAgfSk7XG4gIHJldHVybiBmdXNlXG4gICAgLnNlYXJjaChxdWVyeSlcbiAgICAuZmlsdGVyKChyKSA9PiAoci5zY29yZSA/PyAwKSA8PSB0aHJlc2hvbGQpXG4gICAgLm1hcCgocikgPT4gKHsgZW5kcG9pbnQ6IHIuaXRlbSwgc2NvcmU6IHIuc2NvcmUgPz8gMCB9KSk7XG59XG5cbi8qKlxuICogU25hcCBhIHVzZXItc3VwcGxpZWQgbW9kZWwgbmFtZSB0byB0aGUgc2luZ2xlIGNsb3Nlc3QgY29uZmlndXJlZCBzZXJ2aW5nXG4gKiBlbmRwb2ludCB2aWEge0BsaW5rIHNlYXJjaFNlcnZpbmdFbmRwb2ludHN9LiBSZXR1cm5zIHRoZSBpbnB1dCB1bmNoYW5nZWQgd2l0aFxuICogYG1hdGNoZWQ6IGZhbHNlYCB3aGVuIG5vdGhpbmcgc2NvcmVzIHdpdGhpbiB0aGUgdGhyZXNob2xkIChvciB0aGUgY2F0YWxvZ3VlXG4gKiBpcyBlbXB0eSksIHNvIGEgZGVsaWJlcmF0ZSBtb2RlbCBpZCBpcyBuZXZlciBzaWxlbnRseSByZXdyaXR0ZW4gdG8gYVxuICogc2ltaWxhci1sb29raW5nIG5laWdoYm91ciBhbmQgdGhlIHVwc3RyZWFtIGNhbGwgc3VyZmFjZXMgdGhlIGNhbm9uaWNhbCA0MDQuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiByZXNvbHZlTW9kZWxJZChcbiAgaW5wdXQ6IHN0cmluZyxcbiAgZW5kcG9pbnRzOiByZWFkb25seSBTZXJ2aW5nRW5kcG9pbnRTdW1tYXJ5W10sXG4gIG9wdGlvbnM6IFJlc29sdmVNb2RlbE9wdGlvbnMgPSB7fSxcbik6IFJlc29sdmVkTW9kZWwge1xuICBjb25zdCBbYmVzdF0gPSBzZWFyY2hTZXJ2aW5nRW5kcG9pbnRzKGlucHV0LCBlbmRwb2ludHMsIG9wdGlvbnMpO1xuICBpZiAoYmVzdCkge1xuICAgIHJldHVybiB7IG1vZGVsSWQ6IGJlc3QuZW5kcG9pbnQubmFtZSwgbWF0Y2hlZDogdHJ1ZSwgc2NvcmU6IGJlc3Quc2NvcmUgfTtcbiAgfVxuICByZXR1cm4geyBtb2RlbElkOiBpbnB1dCwgbWF0Y2hlZDogZmFsc2UgfTtcbn1cbiJdfQ==
|