@dbx-tools/model 0.9.21 → 0.9.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/README.md +61 -10
  2. package/index.ts +12 -2
  3. package/lib/exports.d.ts +1 -0
  4. package/lib/exports.js +2 -0
  5. package/lib/index.d.ts +12 -2
  6. package/lib/index.js +9 -2
  7. package/lib/src/_metadata-contract.d.ts +42 -0
  8. package/lib/src/_metadata-contract.js +11 -0
  9. package/lib/src/_metadata-generator.d.ts +7 -0
  10. package/lib/src/_metadata-generator.js +182 -0
  11. package/lib/src/_ranking.d.ts +20 -0
  12. package/lib/src/_ranking.js +226 -0
  13. package/lib/src/_selection.d.ts +23 -0
  14. package/lib/src/_selection.js +95 -0
  15. package/lib/src/classes.d.ts +2 -2
  16. package/lib/src/classes.js +10 -8
  17. package/lib/src/classify.d.ts +130 -0
  18. package/lib/src/classify.js +283 -0
  19. package/lib/src/fallback.d.ts +4 -4
  20. package/lib/src/fallback.js +7 -7
  21. package/lib/src/generated/model-capabilities.json +90 -0
  22. package/lib/src/generated/model-rate-limits.json +342 -0
  23. package/lib/src/generated/retired-models.json +32 -0
  24. package/lib/src/invoke.js +3 -3
  25. package/lib/src/metadata.d.ts +33 -0
  26. package/lib/src/metadata.js +120 -0
  27. package/lib/src/model-catalog.d.ts +4 -4
  28. package/lib/src/model-catalog.js +19 -24
  29. package/lib/src/model-client.d.ts +67 -0
  30. package/lib/src/model-client.js +181 -0
  31. package/lib/src/policy.d.ts +14 -8
  32. package/lib/src/policy.js +173 -14
  33. package/lib/src/python.d.ts +2 -0
  34. package/lib/src/python.js +3 -0
  35. package/lib/src/resolve.d.ts +10 -143
  36. package/lib/src/resolve.js +11 -196
  37. package/lib/tsconfig.tsbuildinfo +1 -1
  38. package/package.json +15 -5
  39. package/src/_metadata-contract.ts +56 -0
  40. package/src/_metadata-generator.ts +199 -0
  41. package/src/_ranking.ts +260 -0
  42. package/src/_selection.ts +141 -0
  43. package/src/classes.ts +11 -7
  44. package/src/classify.ts +324 -0
  45. package/src/fallback.ts +11 -7
  46. package/src/generated/model-capabilities.json +90 -0
  47. package/src/generated/model-rate-limits.json +342 -0
  48. package/src/generated/retired-models.json +32 -0
  49. package/src/invoke.ts +2 -2
  50. package/src/metadata.ts +189 -0
  51. package/src/model-catalog.ts +26 -28
  52. package/src/model-client.ts +311 -0
  53. package/src/policy.ts +176 -19
  54. package/src/python.ts +2 -0
  55. package/src/resolve.ts +24 -270
  56. package/._LICENSE +0 -0
  57. package/._README.md +0 -0
  58. package/._index.ts +0 -0
  59. package/._package.json +0 -0
  60. package/lib/._index.d.ts +0 -0
  61. package/lib/._index.js +0 -0
  62. package/lib/._tsconfig.tsbuildinfo +0 -0
  63. package/lib/src/.__native.d.ts +0 -0
  64. package/lib/src/.__native.js +0 -0
  65. package/lib/src/._classes.d.ts +0 -0
  66. package/lib/src/._classes.js +0 -0
  67. package/lib/src/._fallback.d.ts +0 -0
  68. package/lib/src/._fallback.js +0 -0
  69. package/lib/src/._invoke.d.ts +0 -0
  70. package/lib/src/._invoke.js +0 -0
  71. package/lib/src/._model-catalog.d.ts +0 -0
  72. package/lib/src/._model-catalog.js +0 -0
  73. package/lib/src/._policy.d.ts +0 -0
  74. package/lib/src/._policy.js +0 -0
  75. package/lib/src/._resolve.d.ts +0 -0
  76. package/lib/src/._resolve.js +0 -0
  77. package/lib/src/_native.d.ts +0 -14
  78. package/lib/src/_native.js +0 -124
  79. package/src/.__native.ts +0 -0
  80. package/src/._classes.ts +0 -0
  81. package/src/._fallback.ts +0 -0
  82. package/src/._invoke.ts +0 -0
  83. package/src/._model-catalog.ts +0 -0
  84. package/src/._policy.ts +0 -0
  85. package/src/._resolve.ts +0 -0
  86. package/src/_native.ts +0 -174
@@ -1,215 +1,30 @@
1
- /**
2
- * Workspace-aware model selection.
3
- *
4
- * Given a caller's intent - a search string, a capability {@link ModelClass}
5
- * ceiling, both, or nothing - the toolkit returns matching endpoints ranked by
6
- * match quality then class, or collapses to the single best id the workspace
7
- * actually has, degrading from "best in range" down to the static fallback
8
- * floor. Selection is chat-only: embedding endpoints surface only when
9
- * `modelClass` is explicitly {@link ModelClass.Embedding}.
10
- *
11
- * Two shapes of selection, each in a pure form (over an endpoint list the
12
- * caller already holds) and an I/O wrapper (that lists `/serving-endpoints`
13
- * first): ranking, which returns a match- then class-ordered list, and
14
- * single-selection, which collapses to one id plus how it was reached and
15
- * layers the operator-pinned fallback / static-floor safety net on top. A chat
16
- * `modelClass` acts as a ceiling: that band and the less-capable chat bands
17
- * below it are eligible (see {@link classesAtOrBelow}), so a `chat-balanced`
18
- * ask can fall to `chat-fast` but never escalate to `chat-thinking`.
19
- *
20
- * @module
21
- */
22
- import { object } from "@dbx-tools/shared-core";
23
- import { rankEndpoints } from "./_native.js";
24
- import { FALLBACK_MODEL_IDS, modelsForClass } from "./fallback.js";
1
+ import { lookupModels, rankModelId, resolveModel, } from "./_selection.js";
25
2
  import { listServingEndpoints, } from "./model-catalog.js";
26
- import { endpointSupportsTools } from "./policy.js";
27
- /** Preferred live family for an unconfigured general-purpose chat default. */
28
- const DEFAULT_MODEL_FAMILY_SEARCH = "gpt";
29
- /**
30
- * Rank the live catalogue against a {@link ModelQuery}, best-first.
31
- *
32
- * Rust owns classification, fuzzy scoring, family-version ordering, preferred
33
- * variants, class ceilings, tool filtering, and result limiting. Node retains
34
- * the caller's endpoint objects so fields outside the ranking contract remain
35
- * available unchanged.
36
- */
37
- export function lookupModels(endpoints, query = {}) {
38
- return rankEndpoints(endpoints, query);
39
- }
40
- /**
41
- * Collapse {@link lookupModels} to a single id: the closest endpoint to `search`
42
- * in a catalogue snapshot, or the input verbatim when nothing scores within the
43
- * threshold.
44
- *
45
- * The ranked counterpart to {@link resolveModelId}: equal
46
- * match scores are broken by class and then within-class version, so a loose
47
- * `"opus"` prefers `opus-5` over `opus-4-7` instead of picking whichever
48
- * sibling appeared first. Returning the input unmatched (rather
49
- * than a near neighbour) is deliberate - a deliberate endpoint id is never
50
- * silently rewritten, and Databricks surfaces a clean 404.
51
- */
52
- export function rankModelId(endpoints, search, options = {}) {
53
- const [top] = lookupModels(endpoints, {
54
- search,
55
- limit: 1,
56
- ...(options.threshold !== undefined ? { threshold: options.threshold } : {}),
57
- ...(options.requiresTools !== undefined ? { requiresTools: options.requiresTools } : {}),
58
- });
59
- if (!top)
60
- return { modelId: search, matched: false };
61
- return { modelId: top.endpoint.name, matched: true, score: top.score };
62
- }
63
- /**
64
- * {@link rankModelId} against a catalogue the caller may be holding stale:
65
- * match the loaded snapshot, and on a miss reload once with `force` and match
66
- * again. That way a model deployed after the catalogue was cached still
67
- * resolves on first use, without a restart and without giving up caching.
68
- *
69
- * The catalogue arrives as a loader rather than a client so the caller keeps
70
- * ownership of *how* it is cached - {@link listServingEndpoints} and its
71
- * `CacheManager`, a plain process-lifetime field in a CLI, or a test double.
72
- * Only one reload is attempted: a genuinely unknown name should fail fast
73
- * rather than re-list on every request.
74
- *
75
- * @param load - Returns the catalogue; `force` asks it to bypass its cache.
76
- */
3
+ export { lookupModels, rankModelId, resolveModel } from "./_selection.js";
4
+ /** Resolve against a potentially stale catalogue, refreshing once on a miss. */
77
5
  export async function rankModelIdLive(load, search, options = {}) {
78
6
  const resolved = rankModelId(await load(false), search, options);
79
7
  if (resolved.matched)
80
8
  return resolved;
81
9
  return rankModelId(await load(true), search, options);
82
10
  }
83
- /**
84
- * Rank a workspace's catalogue in one call: list its `/serving-endpoints`
85
- * (cached) and run {@link lookupModels} over the result. The list counterpart to
86
- * {@link selectModel}, for a consumer that wants the full ranked set (a model
87
- * picker, a CLI) rather than a single id. Catalogue fetches fail loud: network
88
- * / auth errors propagate so the caller sees the real SDK message.
89
- *
90
- * @param host - Workspace host used as the cache key. Pass the value resolved
91
- * from `client.config.getHost()`.
92
- */
11
+ /** List and rank a workspace model catalogue. */
93
12
  export async function searchModels(client, host, input = {}) {
94
- const endpoints = await listServingEndpoints(client, host, {
95
- ...(input.ttlMs !== undefined ? { ttlMs: input.ttlMs } : {}),
96
- ...(input.cacheIdentity !== undefined ? { cacheIdentity: input.cacheIdentity } : {}),
97
- });
13
+ const endpoints = await listServingEndpoints(client, host, catalogueOptions(input));
98
14
  return lookupModels(endpoints, input);
99
15
  }
100
- /**
101
- * Resolve a model id for a workspace in one call: list its `/serving-endpoints`
102
- * (cached) and run {@link resolveModel} over the result. This is the entry
103
- * point for any consumer that holds a `WorkspaceClient` and just wants a usable
104
- * model name - a Lakeflow job, a one-off script, or the Mastra plugin alike.
105
- *
106
- * Cheap exit: when an `explicit` name is given, `fuzzy` is off, and tool
107
- * capability is not required, the catalogue is never fetched. Catalogue
108
- * fetches otherwise fail loud: network / auth errors propagate so the caller
109
- * sees the real SDK message instead of a silent fallback.
110
- *
111
- * @param host - Workspace host used as the cache key. Pass the value resolved
112
- * from `client.config.getHost()`.
113
- */
16
+ /** List a workspace catalogue and resolve one model. */
114
17
  export async function selectModel(client, host, input = {}) {
115
18
  if (input.explicit !== undefined && input.fuzzy === false && !input.requiresTools) {
116
19
  return { modelId: input.explicit, source: "explicit" };
117
20
  }
118
- const endpoints = await listServingEndpoints(client, host, {
119
- ...(input.ttlMs !== undefined ? { ttlMs: input.ttlMs } : {}),
120
- ...(input.cacheIdentity !== undefined ? { cacheIdentity: input.cacheIdentity } : {}),
121
- });
21
+ const endpoints = await listServingEndpoints(client, host, catalogueOptions(input));
122
22
  return resolveModel(endpoints, input);
123
23
  }
