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