124
- /**
125
- * Resolve a single model id from the live catalogue and caller intent,
126
- * delegating the live selection to {@link lookupModels} with `limit: 1`.
127
- *
128
- * 1. **Explicit ask**: with `fuzzy` off, returned verbatim; otherwise
129
- * fuzzy-ranked within the (optional) class ceiling and the best taken,
130
- * falling back to the input verbatim when nothing matches.
131
- * 2. **No explicit ask**: an operator-pinned `fallback` that exists in the live
132
- * catalogue wins first. A class request ranks within that ceiling. A
133
- * general request prefers the highest-ranked live GPT, then any ranked live
134
- * chat model. The static {@link FALLBACK_MODEL_IDS} floor is used only when
135
- * the catalogue yields nothing in range.
136
- */
137
- export function resolveModel(endpoints, input = {}) {
138
- if (input.explicit !== undefined) {
139
- if (input.fuzzy === false) {
140
- if (input.requiresTools)
141
- assertToolSupport(endpoints, input.explicit);
142
- return { modelId: input.explicit, source: "explicit" };
143
- }
144
- const [top] = lookupModels(endpoints, buildQuery(input, input.explicit));
145
- if (input.requiresTools && !top) {
146
- throw new Error(`No tool-capable model matches "${input.explicit}"`);
147
- }
148
- return { modelId: top?.endpoint.name ?? input.explicit, source: "fuzzy-match" };
149
- }
150
- // Operator-pinned fallbacks win when present and live (e.g. a regulated
151
- // workspace restricted to an approved subset).
152
- if (input.modelClass === undefined && input.fallbacks && input.fallbacks.length > 0) {
153
- const present = new Set(endpoints
154
- .filter((endpoint) => !input.requiresTools || endpointSupportsTools(endpoint))
155
- .map((endpoint) => endpoint.name));
156
- const pinned = input.fallbacks.find((id) => present.has(id));
157
- if (pinned)
158
- return { modelId: pinned, source: "fallback" };
159
- }
160
- const source = input.modelClass !== undefined ? "class" : "fallback";
161
- if (input.modelClass === undefined) {
162
- const [preferred] = lookupModels(endpoints, buildQuery(input, DEFAULT_MODEL_FAMILY_SEARCH));
163
- if (preferred)
164
- return { modelId: preferred.endpoint.name, source };
165
- }
166
- const [top] = lookupModels(endpoints, buildQuery(input, undefined));
167
- if (top)
168
- return { modelId: top.endpoint.name, source };
169
- if (input.liveOnly) {
170
- throw new Error("No matching live Model Serving endpoint is available");
171
- }
172
- // Live catalogue yielded nothing in range: walk the static floor.
173
- const floorSource = input.modelClass !== undefined ? modelsForClass(input.modelClass) : (input.fallbacks ?? []);
174
- const floor = object.sequence(floorSource).concat(FALLBACK_MODEL_IDS).distinct().toArray();
175
- if (input.requiresTools) {
176
- const available = new Set(endpoints.filter(endpointSupportsTools).map((endpoint) => endpoint.name));
177
- const selected = floor.find((id) => available.has(id));
178
- if (!selected)
179
- throw new Error("No tool-capable model is available");
180
- return { modelId: selected, source };
181
- }
182
- return { modelId: pickFirstAvailable(floor, endpoints), source };
183
- }
184
- /** Build a {@link ModelQuery} from {@link ResolveModelInput} for the `limit: 1` delegation. */
185
- function buildQuery(input, search) {
24
+ function catalogueOptions(input) {
186
25
  return {
187
- ...(search !== undefined ? { search } : {}),
188
- ...(input.modelClass !== undefined ? { modelClass: input.modelClass } : {}),
189
- ...(input.requiresTools !== undefined ? { requiresTools: input.requiresTools } : {}),
190
- ...(input.threshold !== undefined ? { threshold: input.threshold } : {}),
191
- limit: 1,
26
+ ...(input.ttlMs !== undefined ? { ttlMs: input.ttlMs } : {}),
27
+ ...(input.cacheIdentity !== undefined ? { cacheIdentity: input.cacheIdentity } : {}),
192
28
  };
193
29
  }
194
- /** Throw when an explicit id is absent or not verified for function tools. */
195
- function assertToolSupport(endpoints, modelId) {
196
- const endpoint = endpoints.find((candidate) => candidate.name === modelId);
197
- if (!endpoint || !endpointSupportsTools(endpoint)) {
198
- throw new Error(`Model "${modelId}" does not support function tools`);
199
- }
200
- }
201
- /**
202
- * Find the first id in `candidates` whose endpoint is present in `endpoints`.
203
- * Returns the top candidate when the workspace has none of them so callers
204
- * always get a string; an offline workspace then receives a clean 404 from
205
- * Databricks instead of a malformed config.
206
- */
207
- function pickFirstAvailable(candidates, endpoints) {
208
- const present = new Set(endpoints.map((e) => e.name));
209
- for (const candidate of candidates) {
210
- if (present.has(candidate))
211
- return candidate;
212
- }
213
- return candidates[0] ?? FALLBACK_MODEL_IDS[0];
214
- }
215
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVzb2x2ZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9yZXNvbHZlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQW9CRztBQUVILE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSx3QkFBd0IsQ0FBQztBQVFoRCxPQUFPLEVBQUUsYUFBYSxFQUFFLE1BQU0sY0FBYyxDQUFDO0FBQzdDLE9BQU8sRUFBRSxrQkFBa0IsRUFBRSxjQUFjLEVBQUUsTUFBTSxlQUFlLENBQUM7QUFDbkUsT0FBTyxFQUNMLG9CQUFvQixHQUlyQixNQUFNLG9CQUFvQixDQUFDO0FBQzVCLE9BQU8sRUFBRSxxQkFBcUIsRUFBRSxNQUFNLGFBQWEsQ0FBQztBQUlwRCw4RUFBOEU7QUFDOUUsTUFBTSwyQkFBMkIsR0FBRyxLQUFLLENBQUM7QUErRDFDOzs7Ozs7O0dBT0c7QUFDSCxNQUFNLFVBQVUsWUFBWSxDQUMxQixTQUE0QyxFQUM1QyxRQUFvQixFQUFFO0lBRXRCLE9BQU8sYUFBYSxDQUFDLFNBQVMsRUFBRSxLQUFLLENBQUMsQ0FBQztBQUN6QyxDQUFDO0FBRUQ7Ozs7Ozs7Ozs7O0dBV0c7QUFDSCxNQUFNLFVBQVUsV0FBVyxDQUN6QixTQUE0QyxFQUM1QyxNQUFjLEVBQ2QsVUFBK0IsRUFBRTtJQUVqQyxNQUFNLENBQUMsR0FBRyxDQUFDLEdBQUcsWUFBWSxDQUFDLFNBQVMsRUFBRTtRQUNwQyxNQUFNO1FBQ04sS0FBSyxFQUFFLENBQUM7UUFDUixHQUFHLENBQUMsT0FBTyxDQUFDLFNBQVMsS0FBSyxTQUFTLENBQUMsQ0FBQyxDQUFDLEVBQUUsU0FBUyxFQUFFLE9BQU8sQ0FBQyxTQUFTLEVBQUUsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDO1FBQzVFLEdBQUcsQ0FBQyxPQUFPLENBQUMsYUFBYSxLQUFLLFNBQVMsQ0FBQyxDQUFDLENBQUMsRUFBRSxhQUFhLEVBQUUsT0FBTyxDQUFDLGFBQWEsRUFBRSxD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cGFjZS1hd2FyZSBtb2RlbCBzZWxlY3Rpb24uXG4gKlxuICogR2l2ZW4gYSBjYWxsZXIncyBpbnRlbnQgLSBhIHNlYXJjaCBzdHJpbmcsIGEgY2FwYWJpbGl0eSB7QGxpbmsgTW9kZWxDbGFzc31cbiAqIGNlaWxpbmcsIGJvdGgsIG9yIG5vdGhpbmcgLSB0aGUgdG9vbGtpdCByZXR1cm5zIG1hdGNoaW5nIGVuZHBvaW50cyByYW5rZWQgYnlcbiAqIG1hdGNoIHF1YWxpdHkgdGhlbiBjbGFzcywgb3IgY29sbGFwc2VzIHRvIHRoZSBzaW5nbGUgYmVzdCBpZCB0aGUgd29ya3NwYWNlXG4gKiBhY3R1YWxseSBoYXMsIGRlZ3JhZGluZyBmcm9tIFwiYmVzdCBpbiByYW5nZVwiIGRvd24gdG8gdGhlIHN0YXRpYyBmYWxsYmFja1xuICogZmxvb3IuIFNlbGVjdGlvbiBpcyBjaGF0LW9ubHk6IGVtYmVkZGluZyBlbmRwb2ludHMgc3VyZmFjZSBvbmx5IHdoZW5cbiAqIGBtb2RlbENsYXNzYCBpcyBleHBsaWNpdGx5IHtAbGluayBNb2RlbENsYXNzLkVtYmVkZGluZ30uXG4gKlxuICogVHdvIHNoYXBlcyBvZiBzZWxlY3Rpb24sIGVhY2ggaW4gYSBwdXJlIGZvcm0gKG92ZXIgYW4gZW5kcG9pbnQgbGlzdCB0aGVcbiAqIGNhbGxlciBhbHJlYWR5IGhvbGRzKSBhbmQgYW4gSS9PIHdyYXBwZXIgKHRoYXQgbGlzdHMgYC9zZXJ2aW5nLWVuZHBvaW50c2BcbiAqIGZpcnN0KTogcmFua2luZywgd2hpY2ggcmV0dXJucyBhIG1hdGNoLSB0aGVuIGNsYXNzLW9yZGVyZWQgbGlzdCwgYW5kXG4gKiBzaW5nbGUtc2VsZWN0aW9uLCB3aGljaCBjb2xsYXBzZXMgdG8gb25lIGlkIHBsdXMgaG93IGl0IHdhcyByZWFjaGVkIGFuZFxuICogbGF5ZXJzIHRoZSBvcGVyYXRvci1waW5uZWQgZmFsbGJhY2sgLyBzdGF0aWMtZmxvb3Igc2FmZXR5IG5ldCBvbiB0b3AuIEEgY2hhdFxuICogYG1vZGVsQ2xhc3NgIGFjdHMgYXMgYSBjZWlsaW5nOiB0aGF0IGJhbmQgYW5kIHRoZSBsZXNzLWNhcGFibGUgY2hhdCBiYW5kc1xuICogYmVsb3cgaXQgYXJlIGVsaWdpYmxlIChzZWUge0BsaW5rIGNsYXNzZXNBdE9yQmVsb3d9KSwgc28gYSBgY2hhdC1iYWxhbmNlZGBcbiAqIGFzayBjYW4gZmFsbCB0byBgY2hhdC1mYXN0YCBidXQgbmV2ZXIgZXNjYWxhdGUgdG8gYGNoYXQtdGhpbmtpbmdgLlxuICpcbiAqIEBtb2R1bGVcbiAqL1xuXG5pbXBvcnQgeyBvYmplY3QgfSBmcm9tIFwiQGRieC10b29scy9zaGFyZWQtY29yZVwiO1xuaW1wb3J0IHtcbiAgbW9kZWwsXG4gIHR5cGUgTW9kZWxRdWVyeSxcbiAgdHlwZSBSYW5rZWRNb2RlbCxcbiAgdHlwZSBTZXJ2aW5nRW5kcG9pbnRTdW1tYXJ5LFxufSBmcm9tIFwiQGRieC10b29scy9zaGFyZWQtbW9kZWxcIjtcblxuaW1wb3J0IHsgcmFua0VuZHBvaW50cyB9IGZyb20gXCIuL19uYXRpdmUudHNcIjtcbmltcG9ydCB7IEZBTExCQUNLX01PREVMX0lEUywgbW9kZWxzRm9yQ2xhc3MgfSBmcm9tIFwiLi9mYWxsYmFjay50c1wiO1xuaW1wb3J0IHtcbiAgbGlzdFNlcnZpbmdFbmRwb2ludHMsXG4gIHR5cGUgUmVzb2x2ZWRNb2RlbCxcbiAgdHlwZSBSZXNvbHZlTW9kZWxPcHRpb25zLFxuICB0eXBlIFdvcmtzcGFjZUNsaWVudExpa2UsXG59IGZyb20gXCIuL21vZGVsLWNhdGFsb2cudHNcIjtcbmltcG9ydCB7IGVuZHBvaW50U3VwcG9ydHNUb29scyB9IGZyb20gXCIuL3BvbGljeS50c1wiO1xuXG50eXBlIE1vZGVsQ2xhc3MgPSBtb2RlbC5Nb2RlbENsYXNzO1xuXG4vKiogUHJlZmVycmVkIGxpdmUgZmFtaWx5IGZvciBhbiB1bmNvbmZpZ3VyZWQgZ2VuZXJhbC1wdXJwb3NlIGNoYXQgZGVmYXVsdC4gKi9cbmNvbnN0IERFRkFVTFRfTU9ERUxfRkFNSUxZX1NFQVJDSCA9IFwiZ3B0XCI7XG5cbi8qKiBDYWxsZXIgaW50ZW50IHBhc3NlZCB0byB7QGxpbmsgcmVzb2x2ZU1vZGVsfS4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgUmVzb2x2ZU1vZGVsSW5wdXQge1xuICAvKipcbiAgICogRXhwbGljaXQgbW9kZWwgaWQgLyBsb29zZSBuYW1lIChwZXItcmVxdWVzdCBvdmVycmlkZSwgYWdlbnQgLyBwbHVnaW5cbiAgICogZGVmYXVsdCwgb3IgZW52IHZhcikuIFdoZW4gc2V0IGl0IHdpbnMgb3ZlciBgbW9kZWxDbGFzc2AgYW5kIGBmYWxsYmFja3NgLlxuICAgKi9cbiAgZXhwbGljaXQ/OiBzdHJpbmc7XG4gIC8qKlxuICAgKiBGdXp6eS1tYXRjaCBhbiBgZXhwbGljaXRgIG5hbWUgYWdhaW5zdCB0aGUgbGl2ZSBjYXRhbG9ndWUgc28gbG9vc2UgbmFtZXNcbiAgICogbGlrZSBgXCJjbGF1ZGUgc29ubmV0XCJgIHJlc29sdmUuIERlZmF1bHQgYHRydWVgLiBXaGVuIGBmYWxzZWAgdGhlIGV4cGxpY2l0XG4gICAqIGlucHV0IGlzIHJldHVybmVkIHZlcmJhdGltIChEYXRhYnJpY2tzIHN1cmZhY2VzIHRoZSBjYW5vbmljYWwgNDA0IGlmIGl0XG4gICAqIGRvZXNuJ3QgZXhpc3QpLlxuICAgKi9cbiAgZnV6enk/OiBib29sZWFuO1xuICAvKiogUnVzdCBmdXp6eS1kaXN0YW5jZSB0aHJlc2hvbGQgZm9yd2FyZGVkIHRvIHRoZSBzZWFyY2ggbWF0Y2guICovXG4gIHRocmVzaG9sZD86IG51bWJlcjtcbiAgLyoqIFJlcXVpcmUgYSBtb2RlbCB0aGF0IHN1cHBvcnRzIGEgY29tcGxldGUgZnVuY3Rpb24tdG9vbCByb3VuZC10cmlwLiAqL1xuICByZXF1aXJlc1Rvb2xzPzogYm9vbGVhbjtcbiAgLyoqXG4gICAqIENoYXQgY2FwYWJpbGl0eSBjbGFzcyB0byByZXNvbHZlIHdoZW4gbm8gYGV4cGxpY2l0YCBpZCBpcyBnaXZlbi4gVGhlIGxpdmVcbiAgICogY2F0YWxvZ3VlIGlzIGNsYXNzaWZpZWQgYnkgaXRzIEZvdW5kYXRpb24gTW9kZWwgQVBJIHNjb3JlcyBhbmQgdGhlIHRvcFxuICAgKiBhdmFpbGFibGUgbW9kZWwgaW4gdGhlIGNsYXNzIChhbmQgdGhlIGNoYXQgYmFuZHMgYmVsb3cgaXQpIHdpbnMsIGZhbGxpbmdcbiAgICogYmFjayB0byB0aGUgY2xhc3MncyBzbWFsbCBzdGF0aWMgbGlzdC5cbiAgICovXG4gIG1vZGVsQ2xhc3M/OiBNb2RlbENsYXNzO1xuICAvKipcbiAgICogT3BlcmF0b3Itc3VwcGxpZWQgZmFsbGJhY2sgaWRzIHRyaWVkICpmaXJzdCogaW4gdGhlIG5vLWV4cGxpY2l0LCBuby1jbGFzc1xuICAgKiBwYXRoIChlLmcuIGEgcmVndWxhdGVkIHdvcmtzcGFjZSBwaW5uZWQgdG8gYW4gYXBwcm92ZWQgc3Vic2V0KSwgYWhlYWQgb2ZcbiAgICogdGhlIGF1dG8tY2xhc3NpZmllZCBjYXRhbG9ndWUuXG4gICAqL1xuICBmYWxsYmFja3M/OiByZWFkb25seSBzdHJpbmdbXTtcbiAgLyoqXG4gICAqIFJlZnVzZSB0aGUgc3RhdGljIG9mZmxpbmUgZmxvb3Igd2hlbiB0aGUgbGl2ZSBjYXRhbG9ndWUgaGFzIG5vIG1hdGNoLlxuICAgKiBVc2UgZm9yIHVucGlubmVkIGRlZmF1bHRzIHRoYXQgbXVzdCBhbHdheXMgbmFtZSBhIGN1cnJlbnRseSBhdmFpbGFibGVcbiAgICogZW5kcG9pbnQuXG4gICAqL1xuICBsaXZlT25seT86IGJvb2xlYW47XG59XG5cbi8qKiBPdXRjb21lIG9mIHtAbGluayByZXNvbHZlTW9kZWx9OiB0aGUgY2hvc2VuIGlkIHBsdXMgaG93IGl0IHdhcyByZWFjaGVkLiAqL1xuZXhwb3J0IGludGVyZmFjZSBSZXNvbHZlZE1vZGVsU2VsZWN0aW9uIHtcbiAgbW9kZWxJZDogc3RyaW5nO1xuICBzb3VyY2U6IFwiZXhwbGljaXRcIiB8IFwiZnV6enktbWF0Y2hcIiB8IFwiY2xhc3NcIiB8IFwiZmFsbGJhY2tcIjtcbn1cblxuLyoqIEludGVudCArIGNhdGFsb2d1ZSBrbm9icyBwYXNzZWQgdG8ge0BsaW5rIHNlbGVjdE1vZGVsfS4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgU2VsZWN0TW9kZWxJbnB1dCBleHRlbmRzIFJlc29sdmVNb2RlbElucHV0IHtcbiAgLyoqIFRUTCBvdmVycmlkZSBmb3IgdGhlIGNhY2hlZCBgL3NlcnZpbmctZW5kcG9pbnRzYCBsaXN0aW5nLCBpbiBtcy4gKi9cbiAgdHRsTXM/OiBudW1iZXI7XG4gIC8qKiBUcnVzdGVkIG9wYXF1ZSBpZGVudGl0eSBvZiB0aGUgY3JlZGVudGlhbCB1c2VkIGJ5IHRoZSB3b3Jrc3BhY2UgY2xpZW50LiAqL1xuICBjYWNoZUlkZW50aXR5Pzogc3RyaW5nO1xufVxuXG4vKiogVFRMIG92ZXJyaWRlIG1lcmdlZCBpbnRvIGEge0BsaW5rIE1vZGVsUXVlcnl9IGZvciB7QGxpbmsgc2VhcmNoTW9kZWxzfS4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgU2VhcmNoTW9kZWxzSW5wdXQgZXh0ZW5kcyBNb2RlbFF1ZXJ5IHtcbiAgLyoqIFRUTCBvdmVycmlkZSBmb3IgdGhlIGNhY2hlZCBgL3NlcnZpbmctZW5kcG9pbnRzYCBsaXN0aW5nLCBpbiBtcy4gKi9cbiAgdHRsTXM/OiBudW1iZXI7XG4gIC8qKiBUcnVzdGVkIG9wYXF1ZSBpZGVudGl0eSBvZiB0aGUgY3JlZGVudGlhbCB1c2VkIGJ5IHRoZSB3b3Jrc3BhY2UgY2xpZW50LiAqL1xuICBjYWNoZUlkZW50aXR5Pzogc3RyaW5nO1xufVxuXG4vKipcbiAqIFJhbmsgdGhlIGxpdmUgY2F0YWxvZ3VlIGFnYWluc3QgYSB7QGxpbmsgTW9kZWxRdWVyeX0sIGJlc3QtZmlyc3QuXG4gKlxuICogUnVzdCBvd25zIGNsYXNzaWZpY2F0aW9uLCBmdXp6eSBzY29yaW5nLCBmYW1pbHktdmVyc2lvbiBvcmRlcmluZywgcHJlZmVycmVkXG4gKiB2YXJpYW50cywgY2xhc3MgY2VpbGluZ3MsIHRvb2wgZmlsdGVyaW5nLCBhbmQgcmVzdWx0IGxpbWl0aW5nLiBOb2RlIHJldGFpbnNcbiAqIHRoZSBjYWxsZXIncyBlbmRwb2ludCBvYmplY3RzIHNvIGZpZWxkcyBvdXRzaWRlIHRoZSByYW5raW5nIGNvbnRyYWN0IHJlbWFpblxuICogYXZhaWxhYmxlIHVuY2hhbmdlZC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGxvb2t1cE1vZGVscyhcbiAgZW5kcG9pbnRzOiByZWFkb25seSBTZXJ2aW5nRW5kcG9pbnRTdW1tYXJ5W10sXG4gIHF1ZXJ5OiBNb2RlbFF1ZXJ5ID0ge30sXG4pOiBSYW5rZWRNb2RlbFtdIHtcbiAgcmV0dXJuIHJhbmtFbmRwb2ludHMoZW5kcG9pbnRzLCBxdWVyeSk7XG59XG5cbi8qKlxuICogQ29sbGFwc2Uge0BsaW5rIGxvb2t1cE1vZGVsc30gdG8gYSBzaW5nbGUgaWQ6IHRoZSBjbG9zZXN0IGVuZHBvaW50IHRvIGBzZWFyY2hgXG4gKiBpbiBhIGNhdGFsb2d1ZSBzbmFwc2hvdCwgb3IgdGhlIGlucHV0IHZlcmJhdGltIHdoZW4gbm90aGluZyBzY29yZXMgd2l0aGluIHRoZVxuICogdGhyZXNob2xkLlxuICpcbiAqIFRoZSByYW5rZWQgY291bnRlcnBhcnQgdG8ge0BsaW5rIHJlc29sdmVNb2RlbElkfTogZXF1YWxcbiAqIG1hdGNoIHNjb3JlcyBhcmUgYnJva2VuIGJ5IGNsYXNzIGFuZCB0aGVuIHdpdGhpbi1jbGFzcyB2ZXJzaW9uLCBzbyBhIGxvb3NlXG4gKiBgXCJvcHVzXCJgIHByZWZlcnMgYG9wdXMtNWAgb3ZlciBgb3B1cy00LTdgIGluc3RlYWQgb2YgcGlja2luZyB3aGljaGV2ZXJcbiAqIHNpYmxpbmcgYXBwZWFyZWQgZmlyc3QuIFJldHVybmluZyB0aGUgaW5wdXQgdW5tYXRjaGVkIChyYXRoZXJcbiAqIHRoYW4gYSBuZWFyIG5laWdoYm91cikgaXMgZGVsaWJlcmF0ZSAtIGEgZGVsaWJlcmF0ZSBlbmRwb2ludCBpZCBpcyBuZXZlclxuICogc2lsZW50bHkgcmV3cml0dGVuLCBhbmQgRGF0YWJyaWNrcyBzdXJmYWNlcyBhIGNsZWFuIDQwNC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHJhbmtNb2RlbElkKFxuICBlbmRwb2ludHM6IHJlYWRvbmx5IFNlcnZpbmdFbmRwb2ludFN1bW1hcnlbXSxcbiAgc2VhcmNoOiBzdHJpbmcsXG4gIG9wdGlvbnM6IFJlc29sdmVNb2RlbE9wdGlvbnMgPSB7fSxcbik6IFJlc29sdmVkTW9kZWwge1xuICBjb25zdCBbdG9wXSA9IGxvb2t1cE1vZGVscyhlbmRwb2ludHMsIHtcbiAgICBzZWFyY2gsXG4gICAgbGltaXQ6IDEsXG4gICAgLi4uKG9wdGlvbnMudGhyZXNob2xkICE9PSB1bmRlZmluZWQgPyB7IHRocmVzaG9sZDogb3B0aW9ucy50aHJlc2hvbGQgfSA6IHt9KSxcbiAgICAuLi4ob3B0aW9ucy5yZXF1aXJlc1Rvb2xzICE9PSB1bmRlZmluZWQgPyB7IHJlcXVpcmVzVG9vbHM6IG9wdGlvbnMucmVxdWlyZXNUb29scyB9IDoge30pLFxuICB9KTtcbiAgaWYgKCF0b3ApIHJldHVybiB7IG1vZGVsSWQ6IHNlYXJjaCwgbWF0Y2hlZDogZmFsc2UgfTtcbiAgcmV0dXJuIHsgbW9kZWxJZDogdG9wLmVuZHBvaW50Lm5hbWUsIG1hdGNoZWQ6IHRydWUsIHNjb3JlOiB0b3Auc2NvcmUgfTtcbn1cblxuLyoqXG4gKiB7QGxpbmsgcmFua01vZGVsSWR9IGFnYWluc3QgYSBjYXRhbG9ndWUgdGhlIGNhbGxlciBtYXkgYmUgaG9sZGluZyBzdGFsZTpcbiAqIG1hdGNoIHRoZSBsb2FkZWQgc25hcHNob3QsIGFuZCBvbiBhIG1pc3MgcmVsb2FkIG9uY2Ugd2l0aCBgZm9yY2VgIGFuZCBtYXRjaFxuICogYWdhaW4uIFRoYXQgd2F5IGEgbW9kZWwgZGVwbG95ZWQgYWZ0ZXIgdGhlIGNhdGFsb2d1ZSB3YXMgY2FjaGVkIHN0aWxsXG4gKiByZXNvbHZlcyBvbiBmaXJzdCB1c2UsIHdpdGhvdXQgYSByZXN0YXJ0IGFuZCB3aXRob3V0IGdpdmluZyB1cCBjYWNoaW5nLlxuICpcbiAqIFRoZSBjYXRhbG9ndWUgYXJyaXZlcyBhcyBhIGxvYWRlciByYXRoZXIgdGhhbiBhIGNsaWVudCBzbyB0aGUgY2FsbGVyIGtlZXBzXG4gKiBvd25lcnNoaXAgb2YgKmhvdyogaXQgaXMgY2FjaGVkIC0ge0BsaW5rIGxpc3RTZXJ2aW5nRW5kcG9pbnRzfSBhbmQgaXRzXG4gKiBgQ2FjaGVNYW5hZ2VyYCwgYSBwbGFpbiBwcm9jZXNzLWxpZmV0aW1lIGZpZWxkIGluIGEgQ0xJLCBvciBhIHRlc3QgZG91YmxlLlxuICogT25seSBvbmUgcmVsb2FkIGlzIGF0dGVtcHRlZDogYSBnZW51aW5lbHkgdW5rbm93biBuYW1lIHNob3VsZCBmYWlsIGZhc3RcbiAqIHJhdGhlciB0aGFuIHJlLWxpc3Qgb24gZXZlcnkgcmVxdWVzdC5cbiAqXG4gKiBAcGFyYW0gbG9hZCAtIFJldHVybnMgdGhlIGNhdGFsb2d1ZTsgYGZvcmNlYCBhc2tzIGl0IHRvIGJ5cGFzcyBpdHMgY2FjaGUuXG4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiByYW5rTW9kZWxJZExpdmUoXG4gIGxvYWQ6IChmb3JjZTogYm9vbGVhbikgPT4gUHJvbWlzZTxyZWFkb25seSBTZXJ2aW5nRW5kcG9pbnRTdW1tYXJ5W10+LFxuICBzZWFyY2g6IHN0cmluZyxcbiAgb3B0aW9uczogUmVzb2x2ZU1vZGVsT3B0aW9ucyA9IHt9LFxuKTogUHJvbWlzZTxSZXNvbHZlZE1vZGVsPiB7XG4gIGNvbnN0IHJlc29sdmVkID0gcmFua01vZGVsSWQoYXdhaXQgbG9hZChmYWxzZSksIHNlYXJjaCwgb3B0aW9ucyk7XG4gIGlmIChyZXNvbHZlZC5tYXRjaGVkKSByZXR1cm4gcmVzb2x2ZWQ7XG4gIHJldHVybiByYW5rTW9kZWxJZChhd2FpdCBsb2FkKHRydWUpLCBzZWFyY2gsIG9wdGlvbnMpO1xufVxuXG4vKipcbiAqIFJhbmsgYSB3b3Jrc3BhY2UncyBjYXRhbG9ndWUgaW4gb25lIGNhbGw6IGxpc3QgaXRzIGAvc2VydmluZy1lbmRwb2ludHNgXG4gKiAoY2FjaGVkKSBhbmQgcnVuIHtAbGluayBsb29rdXBNb2RlbHN9IG92ZXIgdGhlIHJlc3VsdC4gVGhlIGxpc3QgY291bnRlcnBhcnQgdG9cbiAqIHtAbGluayBzZWxlY3RNb2RlbH0sIGZvciBhIGNvbnN1bWVyIHRoYXQgd2FudHMgdGhlIGZ1bGwgcmFua2VkIHNldCAoYSBtb2RlbFxuICogcGlja2VyLCBhIENMSSkgcmF0aGVyIHRoYW4gYSBzaW5nbGUgaWQuIENhdGFsb2d1ZSBmZXRjaGVzIGZhaWwgbG91ZDogbmV0d29ya1xuICogLyBhdXRoIGVycm9ycyBwcm9wYWdhdGUgc28gdGhlIGNhbGxlciBzZWVzIHRoZSByZWFsIFNESyBtZXNzYWdlLlxuICpcbiAqIEBwYXJhbSBob3N0IC0gV29ya3NwYWNlIGhvc3QgdXNlZCBhcyB0aGUgY2FjaGUga2V5LiBQYXNzIHRoZSB2YWx1ZSByZXNvbHZlZFxuICogICBmcm9tIGBjbGllbnQuY29uZmlnLmdldEhvc3QoKWAuXG4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBzZWFyY2hNb2RlbHMoXG4gIGNsaWVudDogV29ya3NwYWNlQ2xpZW50TGlrZSxcbiAgaG9zdDogc3RyaW5nLFxuICBpbnB1dDogU2VhcmNoTW9kZWxzSW5wdXQgPSB7fSxcbik6IFByb21pc2U8UmFua2VkTW9kZWxbXT4ge1xuICBjb25zdCBlbmRwb2ludHMgPSBhd2FpdCBsaXN0U2VydmluZ0VuZHBvaW50cyhjbGllbnQsIGhvc3QsIHtcbiAgICAuLi4oaW5wdXQudHRsTXMgIT09IHVuZGVmaW5lZCA/IHsgdHRsTXM6IGlucHV0LnR0bE1zIH0gOiB7fSksXG4gICAgLi4uKGlucHV0LmNhY2hlSWRlbnRpdHkgIT09IHVuZGVmaW5lZCA/IHsgY2FjaGVJZGVudGl0eTogaW5wdXQuY2FjaGVJZGVudGl0eSB9IDoge30pLFxuICB9KTtcbiAgcmV0dXJuIGxvb2t1cE1vZGVscyhlbmRwb2ludHMsIGlucHV0KTtcbn1cblxuLyoqXG4gKiBSZXNvbHZlIGEgbW9kZWwgaWQgZm9yIGEgd29ya3NwYWNlIGluIG9uZSBjYWxsOiBsaXN0IGl0cyBgL3NlcnZpbmctZW5kcG9pbnRzYFxuICogKGNhY2hlZCkgYW5kIHJ1biB7QGxpbmsgcmVzb2x2ZU1vZGVsfSBvdmVyIHRoZSByZXN1bHQuIFRoaXMgaXMgdGhlIGVudHJ5XG4gKiBwb2ludCBmb3IgYW55IGNvbnN1bWVyIHRoYXQgaG9sZHMgYSBgV29ya3NwYWNlQ2xpZW50YCBhbmQganVzdCB3YW50cyBhIHVzYWJsZVxuICogbW9kZWwgbmFtZSAtIGEgTGFrZWZsb3cgam9iLCBhIG9uZS1vZmYgc2NyaXB0LCBvciB0aGUgTWFzdHJhIHBsdWdpbiBhbGlrZS5cbiAqXG4gKiBDaGVhcCBleGl0OiB3aGVuIGFuIGBleHBsaWNpdGAgbmFtZSBpcyBnaXZlbiwgYGZ1enp5YCBpcyBvZmYsIGFuZCB0b29sXG4gKiBjYXBhYmlsaXR5IGlzIG5vdCByZXF1aXJlZCwgdGhlIGNhdGFsb2d1ZSBpcyBuZXZlciBmZXRjaGVkLiBDYXRhbG9ndWVcbiAqIGZldGNoZXMgb3RoZXJ3aXNlIGZhaWwgbG91ZDogbmV0d29yayAvIGF1dGggZXJyb3JzIHByb3BhZ2F0ZSBzbyB0aGUgY2FsbGVyXG4gKiBzZWVzIHRoZSByZWFsIFNESyBtZXNzYWdlIGluc3RlYWQgb2YgYSBzaWxlbnQgZmFsbGJhY2suXG4gKlxuICogQHBhcmFtIGhvc3QgLSBXb3Jrc3BhY2UgaG9zdCB1c2VkIGFzIHRoZSBjYWNoZSBrZXkuIFBhc3MgdGhlIHZhbHVlIHJlc29sdmVkXG4gKiAgIGZyb20gYGNsaWVudC5jb25maWcuZ2V0SG9zdCgpYC5cbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIHNlbGVjdE1vZGVsKFxuICBjbGllbnQ6IFdvcmtzcGFjZUNsaWVudExpa2UsXG4gIGhvc3Q6IHN0cmluZyxcbiAgaW5wdXQ6IFNlbGVjdE1vZGVsSW5wdXQgPSB7fSxcbik6IFByb21pc2U8UmVzb2x2ZWRNb2RlbFNlbGVjdGlvbj4ge1xuICBpZiAoaW5wdXQuZXhwbGljaXQgIT09IHVuZGVmaW5lZCAmJiBpbnB1dC5mdXp6eSA9PT0gZmFsc2UgJiYgIWlucHV0LnJlcXVpcmVzVG9vbHMpIHtcbiAgICByZXR1cm4geyBtb2RlbElkOiBpbnB1dC5leHBsaWNpdCwgc291cmNlOiBcImV4cGxpY2l0XCIgfTtcbiAgfVxuICBjb25zdCBlbmRwb2ludHMgPSBhd2FpdCBsaXN0U2VydmluZ0VuZHBvaW50cyhjbGllbnQsIGhvc3QsIHtcbiAgICAuLi4oaW5wdXQudHRsTXMgIT09IHVuZGVmaW5lZCA/IHsgdHRsTXM6IGlucHV0LnR0bE1zIH0gOiB7fSksXG4gICAgLi4uKGlucHV0LmNhY2hlSWRlbnRpdHkgIT09IHVuZGVmaW5lZCA/IHsgY2FjaGVJZGVudGl0eTogaW5wdXQuY2FjaGVJZGVudGl0eSB9IDoge30pLFxuICB9KTtcbiAgcmV0dXJuIHJlc29sdmVNb2RlbChlbmRwb2ludHMsIGlucHV0KTtcbn1cblxuLyoqXG4gKiBSZXNvbHZlIGEgc2luZ2xlIG1vZGVsIGlkIGZyb20gdGhlIGxpdmUgY2F0YWxvZ3VlIGFuZCBjYWxsZXIgaW50ZW50LFxuICogZGVsZWdhdGluZyB0aGUgbGl2ZSBzZWxlY3Rpb24gdG8ge0BsaW5rIGxvb2t1cE1vZGVsc30gd2l0aCBgbGltaXQ6IDFgLlxuICpcbiAqIDEuICoqRXhwbGljaXQgYXNrKio6IHdpdGggYGZ1enp5YCBvZmYsIHJldHVybmVkIHZlcmJhdGltOyBvdGhlcndpc2VcbiAqICAgIGZ1enp5LXJhbmtlZCB3aXRoaW4gdGhlIChvcHRpb25hbCkgY2xhc3MgY2VpbGluZyBhbmQgdGhlIGJlc3QgdGFrZW4sXG4gKiAgICBmYWxsaW5nIGJhY2sgdG8gdGhlIGlucHV0IHZlcmJhdGltIHdoZW4gbm90aGluZyBtYXRjaGVzLlxuICogMi4gKipObyBleHBsaWNpdCBhc2sqKjogYW4gb3BlcmF0b3ItcGlubmVkIGBmYWxsYmFja2AgdGhhdCBleGlzdHMgaW4gdGhlIGxpdmVcbiAqICAgIGNhdGFsb2d1ZSB3aW5zIGZpcnN0LiBBIGNsYXNzIHJlcXVlc3QgcmFua3Mgd2l0aGluIHRoYXQgY2VpbGluZy4gQVxuICogICAgZ2VuZXJhbCByZXF1ZXN0IHByZWZlcnMgdGhlIGhpZ2hlc3QtcmFua2VkIGxpdmUgR1BULCB0aGVuIGFueSByYW5rZWQgbGl2ZVxuICogICAgY2hhdCBtb2RlbC4gVGhlIHN0YXRpYyB7QGxpbmsgRkFMTEJBQ0tfTU9ERUxfSURTfSBmbG9vciBpcyB1c2VkIG9ubHkgd2hlblxuICogICAgdGhlIGNhdGFsb2d1ZSB5aWVsZHMgbm90aGluZyBpbiByYW5nZS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHJlc29sdmVNb2RlbChcbiAgZW5kcG9pbnRzOiByZWFkb25seSBTZXJ2aW5nRW5kcG9pbnRTdW1tYXJ5W10sXG4gIGlucHV0OiBSZXNvbHZlTW9kZWxJbnB1dCA9IHt9LFxuKTogUmVzb2x2ZWRNb2RlbFNlbGVjdGlvbiB7XG4gIGlmIChpbnB1dC5leHBsaWNpdCAhPT0gdW5kZWZpbmVkKSB7XG4gICAgaWYgKGlucHV0LmZ1enp5ID09PSBmYWxzZSkge1xuICAgICAgaWYgKGlucHV0LnJlcXVpcmVzVG9vbHMpIGFzc2VydFRvb2xTdXBwb3J0KGVuZHBvaW50cywgaW5wdXQuZXhwbGljaXQpO1xuICAgICAgcmV0dXJuIHsgbW9kZWxJZDogaW5wdXQuZXhwbGljaXQsIHNvdXJjZTogXCJleHBsaWNpdFwiIH07XG4gICAgfVxuICAgIGNvbnN0IFt0b3BdID0gbG9va3VwTW9kZWxzKGVuZHBvaW50cywgYnVpbGRRdWVyeShpbnB1dCwgaW5wdXQuZXhwbGljaXQpKTtcbiAgICBpZiAoaW5wdXQucmVxdWlyZXNUb29scyAmJiAhdG9wKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoYE5vIHRvb2wtY2FwYWJsZSBtb2RlbCBtYXRjaGVzIFwiJHtpbnB1dC5leHBsaWNpdH1cImApO1xuICAgIH1cbiAgICByZXR1cm4geyBtb2RlbElkOiB0b3A/LmVuZHBvaW50Lm5hbWUgPz8gaW5wdXQuZXhwbGljaXQsIHNvdXJjZTogXCJmdXp6eS1tYXRjaFwiIH07XG4gIH1cblxuICAvLyBPcGVyYXRvci1waW5uZWQgZmFsbGJhY2tzIHdpbiB3aGVuIHByZXNlbnQgYW5kIGxpdmUgKGUuZy4gYSByZWd1bGF0ZWRcbiAgLy8gd29ya3NwYWNlIHJlc3RyaWN0ZWQgdG8gYW4gYXBwcm92ZWQgc3Vic2V0KS5cbiAgaWYgKGlucHV0Lm1vZGVsQ2xhc3MgPT09IHVuZGVmaW5lZCAmJiBpbnB1dC5mYWxsYmFja3MgJiYgaW5wdXQuZmFsbGJhY2tzLmxlbmd0aCA+IDApIHtcbiAgICBjb25zdCBwcmVzZW50ID0gbmV3IFNldChcbiAgICAgIGVuZHBvaW50c1xuICAgICAgICAuZmlsdGVyKChlbmRwb2ludCkgPT4gIWlucHV0LnJlcXVpcmVzVG9vbHMgfHwgZW5kcG9pbnRTdXBwb3J0c1Rvb2xzKGVuZHBvaW50KSlcbiAgICAgICAgLm1hcCgoZW5kcG9pbnQpID0+IGVuZHBvaW50Lm5hbWUpLFxuICAgICk7XG4gICAgY29uc3QgcGlubmVkID0gaW5wdXQuZmFsbGJhY2tzLmZpbmQoKGlkKSA9PiBwcmVzZW50LmhhcyhpZCkpO1xuICAgIGlmIChwaW5uZWQpIHJldHVybiB7IG1vZGVsSWQ6IHBpbm5lZCwgc291cmNlOiBcImZhbGxiYWNrXCIgfTtcbiAgfVxuXG4gIGNvbnN0IHNvdXJjZSA9IGlucHV0Lm1vZGVsQ2xhc3MgIT09IHVuZGVmaW5lZCA/IFwiY2xhc3NcIiA6IFwiZmFsbGJhY2tcIjtcbiAgaWYgKGlucHV0Lm1vZGVsQ2xhc3MgPT09IHVuZGVmaW5lZCkge1xuICAgIGNvbnN0IFtwcmVmZXJyZWRdID0gbG9va3VwTW9kZWxzKGVuZHBvaW50cywgYnVpbGRRdWVyeShpbnB1dCwgREVGQVVMVF9NT0RFTF9GQU1JTFlfU0VBUkNIKSk7XG4gICAgaWYgKHByZWZlcnJlZCkgcmV0dXJuIHsgbW9kZWxJZDogcHJlZmVycmVkLmVuZHBvaW50Lm5hbWUsIHNvdXJjZSB9O1xuICB9XG4gIGNvbnN0IFt0b3BdID0gbG9va3VwTW9kZWxzKGVuZHBvaW50cywgYnVpbGRRdWVyeShpbnB1dCwgdW5kZWZpbmVkKSk7XG4gIGlmICh0b3ApIHJldHVybiB7IG1vZGVsSWQ6IHRvcC5lbmRwb2ludC5uYW1lLCBzb3VyY2UgfTtcblxuICBpZiAoaW5wdXQubGl2ZU9ubHkpIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoXCJObyBtYXRjaGluZyBsaXZlIE1vZGVsIFNlcnZpbmcgZW5kcG9pbnQgaXMgYXZhaWxhYmxlXCIpO1xuICB9XG5cbiAgLy8gTGl2ZSBjYXRhbG9ndWUgeWllbGRlZCBub3RoaW5nIGluIHJhbmdlOiB3YWxrIHRoZSBzdGF0aWMgZmxvb3IuXG4gIGNvbnN0IGZsb29yU291cmNlID1cbiAgICBpbnB1dC5tb2RlbENsYXNzICE9PSB1bmRlZmluZWQgPyBtb2RlbHNGb3JDbGFzcyhpbnB1dC5tb2RlbENsYXNzKSA6IChpbnB1dC5mYWxsYmFja3MgPz8gW10pO1xuICBjb25zdCBmbG9vciA9IG9iamVjdC5zZXF1ZW5jZShmbG9vclNvdXJjZSkuY29uY2F0KEZBTExCQUNLX01PREVMX0lEUykuZGlzdGluY3QoKS50b0FycmF5KCk7XG4gIGlmIChpbnB1dC5yZXF1aXJlc1Rvb2xzKSB7XG4gICAgY29uc3QgYXZhaWxhYmxlID0gbmV3IFNldChcbiAgICAgIGVuZHBvaW50cy5maWx0ZXIoZW5kcG9pbnRTdXBwb3J0c1Rvb2xzKS5tYXAoKGVuZHBvaW50KSA9PiBlbmRwb2ludC5uYW1lKSxcbiAgICApO1xuICAgIGNvbnN0IHNlbGVjdGVkID0gZmxvb3IuZmluZCgoaWQpID0+IGF2YWlsYWJsZS5oYXMoaWQpKTtcbiAgICBpZiAoIXNlbGVjdGVkKSB0aHJvdyBuZXcgRXJyb3IoXCJObyB0b29sLWNhcGFibGUgbW9kZWwgaXMgYXZhaWxhYmxlXCIpO1xuICAgIHJldHVybiB7IG1vZGVsSWQ6IHNlbGVjdGVkLCBzb3VyY2UgfTtcbiAgfVxuICByZXR1cm4geyBtb2RlbElkOiBwaWNrRmlyc3RBdmFpbGFibGUoZmxvb3IsIGVuZHBvaW50cyksIHNvdXJjZSB9O1xufVxuXG4vKiogQnVpbGQgYSB7QGxpbmsgTW9kZWxRdWVyeX0gZnJvbSB7QGxpbmsgUmVzb2x2ZU1vZGVsSW5wdXR9IGZvciB0aGUgYGxpbWl0OiAxYCBkZWxlZ2F0aW9uLiAqL1xuZnVuY3Rpb24gYnVpbGRRdWVyeShpbnB1dDogUmVzb2x2ZU1vZGVsSW5wdXQsIHNlYXJjaDogc3RyaW5nIHwgdW5kZWZpbmVkKTogTW9kZWxRdWVyeSB7XG4gIHJldHVybiB7XG4gICAgLi4uKHNlYXJjaCAhPT0gdW5kZWZpbmVkID8geyBzZWFyY2ggfSA6IHt9KSxcbiAgICAuLi4oaW5wdXQubW9kZWxDbGFzcyAhPT0gdW5kZWZpbmVkID8geyBtb2RlbENsYXNzOiBpbnB1dC5tb2RlbENsYXNzIH0gOiB7fSksXG4gICAgLi4uKGlucHV0LnJlcXVpcmVzVG9vbHMgIT09IHVuZGVmaW5lZCA/IHsgcmVxdWlyZXNUb29sczogaW5wdXQucmVxdWlyZXNUb29scyB9IDoge30pLFxuICAgIC4uLihpbnB1dC50aHJlc2hvbGQgIT09IHVuZGVmaW5lZCA/IHsgdGhyZXNob2xkOiBpbnB1dC50aHJlc2hvbGQgfSA6IHt9KSxcbiAgICBsaW1pdDogMSxcbiAgfTtcbn1cblxuLyoqIFRocm93IHdoZW4gYW4gZXhwbGljaXQgaWQgaXMgYWJzZW50IG9yIG5vdCB2ZXJpZmllZCBmb3IgZnVuY3Rpb24gdG9vbHMuICovXG5mdW5jdGlvbiBhc3NlcnRUb29sU3VwcG9ydChlbmRwb2ludHM6IHJlYWRvbmx5IFNlcnZpbmdFbmRwb2ludFN1bW1hcnlbXSwgbW9kZWxJZDogc3RyaW5nKTogdm9pZCB7XG4gIGNvbnN0IGVuZHBvaW50ID0gZW5kcG9pbnRzLmZpbmQoKGNhbmRpZGF0ZSkgPT4gY2FuZGlkYXRlLm5hbWUgPT09IG1vZGVsSWQpO1xuICBpZiAoIWVuZHBvaW50IHx8ICFlbmRwb2ludFN1cHBvcnRzVG9vbHMoZW5kcG9pbnQpKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKGBNb2RlbCBcIiR7bW9kZWxJZH1cIiBkb2VzIG5vdCBzdXBwb3J0IGZ1bmN0aW9uIHRvb2xzYCk7XG4gIH1cbn1cblxuLyoqXG4gKiBGaW5kIHRoZSBmaXJzdCBpZCBpbiBgY2FuZGlkYXRlc2Agd2hvc2UgZW5kcG9pbnQgaXMgcHJlc2VudCBpbiBgZW5kcG9pbnRzYC5cbiAqIFJldHVybnMgdGhlIHRvcCBjYW5kaWRhdGUgd2hlbiB0aGUgd29ya3NwYWNlIGhhcyBub25lIG9mIHRoZW0gc28gY2FsbGVyc1xuICogYWx3YXlzIGdldCBhIHN0cmluZzsgYW4gb2ZmbGluZSB3b3Jrc3BhY2UgdGhlbiByZWNlaXZlcyBhIGNsZWFuIDQwNCBmcm9tXG4gKiBEYXRhYnJpY2tzIGluc3RlYWQgb2YgYSBtYWxmb3JtZWQgY29uZmlnLlxuICovXG5mdW5jdGlvbiBwaWNrRmlyc3RBdmFpbGFibGUoXG4gIGNhbmRpZGF0ZXM6IHJlYWRvbmx5IHN0cmluZ1tdLFxuICBlbmRwb2ludHM6IHJlYWRvbmx5IFNlcnZpbmdFbmRwb2ludFN1bW1hcnlbXSxcbik6IHN0cmluZyB7XG4gIGNvbnN0IHByZXNlbnQgPSBuZXcgU2V0KGVuZHBvaW50cy5tYXAoKGUpID0+IGUubmFtZSkpO1xuICBmb3IgKGNvbnN0IGNhbmRpZGF0ZSBvZiBjYW5kaWRhdGVzKSB7XG4gICAgaWYgKHByZXNlbnQuaGFzKGNhbmRpZGF0ZSkpIHJldHVybiBjYW5kaWRhdGU7XG4gIH1cbiAgcmV0dXJuIGNhbmRpZGF0ZXNbMF0gPz8gRkFMTEJBQ0tfTU9ERUxfSURTWzBdITtcbn1cbiJdfQ==
30
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVzb2x2ZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9yZXNvbHZlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQU9BLE9BQU8sRUFDTCxZQUFZLEVBQ1osV0FBVyxFQUdYLFlBQVksR0FDYixNQUFNLGlCQUFpQixDQUFDO0FBQ3pCLE9BQU8sRUFDTCxvQkFBb0IsR0FLckIsTUFBTSxvQkFBb0IsQ0FBQztBQUU1QixPQUFPLEVBQUUsWUFBWSxFQUFFLFdBQVcsRUFBRSxZQUFZLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQWUxRSxnRkFBZ0Y7QUFDaEYsTUFBTSxDQUFDLEtBQUssVUFBVSxlQUFlLENBQ25DLElBQW9FLEVBQ3BFLE1BQWMsRUFDZCxVQUErQixFQUFFO0lBRWpDLE1BQU0sUUFBUSxHQUFHLFdBQVcsQ0FBQyxNQUFNLElBQUksQ0FBQyxLQUFLLENBQUMsRUFBRSxNQUFNLEVBQUUsT0FBTyxDQUFDLENBQUM7SUFDakUsSUFBSSxRQUFRLENBQUMsT0FBTztRQUFFLE9BQU8sUUFBUSxDQUFDO0lBQ3RDLE9BQU8sV0FBVyxDQUFDLE1BQU0sSUFBSSxDQUFDLElBQUksQ0FBQyxFQUFFLE1BQU0sRUFBRSxPQUFPLENBQUMsQ0FBQztBQUN4RCxDQUFDO0FBRUQsaURBQWlEO0FBQ2pELE1BQU0sQ0FBQyxLQUFLLFVBQVUsWUFBWSxDQUNoQyxNQUEyQixFQUMzQixJQUFZLEVBQ1osUUFBMkIsRUFBRTtJQUU3QixNQUFNLFNBQVMsR0FBRyxNQUFNLG9CQUFvQixDQUFDLE1BQU0sRUFBRSxJQUFJLEVBQUUsZ0JBQWdCLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQztJQUNwRixPQUFPLFlBQVksQ0FBQyxTQUFTLEVBQUUsS0FBSyxDQUFDLENBQUM7QUFDeEMsQ0FBQztBQUVELHdEQUF3RDtBQUN4RCxNQUFNLENBQUMsS0FBSyxVQUFVLFdBQVcsQ0FDL0IsTUFBMkIsRUFDM0IsSUFBWSxFQUNaLFFBQTBCLEVBQUU7SUFFNUIsSUFBSSxLQUFLLENBQUMsUUFBUSxLQUFLLFNBQVMsSUFBSSxLQUFLLENBQUMsS0FBSyxLQUFLLEtBQUssSUFBSSxDQUFDLEtBQUssQ0FBQyxhQUFhLEVBQUUsQ0FBQztRQUNsRixPQUFPLEVBQUUsT0FBTyxFQUFFLEtBQUssQ0FBQyxRQUFRLEVBQUUsTUFBTSxFQUFFLFVBQVUsRUFBRSxDQUFDO0lBQ3pELENBQUM7SUFDRCxNQUFNLFNBQVMsR0FBRyxNQUFNLG9CQUFvQixDQUFDLE1BQU0sRUFBRSxJQUFJLEVBQUUsZ0JBQWdCLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQztJQUNwRixPQUFPLFlBQVksQ0FBQyxTQUFTLEVBQUUsS0FBSyxDQUFDLENBQUM7QUFDeEMsQ0FBQztBQUVELFNBQVMsZ0JBQWdCLENBQUMsS0FHekI7SUFDQyxPQUFPO1FBQ0wsR0FBRyxDQUFDLEtBQUssQ0FBQyxLQUFLLEtBQUssU0FBUyxDQUFDLENBQUMsQ0FBQyxFQUFFLEtBQUssRUFBRSxLQUFLLENBQUMsS0FBSyxFQUFFLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQztRQUM1RCxHQUFHLENBQUMsS0FBSyxDQUFDLGFBQWEsS0FBSyxTQUFTLENBQUMsQ0FBQyxDQUFDLEVBQUUsYUFBYSxFQUFFLEtBQUssQ0FBQyxhQUFhLEVBQUUsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDO0tBQ3JGLENBQUM7QUFDSixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBXb3Jrc3BhY2UtYXdhcmUgbW9kZWwgc2VsZWN0aW9uIHdyYXBwZXJzLlxuICpcbiAqIEBtb2R1bGVcbiAqL1xuaW1wb3J0IHR5cGUgeyBNb2RlbFF1ZXJ5LCBSYW5rZWRNb2RlbCwgU2VydmluZ0VuZHBvaW50U3VtbWFyeSB9IGZyb20gXCJAZGJ4LXRvb2xzL3NoYXJlZC1tb2RlbFwiO1xuXG5pbXBvcnQge1xuICBsb29rdXBNb2RlbHMsXG4gIHJhbmtNb2RlbElkLFxuICB0eXBlIFJlc29sdmVNb2RlbElucHV0LFxuICB0eXBlIFJlc29sdmVkTW9kZWxTZWxlY3Rpb24sXG4gIHJlc29sdmVNb2RlbCxcbn0gZnJvbSBcIi4vX3NlbGVjdGlvbi50c1wiO1xuaW1wb3J0IHtcbiAgbGlzdFNlcnZpbmdFbmRwb2ludHMsXG4gIHR5cGUgTGlzdFNlcnZpbmdFbmRwb2ludHNPcHRpb25zLFxuICB0eXBlIFJlc29sdmVkTW9kZWwsXG4gIHR5cGUgUmVzb2x2ZU1vZGVsT3B0aW9ucyxcbiAgdHlwZSBXb3Jrc3BhY2VDbGllbnRMaWtlLFxufSBmcm9tIFwiLi9tb2RlbC1jYXRhbG9nLnRzXCI7XG5cbmV4cG9ydCB7IGxvb2t1cE1vZGVscywgcmFua01vZGVsSWQsIHJlc29sdmVNb2RlbCB9IGZyb20gXCIuL19zZWxlY3Rpb24udHNcIjtcbmV4cG9ydCB0eXBlIHsgUmVzb2x2ZU1vZGVsSW5wdXQsIFJlc29sdmVkTW9kZWxTZWxlY3Rpb24gfSBmcm9tIFwiLi9fc2VsZWN0aW9uLnRzXCI7XG5cbi8qKiBJbnRlbnQgcGx1cyBjYXRhbG9ndWUgY2FjaGUgY29udHJvbHMgcGFzc2VkIHRvIHtAbGluayBzZWxlY3RNb2RlbH0uICovXG5leHBvcnQgaW50ZXJmYWNlIFNlbGVjdE1vZGVsSW5wdXQgZXh0ZW5kcyBSZXNvbHZlTW9kZWxJbnB1dCB7XG4gIHR0bE1zPzogbnVtYmVyO1xuICBjYWNoZUlkZW50aXR5Pzogc3RyaW5nO1xufVxuXG4vKiogUXVlcnkgcGx1cyBjYXRhbG9ndWUgY2FjaGUgY29udHJvbHMgcGFzc2VkIHRvIHtAbGluayBzZWFyY2hNb2RlbHN9LiAqL1xuZXhwb3J0IGludGVyZmFjZSBTZWFyY2hNb2RlbHNJbnB1dCBleHRlbmRzIE1vZGVsUXVlcnkge1xuICB0dGxNcz86IG51bWJlcjtcbiAgY2FjaGVJZGVudGl0eT86IHN0cmluZztcbn1cblxuLyoqIFJlc29sdmUgYWdhaW5zdCBhIHBvdGVudGlhbGx5IHN0YWxlIGNhdGFsb2d1ZSwgcmVmcmVzaGluZyBvbmNlIG9uIGEgbWlzcy4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiByYW5rTW9kZWxJZExpdmUoXG4gIGxvYWQ6IChmb3JjZTogYm9vbGVhbikgPT4gUHJvbWlzZTxyZWFkb25seSBTZXJ2aW5nRW5kcG9pbnRTdW1tYXJ5W10+LFxuICBzZWFyY2g6IHN0cmluZyxcbiAgb3B0aW9uczogUmVzb2x2ZU1vZGVsT3B0aW9ucyA9IHt9LFxuKTogUHJvbWlzZTxSZXNvbHZlZE1vZGVsPiB7XG4gIGNvbnN0IHJlc29sdmVkID0gcmFua01vZGVsSWQoYXdhaXQgbG9hZChmYWxzZSksIHNlYXJjaCwgb3B0aW9ucyk7XG4gIGlmIChyZXNvbHZlZC5tYXRjaGVkKSByZXR1cm4gcmVzb2x2ZWQ7XG4gIHJldHVybiByYW5rTW9kZWxJZChhd2FpdCBsb2FkKHRydWUpLCBzZWFyY2gsIG9wdGlvbnMpO1xufVxuXG4vKiogTGlzdCBhbmQgcmFuayBhIHdvcmtzcGFjZSBtb2RlbCBjYXRhbG9ndWUuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gc2VhcmNoTW9kZWxzKFxuICBjbGllbnQ6IFdvcmtzcGFjZUNsaWVudExpa2UsXG4gIGhvc3Q6IHN0cmluZyxcbiAgaW5wdXQ6IFNlYXJjaE1vZGVsc0lucHV0ID0ge30sXG4pOiBQcm9taXNlPFJhbmtlZE1vZGVsW10+IHtcbiAgY29uc3QgZW5kcG9pbnRzID0gYXdhaXQgbGlzdFNlcnZpbmdFbmRwb2ludHMoY2xpZW50LCBob3N0LCBjYXRhbG9ndWVPcHRpb25zKGlucHV0KSk7XG4gIHJldHVybiBsb29rdXBNb2RlbHMoZW5kcG9pbnRzLCBpbnB1dCk7XG59XG5cbi8qKiBMaXN0IGEgd29ya3NwYWNlIGNhdGFsb2d1ZSBhbmQgcmVzb2x2ZSBvbmUgbW9kZWwuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gc2VsZWN0TW9kZWwoXG4gIGNsaWVudDogV29ya3NwYWNlQ2xpZW50TGlrZSxcbiAgaG9zdDogc3RyaW5nLFxuICBpbnB1dDogU2VsZWN0TW9kZWxJbnB1dCA9IHt9LFxuKTogUHJvbWlzZTxSZXNvbHZlZE1vZGVsU2VsZWN0aW9uPiB7XG4gIGlmIChpbnB1dC5leHBsaWNpdCAhPT0gdW5kZWZpbmVkICYmIGlucHV0LmZ1enp5ID09PSBmYWxzZSAmJiAhaW5wdXQucmVxdWlyZXNUb29scykge1xuICAgIHJldHVybiB7IG1vZGVsSWQ6IGlucHV0LmV4cGxpY2l0LCBzb3VyY2U6IFwiZXhwbGljaXRcIiB9O1xuICB9XG4gIGNvbnN0IGVuZHBvaW50cyA9IGF3YWl0IGxpc3RTZXJ2aW5nRW5kcG9pbnRzKGNsaWVudCwgaG9zdCwgY2F0YWxvZ3VlT3B0aW9ucyhpbnB1dCkpO1xuICByZXR1cm4gcmVzb2x2ZU1vZGVsKGVuZHBvaW50cywgaW5wdXQpO1xufVxuXG5mdW5jdGlvbiBjYXRhbG9ndWVPcHRpb25zKGlucHV0OiB7XG4gIHJlYWRvbmx5IHR0bE1zPzogbnVtYmVyO1xuICByZWFkb25seSBjYWNoZUlkZW50aXR5Pzogc3RyaW5nO1xufSk6IExpc3RTZXJ2aW5nRW5kcG9pbnRzT3B0aW9ucyB7XG4gIHJldHVybiB7XG4gICAgLi4uKGlucHV0LnR0bE1zICE9PSB1bmRlZmluZWQgPyB7IHR0bE1zOiBpbnB1dC50dGxNcyB9IDoge30pLFxuICAgIC4uLihpbnB1dC5jYWNoZUlkZW50aXR5ICE9PSB1bmRlZmluZWQgPyB7IGNhY2hlSWRlbnRpdHk6IGlucHV0LmNhY2hlSWRlbnRpdHkgfSA6IHt9KSxcbiAgfTtcbn1cbiJdfQ==
@@ -1 +1 @@
1
- {"root":["../src/_native.ts","../src/classes.ts","../src/fallback.ts","../src/invoke.ts","../src/model-catalog.ts","../src/policy.ts","../src/resolve.ts","../index.ts"],"version":"5.9.3"}
1
+ {"root":["../src/_metadata-contract.ts","../src/_metadata-generator.ts","../src/_ranking.ts","../src/_selection.ts","../src/classes.ts","../src/classify.ts","../src/fallback.ts","../src/invoke.ts","../src/metadata.ts","../src/model-catalog.ts","../src/model-client.ts","../src/policy.ts","../src/python.ts","../src/resolve.ts","../index.ts"],"version":"5.9.3"}
package/package.json CHANGED
@@ -21,14 +21,16 @@
21
21
  "devDependencies": {
22
22
  "@types/bun": "1.3.14",
23
23
  "@types/node": "^24.6.0",
24
+ "cheerio": "^1.2.0",
24
25
  "typescript": "^5.9.3"
25
26
  },
26
27
  "dependencies": {
27
28
  "@databricks/appkit": "0.81.0",
28
- "@dbx-tools/appkit": "^0.9.21",
29
- "@dbx-tools/model-rs": "^0.9.21",
30
- "@dbx-tools/shared-core": "^0.9.21",
31
- "@dbx-tools/shared-model": "^0.9.21"
29
+ "@dbx-tools/appkit": "^0.9.29",
30
+ "@dbx-tools/auth": "^0.9.29",
31
+ "@dbx-tools/shared-core": "^0.9.29",
32
+ "@dbx-tools/shared-model": "^0.9.29",
33
+ "fuse.js": "^7.4.2"
32
34
  },
33
35
  "main": "./lib/index.js",
34
36
  "license": "Apache-2.0",
@@ -41,10 +43,14 @@
41
43
  "types": "./lib/index.d.ts",
42
44
  "default": "./lib/index.js"
43
45
  },
46
+ "./python": {
47
+ "types": "./lib/src/python.d.ts",
48
+ "default": "./lib/src/python.js"
49
+ },
44
50
  "./package.json": "./package.json"
45
51
  }
46
52
  },
47
- "version": "0.9.21",
53
+ "version": "0.9.29",
48
54
  "types": "./lib/index.d.ts",
49
55
  "type": "module",
50
56
  "exports": {
@@ -52,6 +58,10 @@
52
58
  "types": "./lib/index.d.ts",
53
59
  "default": "./lib/index.js"
54
60
  },
61
+ "./python": {
62
+ "types": "./lib/src/python.d.ts",
63
+ "default": "./lib/src/python.js"
64
+ },
55
65
  "./package.json": "./package.json"
56
66
  },
57
67
  "files": [
@@ -0,0 +1,56 @@
1
+ /** One day, matching the refresh cadence of Databricks model documentation. */
2
+ export const MODEL_METADATA_TTL_MS = 24 * 60 * 60 * 1000;
3
+
4
+ /** Databricks documentation URL for retired foundation models. */
5
+ export const RETIRED_MODELS_URL =
6
+ "https://docs.databricks.com/aws/en/machine-learning/retired-models-policy";
7
+
8
+ /** Databricks documentation URL for OpenAI Responses model support. */
9
+ export const OPENAI_RESPONSES_MODELS_URL =
10
+ "https://docs.databricks.com/aws/en/machine-learning/model-serving/query-openai-responses";
11
+
12
+ /** Databricks documentation URL for native web-search model support. */
13
+ export const WEB_SEARCH_MODELS_URL =
14
+ "https://docs.databricks.com/aws/en/machine-learning/model-serving/web-search";
15
+
16
+ /** Databricks documentation URL for Foundation Model API limits. */
17
+ export const MODEL_RATE_LIMITS_URL =
18
+ "https://docs.databricks.com/aws/en/machine-learning/foundation-model-apis/limits";
19
+
20
+ /** Build-generated retired-model snapshot. */
21
+ export interface RetiredModelsSnapshot {
22
+ readonly generatedAt: number;
23
+ readonly models: readonly string[];
24
+ }
25
+
26
+ /** Normalized model identities grouped by documented capability. */
27
+ export interface ModelCapabilityCatalogue {
28
+ readonly responses: readonly string[];
29
+ readonly imageInput: readonly string[];
30
+ readonly applyPatch: readonly string[];
31
+ readonly webSearch: readonly string[];
32
+ }
33
+
34
+ /** Build-generated model-capability snapshot. */
35
+ export interface ModelCapabilitiesSnapshot {
36
+ readonly generatedAt: number;
37
+ readonly capabilities: ModelCapabilityCatalogue;
38
+ }
39
+
40
+ /** Published pay-per-token limits for one model. */
41
+ export interface ModelRateLimits {
42
+ readonly inputTokensPerMinute: number | null;
43
+ readonly outputTokensPerMinute: number | null;
44
+ readonly queriesPerHour: number | null;
45
+ }
46
+
47
+ /** Model limits keyed by normalized Databricks model identity. */
48
+ export interface ModelRateLimitCatalogue {
49
+ readonly models: Readonly<Record<string, ModelRateLimits>>;
50
+ }
51
+
52
+ /** Build-generated model-rate-limit snapshot. */
53
+ export interface ModelRateLimitsSnapshot {
54
+ readonly generatedAt: number;
55
+ readonly catalogue: ModelRateLimitCatalogue;
56
+ }
@@ -0,0 +1,199 @@
1
+ import { load, type CheerioAPI } from "cheerio";
2
+
3
+ import type {
4
+ ModelCapabilitiesSnapshot,
5
+ ModelRateLimitCatalogue,
6
+ ModelRateLimitsSnapshot,
7
+ RetiredModelsSnapshot,
8
+ } from "./_metadata-contract.ts";
9
+ import { modelSearchQuery } from "./policy.ts";
10
+
11
+ type Selection = ReturnType<CheerioAPI>;
12
+
13
+ /** Parse retired model names from Databricks retirement-policy HTML. */
14
+ export function parseRetiredModels(html: string, generatedAt: number): RetiredModelsSnapshot {
15
+ const $ = load(html);
16
+ const models = new Set<string>();
17
+ $("table").each((_, table) => {
18
+ const rows = $(table).find("tr");
19
+ const header = collapseText($(rows.get(0)).find("th, td").first()).toLowerCase();
20
+ if (header !== "open model" && header !== "partner model") return;
21
+ rows.slice(1).each((__, row) => {
22
+ for (const name of collapseText($(row).find("th, td").first()).split("/")) {
23
+ const trimmed = name.trim();
24
+ if (trimmed) models.add(trimmed);
25
+ }
26
+ });
27
+ });
28
+ if (models.size === 0) throw new Error("Databricks retirement tables contained no model names");
29
+ return {
30
+ generatedAt,
31
+ models: [...models].sort((left, right) =>
32
+ left.localeCompare(right, "en", { sensitivity: "base" }),
33
+ ),
34
+ };
35
+ }
36
+
37
+ /** Parse Responses and web-search documentation into model capability sets. */
38
+ export function parseModelCapabilities(
39
+ responsesHtml: string,
40
+ webSearchHtml: string,
41
+ generatedAt: number,
42
+ ): ModelCapabilitiesSnapshot {
43
+ const responsesDocument = load(responsesHtml);
44
+ const responses = modelsAfterHeading(responsesDocument, "databricks-hosted-foundation-models");
45
+ if (responses.length === 0) {
46
+ throw new Error("Databricks documentation contained no OpenAI Responses models");
47
+ }
48
+ const inputTypes = sectionElements(responsesDocument, "supported-input-types")
49
+ .map((element) => collapseText(element))
50
+ .join(" ")
51
+ .toLowerCase();
52
+ const inputTypeWords = new Set(inputTypes.split(/[^a-z0-9]+/).filter(Boolean));
53
+ const imageInput = inputTypeWords.has("text") && inputTypeWords.has("image") ? responses : [];
54
+ const applyPatch = codeValuesAfterHeading(responsesDocument, "limitations").includes(
55
+ "apply_patch",
56
+ )
57
+ ? responses
58
+ : [];
59
+ const webSearch = nativeWebSearchModels(load(webSearchHtml), responses);
60
+ if (webSearch.length === 0) {
61
+ throw new Error("Databricks documentation contained no native web-search models");
62
+ }
63
+ return {
64
+ generatedAt,
65
+ capabilities: { responses, imageInput, applyPatch, webSearch },
66
+ };
67
+ }
68
+
69
+ /** Parse pay-per-token limits from Databricks Foundation Model API HTML. */
70
+ export function parseModelRateLimits(html: string, generatedAt: number): ModelRateLimitsSnapshot {
71
+ const $ = load(html);
72
+ const models: Record<string, ModelRateLimitCatalogue["models"][string]> = {};
73
+ $("table").each((_, table) => {
74
+ const rows = $(table).find("tr");
75
+ const headers = $(rows.get(0))
76
+ .find("th, td")
77
+ .toArray()
78
+ .map((cell) => collapseText($(cell)).toLowerCase());
79
+ const modelIndex = headers.findIndex((value) => value.includes("model"));
80
+ const inputIndex = headers.findIndex((value) => value.includes("itpm"));
81
+ const outputIndex = headers.findIndex((value) => value.includes("otpm"));
82
+ const queryIndex = headers.findIndex((value) => value.includes("qph"));
83
+ if (modelIndex < 0 || inputIndex < 0 || outputIndex < 0) return;
84
+ rows.slice(1).each((__, row) => {
85
+ const cells = $(row)
86
+ .find("th, td")
87
+ .toArray()
88
+ .map((cell) => collapseText($(cell)));
89
+ const key = modelKey(cells[modelIndex]);
90
+ if (!key) return;
91
+ models[key] = {
92
+ inputTokensPerMinute: number(cells[inputIndex]),
93
+ outputTokensPerMinute: number(cells[outputIndex]),
94
+ queriesPerHour: queryIndex < 0 ? null : number(cells[queryIndex]),
95
+ };
96
+ });
97
+ });
98
+ if (Object.keys(models).length === 0) {
99
+ throw new Error("Databricks model-limit documentation contained no models");
100
+ }
101
+ return { generatedAt, catalogue: { models: sortRecord(models) } };
102
+ }
103
+
104
+ function modelsAfterHeading($: CheerioAPI, headingId: string): string[] {
105
+ return [
106
+ ...new Set(codeValuesAfterHeading($, headingId).flatMap((value) => modelKey(value) ?? [])),
107
+ ].sort();
108
+ }
109
+
110
+ function nativeWebSearchModels($: CheerioAPI, responses: readonly string[]): string[] {
111
+ const families = new Set<string>();
112
+ const models = new Set<string>();
113
+ let nativeSection = false;
114
+ for (const element of sectionElements($, "supported-models")) {
115
+ if (headingLevel(nodeTagName(element.get(0))) !== undefined) {
116
+ nativeSection = !collapseText(element).toLowerCase().includes("via mcp");
117
+ continue;
118
+ }
119
+ if (!nativeSection) continue;
120
+ const values = nodeTagName(element.get(0)) === "code" ? [collapseText(element)] : [];
121
+ element.find("code").each((_, code) => {
122
+ values.push(collapseText($(code)));
123
+ });
124
+ for (const value of values) {
125
+ const model = modelKey(value);
126
+ if (!model) continue;
127
+ models.add(model);
128
+ families.add(model.split("-", 1)[0]);
129
+ }
130
+ }
131
+ for (const model of responses) {
132
+ if (families.has(model.split("-", 1)[0])) models.add(model);
133
+ }
134
+ return [...models].sort();
135
+ }
136
+
137
+ function codeValuesAfterHeading($: CheerioAPI, headingId: string): string[] {
138
+ const values: string[] = [];
139
+ for (const element of sectionElements($, headingId)) {
140
+ if (nodeTagName(element.get(0)) === "code") values.push(collapseText(element));
141
+ element.find("code").each((_, code) => {
142
+ values.push(collapseText($(code)));
143
+ });
144
+ }
145
+ return values;
146
+ }
147
+
148
+ function sectionElements($: CheerioAPI, headingId: string): Selection[] {
149
+ const heading = $(`#${headingId}`).first();
150
+ if (heading.length === 0)
151
+ throw new Error(`Databricks capability page is missing section ${headingId}`);
152
+ const level = headingLevel(nodeTagName(heading.get(0)));
153
+ if (level === undefined)
154
+ throw new Error(`Databricks capability section ${headingId} is not a heading`);
155
+ const elements: Selection[] = [];
156
+ let sibling = heading.next();
157
+ while (sibling.length > 0) {
158
+ const siblingLevel = headingLevel(nodeTagName(sibling.get(0)));
159
+ if (siblingLevel !== undefined && siblingLevel <= level) break;
160
+ elements.push(sibling);
161
+ sibling = sibling.next();
162
+ }
163
+ return elements;
164
+ }
165
+
166
+ function headingLevel(name: string | undefined): number | undefined {
167
+ const match = /^h([1-6])$/.exec(name ?? "");
168
+ return match ? Number(match[1]) : undefined;
169
+ }
170
+
171
+ function collapseText(element: Selection): string {
172
+ return element.text().split(/\s+/).filter(Boolean).join(" ");
173
+ }
174
+
175
+ function nodeTagName(node: unknown): string | undefined {
176
+ if (!node || typeof node !== "object" || !("tagName" in node)) return undefined;
177
+ return typeof node.tagName === "string" ? node.tagName : undefined;
178
+ }
179
+
180
+ function modelKey(value: string | undefined): string | undefined {
181
+ if (!value) return undefined;
182
+ let name = value.trim().replace(/\*+$/, "").trim();
183
+ for (const suffix of ["(Public Preview)", "(Beta)", "(Preview)"]) {
184
+ if (name.endsWith(suffix)) name = name.slice(0, -suffix.length).trim();
185
+ }
186
+ return modelSearchQuery(name)?.replaceAll(" ", "-");
187
+ }
188
+
189
+ function number(value: string | undefined): number | null {
190
+ if (!value) return null;
191
+ const parsed = Number(value.replaceAll(",", "").trim());
192
+ return Number.isSafeInteger(parsed) && parsed >= 0 ? parsed : null;
193
+ }
194
+
195
+ function sortRecord<T>(record: Readonly<Record<string, T>>): Record<string, T> {
196
+ return Object.fromEntries(
197
+ Object.entries(record).sort(([left], [right]) => left.localeCompare(right)),
198
+ );
199
+ }