@dbx-tools/cli-model-proxy 0.3.44 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/bin/dbx-tools-model-proxy.d.ts +2 -0
- package/lib/bin/dbx-tools-model-proxy.js +13 -0
- package/lib/index.d.ts +11 -0
- package/lib/index.js +12 -0
- package/lib/src/backend.d.ts +77 -0
- package/lib/src/backend.js +100 -0
- package/lib/src/cli.d.ts +25 -0
- package/lib/src/cli.js +164 -0
- package/lib/src/defaults.d.ts +43 -0
- package/lib/src/defaults.js +48 -0
- package/lib/src/server.d.ts +79 -0
- package/lib/src/server.js +515 -0
- package/lib/tsconfig.tsbuildinfo +1 -0
- package/package.json +28 -12
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env -S npx tsx
|
|
2
|
+
/**
|
|
3
|
+
* `dbx-tools-model-proxy` entry: a local OpenAI-compatible proxy in front of Databricks
|
|
4
|
+
* Model Serving. Delegates to the commander program in `../src/cli`.
|
|
5
|
+
*/
|
|
6
|
+
import { CommanderError, runCli } from "../src/cli.js";
|
|
7
|
+
runCli(process.argv).catch((err) => {
|
|
8
|
+
if (err instanceof CommanderError)
|
|
9
|
+
process.exit(err.exitCode);
|
|
10
|
+
process.stderr.write(`${err instanceof Error ? (err.stack ?? err.message) : String(err)}\n`);
|
|
11
|
+
process.exit(1);
|
|
12
|
+
});
|
|
13
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGJ4LXRvb2xzLW1vZGVsLXByb3h5LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vYmluL2RieC10b29scy1tb2RlbC1wcm94eS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQ0E7OztHQUdHO0FBQ0gsT0FBTyxFQUFFLGNBQWMsRUFBRSxNQUFNLEVBQUUsTUFBTSxZQUFZLENBQUM7QUFFcEQsTUFBTSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxHQUFZLEVBQUUsRUFBRTtJQUMxQyxJQUFJLEdBQUcsWUFBWSxjQUFjO1FBQUUsT0FBTyxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsUUFBUSxDQUFDLENBQUM7SUFDOUQsT0FBTyxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsR0FBRyxHQUFHLFlBQVksS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxLQUFLLElBQUksR0FBRyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxDQUFDO0lBQzdGLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDbEIsQ0FBQyxDQUFDLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIjIS91c3IvYmluL2VudiAtUyBucHggdHN4XG4vKipcbiAqIGBkYngtdG9vbHMtbW9kZWwtcHJveHlgIGVudHJ5OiBhIGxvY2FsIE9wZW5BSS1jb21wYXRpYmxlIHByb3h5IGluIGZyb250IG9mIERhdGFicmlja3NcbiAqIE1vZGVsIFNlcnZpbmcuIERlbGVnYXRlcyB0byB0aGUgY29tbWFuZGVyIHByb2dyYW0gaW4gYC4uL3NyYy9jbGlgLlxuICovXG5pbXBvcnQgeyBDb21tYW5kZXJFcnJvciwgcnVuQ2xpIH0gZnJvbSBcIi4uL3NyYy9jbGlcIjtcblxucnVuQ2xpKHByb2Nlc3MuYXJndikuY2F0Y2goKGVycjogdW5rbm93bikgPT4ge1xuICBpZiAoZXJyIGluc3RhbmNlb2YgQ29tbWFuZGVyRXJyb3IpIHByb2Nlc3MuZXhpdChlcnIuZXhpdENvZGUpO1xuICBwcm9jZXNzLnN0ZGVyci53cml0ZShgJHtlcnIgaW5zdGFuY2VvZiBFcnJvciA/IChlcnIuc3RhY2sgPz8gZXJyLm1lc3NhZ2UpIDogU3RyaW5nKGVycil9XFxuYCk7XG4gIHByb2Nlc3MuZXhpdCgxKTtcbn0pO1xuIl19
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export * as backend from "./src/backend.js";
|
|
2
|
+
export * as cli from "./src/cli.js";
|
|
3
|
+
export * as defaults from "./src/defaults.js";
|
|
4
|
+
export * as server from "./src/server.js";
|
|
5
|
+
export { DatabricksBackend } from "./src/backend.js";
|
|
6
|
+
export type { BackendOptions } from "./src/backend.js";
|
|
7
|
+
export { buildProgram, runCli, CommanderError } from "./src/cli.js";
|
|
8
|
+
export { DEFAULT_BIND_HOST, DEFAULT_PORT, DEFAULT_RETRY, resolveRetryConfig } from "./src/defaults.js";
|
|
9
|
+
export type { RetryConfig } from "./src/defaults.js";
|
|
10
|
+
export { createProxyServer, parseRetryAfterMs, backoffDelayMs, startProxyServer } from "./src/server.js";
|
|
11
|
+
export type { ProxyServerOptions, StartProxyOptions } from "./src/server.js";
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// GENERATED by projen watch - DO NOT EDIT.
|
|
2
|
+
// Regenerated from the exporting modules in ./src.
|
|
3
|
+
// Hand edits are overwritten on the next watch; this file is read-only.
|
|
4
|
+
export * as backend from "./src/backend.js";
|
|
5
|
+
export * as cli from "./src/cli.js";
|
|
6
|
+
export * as defaults from "./src/defaults.js";
|
|
7
|
+
export * as server from "./src/server.js";
|
|
8
|
+
export { DatabricksBackend } from "./src/backend.js";
|
|
9
|
+
export { buildProgram, runCli, CommanderError } from "./src/cli.js";
|
|
10
|
+
export { DEFAULT_BIND_HOST, DEFAULT_PORT, DEFAULT_RETRY, resolveRetryConfig } from "./src/defaults.js";
|
|
11
|
+
export { createProxyServer, parseRetryAfterMs, backoffDelayMs, startProxyServer } from "./src/server.js";
|
|
12
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwyQ0FBMkM7QUFDM0MsbURBQW1EO0FBQ25ELHdFQUF3RTtBQUV4RSxPQUFPLEtBQUssT0FBTyxNQUFNLGVBQWUsQ0FBQztBQUN6QyxPQUFPLEtBQUssR0FBRyxNQUFNLFdBQVcsQ0FBQztBQUNqQyxPQUFPLEtBQUssUUFBUSxNQUFNLGdCQUFnQixDQUFDO0FBQzNDLE9BQU8sS0FBSyxNQUFNLE1BQU0sY0FBYyxDQUFDO0FBQ3ZDLE9BQU8sRUFBRSxpQkFBaUIsRUFBRSxNQUFNLGVBQWUsQ0FBQztBQUVsRCxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sRUFBRSxjQUFjLEVBQUUsTUFBTSxXQUFXLENBQUM7QUFDakUsT0FBTyxFQUFFLGlCQUFpQixFQUFFLFlBQVksRUFBRSxhQUFhLEVBQUUsa0JBQWtCLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQztBQUVwRyxPQUFPLEVBQUUsaUJBQWlCLEVBQUUsaUJBQWlCLEVBQUUsY0FBYyxFQUFFLGdCQUFnQixFQUFFLE1BQU0sY0FBYyxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLy8gR0VORVJBVEVEIGJ5IHByb2plbiB3YXRjaCAtIERPIE5PVCBFRElULlxuLy8gUmVnZW5lcmF0ZWQgZnJvbSB0aGUgZXhwb3J0aW5nIG1vZHVsZXMgaW4gLi9zcmMuXG4vLyBIYW5kIGVkaXRzIGFyZSBvdmVyd3JpdHRlbiBvbiB0aGUgbmV4dCB3YXRjaDsgdGhpcyBmaWxlIGlzIHJlYWQtb25seS5cblxuZXhwb3J0ICogYXMgYmFja2VuZCBmcm9tIFwiLi9zcmMvYmFja2VuZFwiO1xuZXhwb3J0ICogYXMgY2xpIGZyb20gXCIuL3NyYy9jbGlcIjtcbmV4cG9ydCAqIGFzIGRlZmF1bHRzIGZyb20gXCIuL3NyYy9kZWZhdWx0c1wiO1xuZXhwb3J0ICogYXMgc2VydmVyIGZyb20gXCIuL3NyYy9zZXJ2ZXJcIjtcbmV4cG9ydCB7IERhdGFicmlja3NCYWNrZW5kIH0gZnJvbSBcIi4vc3JjL2JhY2tlbmRcIjtcbmV4cG9ydCB0eXBlIHsgQmFja2VuZE9wdGlvbnMgfSBmcm9tIFwiLi9zcmMvYmFja2VuZFwiO1xuZXhwb3J0IHsgYnVpbGRQcm9ncmFtLCBydW5DbGksIENvbW1hbmRlckVycm9yIH0gZnJvbSBcIi4vc3JjL2NsaVwiO1xuZXhwb3J0IHsgREVGQVVMVF9CSU5EX0hPU1QsIERFRkFVTFRfUE9SVCwgREVGQVVMVF9SRVRSWSwgcmVzb2x2ZVJldHJ5Q29uZmlnIH0gZnJvbSBcIi4vc3JjL2RlZmF1bHRzXCI7XG5leHBvcnQgdHlwZSB7IFJldHJ5Q29uZmlnIH0gZnJvbSBcIi4vc3JjL2RlZmF1bHRzXCI7XG5leHBvcnQgeyBjcmVhdGVQcm94eVNlcnZlciwgcGFyc2VSZXRyeUFmdGVyTXMsIGJhY2tvZmZEZWxheU1zLCBzdGFydFByb3h5U2VydmVyIH0gZnJvbSBcIi4vc3JjL3NlcnZlclwiO1xuZXhwb3J0IHR5cGUgeyBQcm94eVNlcnZlck9wdGlvbnMsIFN0YXJ0UHJveHlPcHRpb25zIH0gZnJvbSBcIi4vc3JjL3NlcnZlclwiO1xuIl19
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Databricks backend for the local model proxy.
|
|
3
|
+
*
|
|
4
|
+
* Wraps a single default-auth {@link WorkspaceClient} and exposes the three
|
|
5
|
+
* things the proxy server needs, each delegating to `@dbx-tools/model`: the
|
|
6
|
+
* workspace serving-endpoint list, fuzzy name resolution ({@link
|
|
7
|
+
* resolve.rankModelIdLive}, so a loose `"opus"` / `"claude sonnet"` snaps to
|
|
8
|
+
* the best live endpoint id - match score, then class, then within-class
|
|
9
|
+
* version), and a fresh set of auth headers per upstream request ({@link
|
|
10
|
+
* invoke.authHeaders}).
|
|
11
|
+
*
|
|
12
|
+
* What is left here is only the proxy's own policy: which client to construct,
|
|
13
|
+
* and holding the endpoint catalogue for the process lifetime. The catalogue is
|
|
14
|
+
* listed once and re-listed on a resolve miss, so a model deployed after
|
|
15
|
+
* start-up still resolves on first use - no cache layer, just one lazy load.
|
|
16
|
+
*
|
|
17
|
+
* @module
|
|
18
|
+
*/
|
|
19
|
+
import { type ResolvedModel } from "@dbx-tools/model";
|
|
20
|
+
import { type ServingEndpointSummary } from "@dbx-tools/shared-model";
|
|
21
|
+
/** Options for {@link DatabricksBackend.create}. */
|
|
22
|
+
export interface BackendOptions {
|
|
23
|
+
/** Databricks config profile (`~/.databrickscfg`). Defaults to SDK auth resolution. */
|
|
24
|
+
profile?: string;
|
|
25
|
+
/** Override the workspace host; otherwise resolved from SDK auth (env / profile). */
|
|
26
|
+
host?: string;
|
|
27
|
+
/** Fuse.js fuzzy threshold (0 = exact, 1 = anything). Defaults to the model package default. */
|
|
28
|
+
threshold?: number;
|
|
29
|
+
}
|
|
30
|
+
export declare class DatabricksBackend {
|
|
31
|
+
private readonly client;
|
|
32
|
+
/** Resolved workspace host, e.g. `https://my-workspace.cloud.databricks.com/`. */
|
|
33
|
+
readonly host: string;
|
|
34
|
+
private readonly threshold;
|
|
35
|
+
/** Lazily loaded endpoint catalogue, reused for the process lifetime. */
|
|
36
|
+
private endpoints;
|
|
37
|
+
private constructor();
|
|
38
|
+
/**
|
|
39
|
+
* Build a backend: construct a default-auth workspace client (optionally
|
|
40
|
+
* pinned to a profile / host) and resolve the workspace host once, so a bad
|
|
41
|
+
* profile fails at start-up rather than on the first proxied request.
|
|
42
|
+
*/
|
|
43
|
+
static create(options?: BackendOptions): Promise<DatabricksBackend>;
|
|
44
|
+
/**
|
|
45
|
+
* The workspace's serving-endpoint catalogue, as the minimal
|
|
46
|
+
* {@link ServingEndpointSummary} the resolver needs. Loaded lazily and
|
|
47
|
+
* reused; pass `force` to re-list (used by `/v1/models` and the
|
|
48
|
+
* resolve-on-miss path).
|
|
49
|
+
*
|
|
50
|
+
* Deliberately the uncached listing: the cached one runs through AppKit's
|
|
51
|
+
* `CacheManager`, which a plain CLI has no app to initialize.
|
|
52
|
+
*/
|
|
53
|
+
models(force?: boolean): Promise<ServingEndpointSummary[]>;
|
|
54
|
+
/**
|
|
55
|
+
* Snap a (possibly loose) OpenAI-style model name to the best real serving
|
|
56
|
+
* endpoint. {@link resolve.rankModelIdLive} owns the policy: rank against the
|
|
57
|
+
* loaded catalogue, and on a miss re-list once and retry so a freshly
|
|
58
|
+
* deployed model resolves without a restart. An unmatched name comes back
|
|
59
|
+
* unchanged so a deliberate endpoint id is never silently rewritten.
|
|
60
|
+
*/
|
|
61
|
+
resolve(model: string): Promise<ResolvedModel>;
|
|
62
|
+
/**
|
|
63
|
+
* Mint auth headers for one upstream request. The SDK refreshes the
|
|
64
|
+
* underlying token when needed, so every call gets a valid `Authorization`
|
|
65
|
+
* header without the proxy tracking expiry.
|
|
66
|
+
*/
|
|
67
|
+
authHeaders(): Promise<Record<string, string>>;
|
|
68
|
+
/** OpenAI-compatible chat-completions invocations URL for a resolved endpoint id. */
|
|
69
|
+
invocationsUrl(endpoint: string): string;
|
|
70
|
+
/**
|
|
71
|
+
* Databricks Responses upstream for a resolved endpoint id
|
|
72
|
+
* (`/serving-endpoints/responses` for OpenAI-family, `/open-responses` else).
|
|
73
|
+
*/
|
|
74
|
+
responsesUrl(endpoint: string): string;
|
|
75
|
+
/** True when the endpoint rejects Chat Completions and needs Responses. */
|
|
76
|
+
isResponsesOnly(endpoint: string): boolean;
|
|
77
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Databricks backend for the local model proxy.
|
|
3
|
+
*
|
|
4
|
+
* Wraps a single default-auth {@link WorkspaceClient} and exposes the three
|
|
5
|
+
* things the proxy server needs, each delegating to `@dbx-tools/model`: the
|
|
6
|
+
* workspace serving-endpoint list, fuzzy name resolution ({@link
|
|
7
|
+
* resolve.rankModelIdLive}, so a loose `"opus"` / `"claude sonnet"` snaps to
|
|
8
|
+
* the best live endpoint id - match score, then class, then within-class
|
|
9
|
+
* version), and a fresh set of auth headers per upstream request ({@link
|
|
10
|
+
* invoke.authHeaders}).
|
|
11
|
+
*
|
|
12
|
+
* What is left here is only the proxy's own policy: which client to construct,
|
|
13
|
+
* and holding the endpoint catalogue for the process lifetime. The catalogue is
|
|
14
|
+
* listed once and re-listed on a resolve miss, so a model deployed after
|
|
15
|
+
* start-up still resolves on first use - no cache layer, just one lazy load.
|
|
16
|
+
*
|
|
17
|
+
* @module
|
|
18
|
+
*/
|
|
19
|
+
import { WorkspaceClient } from "@databricks/sdk-experimental";
|
|
20
|
+
import { invoke, resolve, serving } from "@dbx-tools/model";
|
|
21
|
+
import { log } from "@dbx-tools/shared-core";
|
|
22
|
+
const logger = log.logger("model-proxy/backend");
|
|
23
|
+
export class DatabricksBackend {
|
|
24
|
+
client;
|
|
25
|
+
/** Resolved workspace host, e.g. `https://my-workspace.cloud.databricks.com/`. */
|
|
26
|
+
host;
|
|
27
|
+
threshold;
|
|
28
|
+
/** Lazily loaded endpoint catalogue, reused for the process lifetime. */
|
|
29
|
+
endpoints;
|
|
30
|
+
constructor(client, host, threshold) {
|
|
31
|
+
this.client = client;
|
|
32
|
+
this.host = host;
|
|
33
|
+
this.threshold = threshold;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Build a backend: construct a default-auth workspace client (optionally
|
|
37
|
+
* pinned to a profile / host) and resolve the workspace host once, so a bad
|
|
38
|
+
* profile fails at start-up rather than on the first proxied request.
|
|
39
|
+
*/
|
|
40
|
+
static async create(options = {}) {
|
|
41
|
+
const client = new WorkspaceClient({
|
|
42
|
+
...(options.host ? { host: options.host } : {}),
|
|
43
|
+
...(options.profile ? { profile: options.profile } : {}),
|
|
44
|
+
});
|
|
45
|
+
const host = (await client.config.getHost()).toString();
|
|
46
|
+
logger.info("connected", { host });
|
|
47
|
+
return new DatabricksBackend(client, host, options.threshold);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The workspace's serving-endpoint catalogue, as the minimal
|
|
51
|
+
* {@link ServingEndpointSummary} the resolver needs. Loaded lazily and
|
|
52
|
+
* reused; pass `force` to re-list (used by `/v1/models` and the
|
|
53
|
+
* resolve-on-miss path).
|
|
54
|
+
*
|
|
55
|
+
* Deliberately the uncached listing: the cached one runs through AppKit's
|
|
56
|
+
* `CacheManager`, which a plain CLI has no app to initialize.
|
|
57
|
+
*/
|
|
58
|
+
async models(force = false) {
|
|
59
|
+
if (this.endpoints && !force)
|
|
60
|
+
return this.endpoints;
|
|
61
|
+
const out = await serving.listServingEndpointsUncached(this.client);
|
|
62
|
+
this.endpoints = out;
|
|
63
|
+
logger.debug("listed endpoints", { count: out.length });
|
|
64
|
+
return out;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Snap a (possibly loose) OpenAI-style model name to the best real serving
|
|
68
|
+
* endpoint. {@link resolve.rankModelIdLive} owns the policy: rank against the
|
|
69
|
+
* loaded catalogue, and on a miss re-list once and retry so a freshly
|
|
70
|
+
* deployed model resolves without a restart. An unmatched name comes back
|
|
71
|
+
* unchanged so a deliberate endpoint id is never silently rewritten.
|
|
72
|
+
*/
|
|
73
|
+
async resolve(model) {
|
|
74
|
+
return resolve.rankModelIdLive((force) => this.models(force), model, this.threshold !== undefined ? { threshold: this.threshold } : {});
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Mint auth headers for one upstream request. The SDK refreshes the
|
|
78
|
+
* underlying token when needed, so every call gets a valid `Authorization`
|
|
79
|
+
* header without the proxy tracking expiry.
|
|
80
|
+
*/
|
|
81
|
+
async authHeaders() {
|
|
82
|
+
return invoke.authHeaders(this.client);
|
|
83
|
+
}
|
|
84
|
+
/** OpenAI-compatible chat-completions invocations URL for a resolved endpoint id. */
|
|
85
|
+
invocationsUrl(endpoint) {
|
|
86
|
+
return invoke.invocationsUrl(this.host, endpoint);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Databricks Responses upstream for a resolved endpoint id
|
|
90
|
+
* (`/serving-endpoints/responses` for OpenAI-family, `/open-responses` else).
|
|
91
|
+
*/
|
|
92
|
+
responsesUrl(endpoint) {
|
|
93
|
+
return invoke.responsesUpstreamUrl(this.host, endpoint);
|
|
94
|
+
}
|
|
95
|
+
/** True when the endpoint rejects Chat Completions and needs Responses. */
|
|
96
|
+
isResponsesOnly(endpoint) {
|
|
97
|
+
return invoke.isResponsesOnly(endpoint);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYmFja2VuZC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9iYWNrZW5kLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7Ozs7Ozs7Ozs7OztHQWlCRztBQUVILE9BQU8sRUFBRSxlQUFlLEVBQUUsTUFBTSw4QkFBOEIsQ0FBQztBQUMvRCxPQUFPLEVBQUUsTUFBTSxFQUFFLE9BQU8sRUFBRSxPQUFPLEVBQXNCLE1BQU0sa0JBQWtCLENBQUM7QUFDaEYsT0FBTyxFQUFFLEdBQUcsRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBRzdDLE1BQU0sTUFBTSxHQUFHLEdBQUcsQ0FBQyxNQUFNLENBQUMscUJBQXFCLENBQUMsQ0FBQztBQVlqRCxNQUFNLE9BQU8saUJBQWlCO0lBQ1gsTUFBTSxDQUFrQjtJQUN6QyxrRkFBa0Y7SUFDekUsSUFBSSxDQUFTO0lBQ0wsU0FBUyxDQUFxQjtJQUMvQyx5RUFBeUU7SUFDakUsU0FBUyxDQUF1QztJQUV4RCxZQUFvQixNQUF1QixFQUFFLElBQVksRUFBRSxTQUE2QjtRQUN0RixJQUFJLENBQUMsTUFBTSxHQUFHLE1BQU0sQ0FBQztRQUNyQixJQUFJLENBQUMsSUFBSSxHQUFHLElBQUksQ0FBQztRQUNqQixJQUFJLENBQUMsU0FBUyxHQUFHLFNBQVMsQ0FBQztJQUM3QixDQUFDO0lBRUQ7Ozs7T0FJRztJQUNILE1BQU0sQ0FBQyxLQUFLLENBQUMsTUFBTSxDQUFDLFVBQTBCLEVBQUU7UUFDOUMsTUFBTSxNQUFNLEdBQUcsSUFBSSxlQUFlLENBQUM7WUFDakMsR0FBRyxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLEVBQUUsSUFBSSxFQUFFLE9BQU8sQ0FBQyxJQUFJLEVBQUUsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDO1lBQy9DLEdBQUcsQ0FBQyxPQUFPLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxFQUFFLE9BQU8sRUFBRSxPQUFPLENBQUMsT0FBTyxFQUFFLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQztTQUN6RCxDQUFDLENBQUM7UUFDSCxNQUFNLElBQUksR0FBRyxDQUFDLE1BQU0sTUFBTSxDQUFDLE1BQU0sQ0FBQyxPQUFPLEVBQUUsQ0FBQyxDQUFDLFFBQVEsRUFBRSxDQUFDO1FBQ3hELE1BQU0sQ0FBQyxJQUFJLENBQUMsV0FBVyxFQUFFLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQztRQUNuQyxPQUFPLElBQUksaUJBQWlCLENBQUMsTUFBTSxFQUFFLElBQUksRUFBRSxPQUFPLENBQUMsU0FBUyxDQUFDLENBQUM7SUFDaEUsQ0FBQztJQUVEOzs7Ozs7OztPQVFHO0lBQ0gsS0FBSyxDQUFDLE1BQU0sQ0FBQyxLQUFLLEdBQUcsS0FBSztRQUN4QixJQUFJLElBQUksQ0FBQyxTQUFTLElBQUksQ0FBQyxLQUFLO1lBQUUsT0FBTyxJQUFJLENBQUMsU0FBUyxDQUFDO1FBQ3BELE1BQU0sR0FBRyxHQUFHLE1BQU0sT0FBTyxDQUFDLDRCQUE0QixDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUNwRSxJQUFJLENBQUMsU0FBUyxHQUFHLEdBQUcsQ0FBQztRQUNyQixNQUFNLENBQUMsS0FBSyxDQUFDLGtCQUFrQixFQUFFLEVBQUUsS0FBSyxFQUFFLEdBQUcsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxDQUFDO1FBQ3hELE9BQU8sR0FBRyxDQUFDO0lBQ2IsQ0FBQztJQUVEOzs7Ozs7T0FNRztJQUNILEtBQUssQ0FBQyxPQUFPLENBQUMsS0FBYTtRQUN6QixPQUFPLE9BQU8sQ0FBQyxlQUFlLENBQzVCLENBQUMsS0FBSyxFQUFFLEVBQUUsQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxFQUM3QixLQUFLLEVBQ0wsSUFBSSxDQUFDLFNBQVMsS0FBSyxTQUFTLENBQUMsQ0FBQyxDQUFDLEVBQUUsU0FBUyxFQUFFLElBQUksQ0FBQyxTQUFTLEVBQUUsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUNsRSxDQUFDO0lBQ0osQ0FBQztJQUVEOzs7O09BSUc7SUFDSCxLQUFLLENBQUMsV0FBVztRQUNmLE9BQU8sTUFBTSxDQUFDLFdBQVcsQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLENBQUM7SUFDekMsQ0FBQztJQUVELHFGQUFxRjtJQUNyRixjQUFjLENBQUMsUUFBZ0I7UUFDN0IsT0FBTyxNQUFNLENBQUMsY0FBYyxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsUUFBUSxDQUFDLENBQUM7SUFDcEQsQ0FBQztJQUVEOzs7T0FHRztJQUNILFlBQVksQ0FBQyxRQUFnQjtRQUMzQixPQUFPLE1BQU0sQ0FBQyxvQkFBb0IsQ0FBQyxJQUFJLENBQUMsSUFBSSxFQUFFLFFBQVEsQ0FBQyxDQUFDO0lBQzFELENBQUM7SUFFRCwyRUFBMkU7SUFDM0UsZUFBZSxDQUFDLFFBQWdCO1FBQzlCLE9BQU8sTUFBTSxDQUFDLGVBQWUsQ0FBQyxRQUFRLENBQUMsQ0FBQztJQUMxQyxDQUFDO0NBQ0YiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIERhdGFicmlja3MgYmFja2VuZCBmb3IgdGhlIGxvY2FsIG1vZGVsIHByb3h5LlxuICpcbiAqIFdyYXBzIGEgc2luZ2xlIGRlZmF1bHQtYXV0aCB7QGxpbmsgV29ya3NwYWNlQ2xpZW50fSBhbmQgZXhwb3NlcyB0aGUgdGhyZWVcbiAqIHRoaW5ncyB0aGUgcHJveHkgc2VydmVyIG5lZWRzLCBlYWNoIGRlbGVnYXRpbmcgdG8gYEBkYngtdG9vbHMvbW9kZWxgOiB0aGVcbiAqIHdvcmtzcGFjZSBzZXJ2aW5nLWVuZHBvaW50IGxpc3QsIGZ1enp5IG5hbWUgcmVzb2x1dGlvbiAoe0BsaW5rXG4gKiByZXNvbHZlLnJhbmtNb2RlbElkTGl2ZX0sIHNvIGEgbG9vc2UgYFwib3B1c1wiYCAvIGBcImNsYXVkZSBzb25uZXRcImAgc25hcHMgdG9cbiAqIHRoZSBiZXN0IGxpdmUgZW5kcG9pbnQgaWQgLSBtYXRjaCBzY29yZSwgdGhlbiBjbGFzcywgdGhlbiB3aXRoaW4tY2xhc3NcbiAqIHZlcnNpb24pLCBhbmQgYSBmcmVzaCBzZXQgb2YgYXV0aCBoZWFkZXJzIHBlciB1cHN0cmVhbSByZXF1ZXN0ICh7QGxpbmtcbiAqIGludm9rZS5hdXRoSGVhZGVyc30pLlxuICpcbiAqIFdoYXQgaXMgbGVmdCBoZXJlIGlzIG9ubHkgdGhlIHByb3h5J3Mgb3duIHBvbGljeTogd2hpY2ggY2xpZW50IHRvIGNvbnN0cnVjdCxcbiAqIGFuZCBob2xkaW5nIHRoZSBlbmRwb2ludCBjYXRhbG9ndWUgZm9yIHRoZSBwcm9jZXNzIGxpZmV0aW1lLiBUaGUgY2F0YWxvZ3VlIGlzXG4gKiBsaXN0ZWQgb25jZSBhbmQgcmUtbGlzdGVkIG9uIGEgcmVzb2x2ZSBtaXNzLCBzbyBhIG1vZGVsIGRlcGxveWVkIGFmdGVyXG4gKiBzdGFydC11cCBzdGlsbCByZXNvbHZlcyBvbiBmaXJzdCB1c2UgLSBubyBjYWNoZSBsYXllciwganVzdCBvbmUgbGF6eSBsb2FkLlxuICpcbiAqIEBtb2R1bGVcbiAqL1xuXG5pbXBvcnQgeyBXb3Jrc3BhY2VDbGllbnQgfSBmcm9tIFwiQGRhdGFicmlja3Mvc2RrLWV4cGVyaW1lbnRhbFwiO1xuaW1wb3J0IHsgaW52b2tlLCByZXNvbHZlLCBzZXJ2aW5nLCB0eXBlIFJlc29sdmVkTW9kZWwgfSBmcm9tIFwiQGRieC10b29scy9tb2RlbFwiO1xuaW1wb3J0IHsgbG9nIH0gZnJvbSBcIkBkYngtdG9vbHMvc2hhcmVkLWNvcmVcIjtcbmltcG9ydCB7IHR5cGUgU2VydmluZ0VuZHBvaW50U3VtbWFyeSB9IGZyb20gXCJAZGJ4LXRvb2xzL3NoYXJlZC1tb2RlbFwiO1xuXG5jb25zdCBsb2dnZXIgPSBsb2cubG9nZ2VyKFwibW9kZWwtcHJveHkvYmFja2VuZFwiKTtcblxuLyoqIE9wdGlvbnMgZm9yIHtAbGluayBEYXRhYnJpY2tzQmFja2VuZC5jcmVhdGV9LiAqL1xuZXhwb3J0IGludGVyZmFjZSBCYWNrZW5kT3B0aW9ucyB7XG4gIC8qKiBEYXRhYnJpY2tzIGNvbmZpZyBwcm9maWxlIChgfi8uZGF0YWJyaWNrc2NmZ2ApLiBEZWZhdWx0cyB0byBTREsgYXV0aCByZXNvbHV0aW9uLiAqL1xuICBwcm9maWxlPzogc3RyaW5nO1xuICAvKiogT3ZlcnJpZGUgdGhlIHdvcmtzcGFjZSBob3N0OyBvdGhlcndpc2UgcmVzb2x2ZWQgZnJvbSBTREsgYXV0aCAoZW52IC8gcHJvZmlsZSkuICovXG4gIGhvc3Q/OiBzdHJpbmc7XG4gIC8qKiBGdXNlLmpzIGZ1enp5IHRocmVzaG9sZCAoMCA9IGV4YWN0LCAxID0gYW55dGhpbmcpLiBEZWZhdWx0cyB0byB0aGUgbW9kZWwgcGFja2FnZSBkZWZhdWx0LiAqL1xuICB0aHJlc2hvbGQ/OiBudW1iZXI7XG59XG5cbmV4cG9ydCBjbGFzcyBEYXRhYnJpY2tzQmFja2VuZCB7XG4gIHByaXZhdGUgcmVhZG9ubHkgY2xpZW50OiBXb3Jrc3BhY2VDbGllbnQ7XG4gIC8qKiBSZXNvbHZlZCB3b3Jrc3BhY2UgaG9zdCwgZS5nLiBgaHR0cHM6Ly9teS13b3Jrc3BhY2UuY2xvdWQuZGF0YWJyaWNrcy5jb20vYC4gKi9cbiAgcmVhZG9ubHkgaG9zdDogc3RyaW5nO1xuICBwcml2YXRlIHJlYWRvbmx5IHRocmVzaG9sZDogbnVtYmVyIHwgdW5kZWZpbmVkO1xuICAvKiogTGF6aWx5IGxvYWRlZCBlbmRwb2ludCBjYXRhbG9ndWUsIHJldXNlZCBmb3IgdGhlIHByb2Nlc3MgbGlmZXRpbWUuICovXG4gIHByaXZhdGUgZW5kcG9pbnRzOiBTZXJ2aW5nRW5kcG9pbnRTdW1tYXJ5W10gfCB1bmRlZmluZWQ7XG5cbiAgcHJpdmF0ZSBjb25zdHJ1Y3RvcihjbGllbnQ6IFdvcmtzcGFjZUNsaWVudCwgaG9zdDogc3RyaW5nLCB0aHJlc2hvbGQ6IG51bWJlciB8IHVuZGVmaW5lZCkge1xuICAgIHRoaXMuY2xpZW50ID0gY2xpZW50O1xuICAgIHRoaXMuaG9zdCA9IGhvc3Q7XG4gICAgdGhpcy50aHJlc2hvbGQgPSB0aHJlc2hvbGQ7XG4gIH1cblxuICAvKipcbiAgICogQnVpbGQgYSBiYWNrZW5kOiBjb25zdHJ1Y3QgYSBkZWZhdWx0LWF1dGggd29ya3NwYWNlIGNsaWVudCAob3B0aW9uYWxseVxuICAgKiBwaW5uZWQgdG8gYSBwcm9maWxlIC8gaG9zdCkgYW5kIHJlc29sdmUgdGhlIHdvcmtzcGFjZSBob3N0IG9uY2UsIHNvIGEgYmFkXG4gICAqIHByb2ZpbGUgZmFpbHMgYXQgc3RhcnQtdXAgcmF0aGVyIHRoYW4gb24gdGhlIGZpcnN0IHByb3hpZWQgcmVxdWVzdC5cbiAgICovXG4gIHN0YXRpYyBhc3luYyBjcmVhdGUob3B0aW9uczogQmFja2VuZE9wdGlvbnMgPSB7fSk6IFByb21pc2U8RGF0YWJyaWNrc0JhY2tlbmQ+IHtcbiAgICBjb25zdCBjbGllbnQgPSBuZXcgV29ya3NwYWNlQ2xpZW50KHtcbiAgICAgIC4uLihvcHRpb25zLmhvc3QgPyB7IGhvc3Q6IG9wdGlvbnMuaG9zdCB9IDoge30pLFxuICAgICAgLi4uKG9wdGlvbnMucHJvZmlsZSA/IHsgcHJvZmlsZTogb3B0aW9ucy5wcm9maWxlIH0gOiB7fSksXG4gICAgfSk7XG4gICAgY29uc3QgaG9zdCA9IChhd2FpdCBjbGllbnQuY29uZmlnLmdldEhvc3QoKSkudG9TdHJpbmcoKTtcbiAgICBsb2dnZXIuaW5mbyhcImNvbm5lY3RlZFwiLCB7IGhvc3QgfSk7XG4gICAgcmV0dXJuIG5ldyBEYXRhYnJpY2tzQmFja2VuZChjbGllbnQsIGhvc3QsIG9wdGlvbnMudGhyZXNob2xkKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBUaGUgd29ya3NwYWNlJ3Mgc2VydmluZy1lbmRwb2ludCBjYXRhbG9ndWUsIGFzIHRoZSBtaW5pbWFsXG4gICAqIHtAbGluayBTZXJ2aW5nRW5kcG9pbnRTdW1tYXJ5fSB0aGUgcmVzb2x2ZXIgbmVlZHMuIExvYWRlZCBsYXppbHkgYW5kXG4gICAqIHJldXNlZDsgcGFzcyBgZm9yY2VgIHRvIHJlLWxpc3QgKHVzZWQgYnkgYC92MS9tb2RlbHNgIGFuZCB0aGVcbiAgICogcmVzb2x2ZS1vbi1taXNzIHBhdGgpLlxuICAgKlxuICAgKiBEZWxpYmVyYXRlbHkgdGhlIHVuY2FjaGVkIGxpc3Rpbmc6IHRoZSBjYWNoZWQgb25lIHJ1bnMgdGhyb3VnaCBBcHBLaXQnc1xuICAgKiBgQ2FjaGVNYW5hZ2VyYCwgd2hpY2ggYSBwbGFpbiBDTEkgaGFzIG5vIGFwcCB0byBpbml0aWFsaXplLlxuICAgKi9cbiAgYXN5bmMgbW9kZWxzKGZvcmNlID0gZmFsc2UpOiBQcm9taXNlPFNlcnZpbmdFbmRwb2ludFN1bW1hcnlbXT4ge1xuICAgIGlmICh0aGlzLmVuZHBvaW50cyAmJiAhZm9yY2UpIHJldHVybiB0aGlzLmVuZHBvaW50cztcbiAgICBjb25zdCBvdXQgPSBhd2FpdCBzZXJ2aW5nLmxpc3RTZXJ2aW5nRW5kcG9pbnRzVW5jYWNoZWQodGhpcy5jbGllbnQpO1xuICAgIHRoaXMuZW5kcG9pbnRzID0gb3V0O1xuICAgIGxvZ2dlci5kZWJ1ZyhcImxpc3RlZCBlbmRwb2ludHNcIiwgeyBjb3VudDogb3V0Lmxlbmd0aCB9KTtcbiAgICByZXR1cm4gb3V0O1xuICB9XG5cbiAgLyoqXG4gICAqIFNuYXAgYSAocG9zc2libHkgbG9vc2UpIE9wZW5BSS1zdHlsZSBtb2RlbCBuYW1lIHRvIHRoZSBiZXN0IHJlYWwgc2VydmluZ1xuICAgKiBlbmRwb2ludC4ge0BsaW5rIHJlc29sdmUucmFua01vZGVsSWRMaXZlfSBvd25zIHRoZSBwb2xpY3k6IHJhbmsgYWdhaW5zdCB0aGVcbiAgICogbG9hZGVkIGNhdGFsb2d1ZSwgYW5kIG9uIGEgbWlzcyByZS1saXN0IG9uY2UgYW5kIHJldHJ5IHNvIGEgZnJlc2hseVxuICAgKiBkZXBsb3llZCBtb2RlbCByZXNvbHZlcyB3aXRob3V0IGEgcmVzdGFydC4gQW4gdW5tYXRjaGVkIG5hbWUgY29tZXMgYmFja1xuICAgKiB1bmNoYW5nZWQgc28gYSBkZWxpYmVyYXRlIGVuZHBvaW50IGlkIGlzIG5ldmVyIHNpbGVudGx5IHJld3JpdHRlbi5cbiAgICovXG4gIGFzeW5jIHJlc29sdmUobW9kZWw6IHN0cmluZyk6IFByb21pc2U8UmVzb2x2ZWRNb2RlbD4ge1xuICAgIHJldHVybiByZXNvbHZlLnJhbmtNb2RlbElkTGl2ZShcbiAgICAgIChmb3JjZSkgPT4gdGhpcy5tb2RlbHMoZm9yY2UpLFxuICAgICAgbW9kZWwsXG4gICAgICB0aGlzLnRocmVzaG9sZCAhPT0gdW5kZWZpbmVkID8geyB0aHJlc2hvbGQ6IHRoaXMudGhyZXNob2xkIH0gOiB7fSxcbiAgICApO1xuICB9XG5cbiAgLyoqXG4gICAqIE1pbnQgYXV0aCBoZWFkZXJzIGZvciBvbmUgdXBzdHJlYW0gcmVxdWVzdC4gVGhlIFNESyByZWZyZXNoZXMgdGhlXG4gICAqIHVuZGVybHlpbmcgdG9rZW4gd2hlbiBuZWVkZWQsIHNvIGV2ZXJ5IGNhbGwgZ2V0cyBhIHZhbGlkIGBBdXRob3JpemF0aW9uYFxuICAgKiBoZWFkZXIgd2l0aG91dCB0aGUgcHJveHkgdHJhY2tpbmcgZXhwaXJ5LlxuICAgKi9cbiAgYXN5bmMgYXV0aEhlYWRlcnMoKTogUHJvbWlzZTxSZWNvcmQ8c3RyaW5nLCBzdHJpbmc+PiB7XG4gICAgcmV0dXJuIGludm9rZS5hdXRoSGVhZGVycyh0aGlzLmNsaWVudCk7XG4gIH1cblxuICAvKiogT3BlbkFJLWNvbXBhdGlibGUgY2hhdC1jb21wbGV0aW9ucyBpbnZvY2F0aW9ucyBVUkwgZm9yIGEgcmVzb2x2ZWQgZW5kcG9pbnQgaWQuICovXG4gIGludm9jYXRpb25zVXJsKGVuZHBvaW50OiBzdHJpbmcpOiBzdHJpbmcge1xuICAgIHJldHVybiBpbnZva2UuaW52b2NhdGlvbnNVcmwodGhpcy5ob3N0LCBlbmRwb2ludCk7XG4gIH1cblxuICAvKipcbiAgICogRGF0YWJyaWNrcyBSZXNwb25zZXMgdXBzdHJlYW0gZm9yIGEgcmVzb2x2ZWQgZW5kcG9pbnQgaWRcbiAgICogKGAvc2VydmluZy1lbmRwb2ludHMvcmVzcG9uc2VzYCBmb3IgT3BlbkFJLWZhbWlseSwgYC9vcGVuLXJlc3BvbnNlc2AgZWxzZSkuXG4gICAqL1xuICByZXNwb25zZXNVcmwoZW5kcG9pbnQ6IHN0cmluZyk6IHN0cmluZyB7XG4gICAgcmV0dXJuIGludm9rZS5yZXNwb25zZXNVcHN0cmVhbVVybCh0aGlzLmhvc3QsIGVuZHBvaW50KTtcbiAgfVxuXG4gIC8qKiBUcnVlIHdoZW4gdGhlIGVuZHBvaW50IHJlamVjdHMgQ2hhdCBDb21wbGV0aW9ucyBhbmQgbmVlZHMgUmVzcG9uc2VzLiAqL1xuICBpc1Jlc3BvbnNlc09ubHkoZW5kcG9pbnQ6IHN0cmluZyk6IGJvb2xlYW4ge1xuICAgIHJldHVybiBpbnZva2UuaXNSZXNwb25zZXNPbmx5KGVuZHBvaW50KTtcbiAgfVxufVxuIl19
|
package/lib/src/cli.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `dbx-tools-model-proxy` CLI.
|
|
3
|
+
*
|
|
4
|
+
* Run bare, it serves: a loopback OpenAI-compatible endpoint that fronts
|
|
5
|
+
* Databricks Model Serving with fuzzy model names and per-request auth. `chat`
|
|
6
|
+
* starts that same proxy and hands off to an off-the-shelf terminal client
|
|
7
|
+
* wired to it. `models` lists the resolvable endpoints; `resolve` shows what a
|
|
8
|
+
* fuzzy name snaps to. Auth comes from the standard Databricks SDK resolution
|
|
9
|
+
* (env vars, `--profile`, or `databricks auth login`).
|
|
10
|
+
*
|
|
11
|
+
* Shared flags (`--profile`, `--workspace-host`, `--threshold`, …) are declared
|
|
12
|
+
* on the root *and* redeclared on subcommands for help discoverability. Commander
|
|
13
|
+
* parks the values on the parent when both declare the same flag, so every
|
|
14
|
+
* subcommand action must read {@link Command.optsWithGlobals} - local `opts`
|
|
15
|
+
* alone misses `--profile` (env `DATABRICKS_CONFIG_PROFILE` still worked because
|
|
16
|
+
* the SDK reads that without the CLI flag).
|
|
17
|
+
*
|
|
18
|
+
* @module
|
|
19
|
+
*/
|
|
20
|
+
import { Command, CommanderError } from "commander";
|
|
21
|
+
/** Build the `dbx-tools-model-proxy` commander program (no side effects until parsed). */
|
|
22
|
+
export declare function buildProgram(): Command;
|
|
23
|
+
/** Parse `argv` and run the matching command. Throws {@link CommanderError} on flag errors. */
|
|
24
|
+
export declare function runCli(argv: string[]): Promise<void>;
|
|
25
|
+
export { CommanderError };
|
package/lib/src/cli.js
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `dbx-tools-model-proxy` CLI.
|
|
3
|
+
*
|
|
4
|
+
* Run bare, it serves: a loopback OpenAI-compatible endpoint that fronts
|
|
5
|
+
* Databricks Model Serving with fuzzy model names and per-request auth. `chat`
|
|
6
|
+
* starts that same proxy and hands off to an off-the-shelf terminal client
|
|
7
|
+
* wired to it. `models` lists the resolvable endpoints; `resolve` shows what a
|
|
8
|
+
* fuzzy name snaps to. Auth comes from the standard Databricks SDK resolution
|
|
9
|
+
* (env vars, `--profile`, or `databricks auth login`).
|
|
10
|
+
*
|
|
11
|
+
* Shared flags (`--profile`, `--workspace-host`, `--threshold`, …) are declared
|
|
12
|
+
* on the root *and* redeclared on subcommands for help discoverability. Commander
|
|
13
|
+
* parks the values on the parent when both declare the same flag, so every
|
|
14
|
+
* subcommand action must read {@link Command.optsWithGlobals} - local `opts`
|
|
15
|
+
* alone misses `--profile` (env `DATABRICKS_CONFIG_PROFILE` still worked because
|
|
16
|
+
* the SDK reads that without the CLI flag).
|
|
17
|
+
*
|
|
18
|
+
* @module
|
|
19
|
+
*/
|
|
20
|
+
import { spawn } from "node:child_process";
|
|
21
|
+
import { classify } from "@dbx-tools/shared-model";
|
|
22
|
+
import { Command, CommanderError } from "commander";
|
|
23
|
+
import { DatabricksBackend } from "./backend.js";
|
|
24
|
+
import { DEFAULT_BIND_HOST, DEFAULT_PORT, resolveRetryConfig } from "./defaults.js";
|
|
25
|
+
import { startProxyServer } from "./server.js";
|
|
26
|
+
/**
|
|
27
|
+
* Default terminal client for `chat`, launched via `bunx`. OpenHarness is an
|
|
28
|
+
* OpenAI-compatible TUI that reads the endpoint straight from the env we inject
|
|
29
|
+
* (`LLM_PROVIDER=openai-compat` + `OPENAI_BASE_URL`), so no config step is
|
|
30
|
+
* needed. Override with `--client` / `PROXY_CHAT_CLIENT`.
|
|
31
|
+
*/
|
|
32
|
+
const DEFAULT_CHAT_CLIENT = "bunx @alhazmiai/openharness";
|
|
33
|
+
/** Map shared CLI flags onto {@link BackendOptions}. */
|
|
34
|
+
function backendOptions(opts) {
|
|
35
|
+
return {
|
|
36
|
+
...(opts.profile ? { profile: opts.profile } : {}),
|
|
37
|
+
...(opts.workspaceHost ? { host: opts.workspaceHost } : {}),
|
|
38
|
+
...(opts.threshold !== undefined ? { threshold: Number(opts.threshold) } : {}),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Subcommand options merged with parent (root) options. Required for shared
|
|
43
|
+
* flags that are declared on both - Commander stores them on the parent, so
|
|
44
|
+
* the action's local `opts` alone omits `--profile` / `--workspace-host`.
|
|
45
|
+
*/
|
|
46
|
+
function globalOpts(command) {
|
|
47
|
+
return command.optsWithGlobals();
|
|
48
|
+
}
|
|
49
|
+
/** Create the backend and start the proxy from the shared listen flags. */
|
|
50
|
+
async function startProxy(opts) {
|
|
51
|
+
const backend = await DatabricksBackend.create(backendOptions(opts));
|
|
52
|
+
const apiKey = opts.apiKey ?? process.env.PROXY_API_KEY;
|
|
53
|
+
// `--no-retry-429` (opts.retry429 === false) is the only explicit override;
|
|
54
|
+
// otherwise resolveRetryConfig layers PROXY_RETRY_* env then the on-default.
|
|
55
|
+
const retry = resolveRetryConfig(opts.retry429 === false ? { enabled: false } : {});
|
|
56
|
+
const { server, url } = await startProxyServer(backend, {
|
|
57
|
+
host: opts.host,
|
|
58
|
+
port: Number(opts.port),
|
|
59
|
+
retry,
|
|
60
|
+
...(apiKey ? { apiKey } : {}),
|
|
61
|
+
});
|
|
62
|
+
return { backend, server, url };
|
|
63
|
+
}
|
|
64
|
+
/** Shared auth / fuzzy-match flags added to the root and every subcommand. */
|
|
65
|
+
function addAuthOptions(command) {
|
|
66
|
+
return command
|
|
67
|
+
.option("--profile <profile>", "Databricks config profile")
|
|
68
|
+
.option("--workspace-host <url>", "override the Databricks workspace host")
|
|
69
|
+
.option("-t, --threshold <n>", "fuzzy match threshold (0..1)");
|
|
70
|
+
}
|
|
71
|
+
/** Build the `dbx-tools-model-proxy` commander program (no side effects until parsed). */
|
|
72
|
+
export function buildProgram() {
|
|
73
|
+
// Serving is the root action, not a subcommand: the bare command runs the
|
|
74
|
+
// proxy, and `chat`/`models`/`resolve` are the named detours off it.
|
|
75
|
+
const program = addAuthOptions(new Command()
|
|
76
|
+
.name("dbx-tools-model-proxy")
|
|
77
|
+
.description("Local OpenAI-compatible proxy to Databricks Model Serving.")
|
|
78
|
+
.option("-p, --port <port>", "port to listen on", String(DEFAULT_PORT))
|
|
79
|
+
.option("-H, --host <host>", "address to bind", DEFAULT_BIND_HOST)
|
|
80
|
+
.option("-k, --api-key <key>", "require this bearer token from local clients")
|
|
81
|
+
.option("--no-retry-429", "relay upstream 429s instead of retrying with backoff (default: retry)")).action(async (opts) => {
|
|
82
|
+
const { backend, url } = await startProxy(opts);
|
|
83
|
+
process.stderr.write(`model-proxy -> ${backend.host}\n`);
|
|
84
|
+
process.stderr.write(` OpenAI base URL: ${url}/v1\n`);
|
|
85
|
+
});
|
|
86
|
+
addAuthOptions(program
|
|
87
|
+
.command("chat")
|
|
88
|
+
.description("Start the proxy and launch a terminal chat client wired to it.")
|
|
89
|
+
.option("-p, --port <port>", "proxy port", String(DEFAULT_PORT))
|
|
90
|
+
.option("-H, --host <host>", "proxy bind host", DEFAULT_BIND_HOST)
|
|
91
|
+
.option("-m, --model <name>", "default model (fuzzy name ok)")
|
|
92
|
+
.option("--no-retry-429", "relay upstream 429s instead of retrying with backoff (default: retry)")
|
|
93
|
+
.option("--client <cmd>", "terminal chat CLI to launch (run via your shell)", process.env.PROXY_CHAT_CLIENT ?? DEFAULT_CHAT_CLIENT)).action(async (_local, command) => {
|
|
94
|
+
const opts = globalOpts(command);
|
|
95
|
+
const { backend, server, url } = await startProxy(opts);
|
|
96
|
+
const baseUrl = `${url}/v1`;
|
|
97
|
+
process.stderr.write(`model-proxy -> ${backend.host}\n OpenAI base URL: ${baseUrl}\n launching: ${opts.client}\n`);
|
|
98
|
+
// Hand off to an off-the-shelf OpenAI-compatible client, pointing it at
|
|
99
|
+
// the proxy via the standard env vars (plus the provider switches a
|
|
100
|
+
// couple of popular CLIs read). `shell: true` lets `--client` carry its
|
|
101
|
+
// own args, e.g. `--client "bunx merlion"`.
|
|
102
|
+
const child = spawn(opts.client, {
|
|
103
|
+
stdio: "inherit",
|
|
104
|
+
shell: true,
|
|
105
|
+
env: {
|
|
106
|
+
...process.env,
|
|
107
|
+
OPENAI_BASE_URL: baseUrl,
|
|
108
|
+
OPENAI_API_KEY: process.env.OPENAI_API_KEY ?? "model-proxy",
|
|
109
|
+
...(opts.model ? { OPENAI_MODEL: opts.model } : {}),
|
|
110
|
+
LLM_PROVIDER: "openai-compat",
|
|
111
|
+
CLAUDE_CODE_USE_OPENAI: "1",
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
child.on("exit", (code) => {
|
|
115
|
+
server.close();
|
|
116
|
+
process.exit(code ?? 0);
|
|
117
|
+
});
|
|
118
|
+
process.on("SIGINT", () => child.kill("SIGINT"));
|
|
119
|
+
});
|
|
120
|
+
addAuthOptions(program
|
|
121
|
+
.command("models")
|
|
122
|
+
.description("List resolvable Databricks serving endpoints (as JSON).")
|
|
123
|
+
.option("--chat", "only list chat-capable endpoints")).action(async (_local, command) => {
|
|
124
|
+
const opts = globalOpts(command);
|
|
125
|
+
const backend = await DatabricksBackend.create(backendOptions(opts));
|
|
126
|
+
const endpoints = await backend.models();
|
|
127
|
+
const enriched = endpoints.map(enrichEndpoint);
|
|
128
|
+
const out = opts.chat ? enriched.filter((e) => e.capabilities.chat) : enriched;
|
|
129
|
+
process.stdout.write(`${JSON.stringify(out, null, 2)}\n`);
|
|
130
|
+
});
|
|
131
|
+
addAuthOptions(program
|
|
132
|
+
.command("resolve")
|
|
133
|
+
.description("Show what a fuzzy model name resolves to (as JSON).")
|
|
134
|
+
.argument("<query...>", "model name / fuzzy search terms")).action(async (query, _local, command) => {
|
|
135
|
+
const opts = globalOpts(command);
|
|
136
|
+
const backend = await DatabricksBackend.create(backendOptions(opts));
|
|
137
|
+
const search = query.join(" ");
|
|
138
|
+
const resolved = await backend.resolve(search);
|
|
139
|
+
// Same shape as one `models` entry when matched; null when nothing in the
|
|
140
|
+
// catalogue scores within the threshold (caller can fall back to the query).
|
|
141
|
+
if (!resolved.matched) {
|
|
142
|
+
process.stdout.write("null\n");
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
const endpoint = (await backend.models()).find((e) => e.name === resolved.modelId);
|
|
146
|
+
process.stdout.write(`${JSON.stringify(endpoint ? enrichEndpoint(endpoint) : null, null, 2)}\n`);
|
|
147
|
+
});
|
|
148
|
+
return program;
|
|
149
|
+
}
|
|
150
|
+
/** One `models` / `resolve` list entry: endpoint summary + derived capabilities. */
|
|
151
|
+
function enrichEndpoint(endpoint) {
|
|
152
|
+
return {
|
|
153
|
+
...endpoint,
|
|
154
|
+
// `chat` = OpenAI chat/completions + Responses; `embedding` = vectors;
|
|
155
|
+
// `tools` = function/tool calls (every chat endpoint here).
|
|
156
|
+
capabilities: classify.endpointCapabilities(endpoint),
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
/** Parse `argv` and run the matching command. Throws {@link CommanderError} on flag errors. */
|
|
160
|
+
export async function runCli(argv) {
|
|
161
|
+
await buildProgram().parseAsync(argv);
|
|
162
|
+
}
|
|
163
|
+
export { CommanderError };
|
|
164
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xpLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2NsaS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBa0JHO0FBRUgsT0FBTyxFQUFFLEtBQUssRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBRTNDLE9BQU8sRUFBRSxRQUFRLEVBQStCLE1BQU0seUJBQXlCLENBQUM7QUFDaEYsT0FBTyxFQUFFLE9BQU8sRUFBRSxjQUFjLEVBQUUsTUFBTSxXQUFXLENBQUM7QUFFcEQsT0FBTyxFQUFFLGlCQUFpQixFQUF1QixNQUFNLFdBQVcsQ0FBQztBQUNuRSxPQUFPLEVBQUUsaUJBQWlCLEVBQUUsWUFBWSxFQUFFLGtCQUFrQixFQUFFLE1BQU0sWUFBWSxDQUFDO0FBQ2pGLE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLFVBQVUsQ0FBQztBQUU1Qzs7Ozs7R0FLRztBQUNILE1BQU0sbUJBQW1CLEdBQUcsNkJBQTZCLENBQUM7QUFxQjFELHdEQUF3RDtBQUN4RCxTQUFTLGNBQWMsQ0FBQyxJQUFnQjtJQUN0QyxPQUFPO1FBQ0wsR0FBRyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLEVBQUUsT0FBTyxFQUFFLElBQUksQ0FBQyxPQUFPLEVBQUUsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDO1FBQ2xELEdBQUcsQ0FBQyxJQUFJLENBQUMsYUFBYSxDQUFDLENBQUMsQ0FBQyxFQUFFLElBQUksRUFBRSxJQUFJLENBQUMsYUFBYSxFQUFFLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQztRQUMzRCxHQUFHLENBQUMsSUFBSSxDQUFDLFNBQVMsS0FBSyxTQUFTLENBQUMsQ0FBQyxDQUFDLEVBQUUsU0FBUyxFQUFFLE1BQU0sQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDO0tBQy9FLENBQUM7QUFDSixDQUFDO0FBRUQ7Ozs7R0FJRztBQUNILFNBQVMsVUFBVSxDQUFJLE9BQWdCO0lBQ3JDLE9BQU8sT0FBTyxDQUFDLGVBQWUsRUFBTyxDQUFDO0FBQ3hDLENBQUM7QUFFRCwyRUFBMkU7QUFDM0UsS0FBSyxVQUFVLFVBQVUsQ0FDdkIsSUFBZTtJQUVmLE1BQU0sT0FBTyxHQUFHLE1BQU0saUJBQWlCLENBQUMsTUFBTSxDQUFDLGNBQWMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDO0lBQ3JFLE1BQU0sTUFBTSxHQUFHLElBQUksQ0FBQyxNQUFNLElBQUksT0FBTyxDQUFDLEdBQUcsQ0FBQyxhQUFhLENBQUM7SUFDeEQsNEVBQTRFO0lBQzVFLDZFQUE2RTtJQUM3RSxNQUFNLEtBQUssR0FBRyxrQkFBa0IsQ0FBQyxJQUFJLENBQUMsUUFBUSxLQUFLLEtBQUssQ0FBQyxDQUFDLENBQUMsRUFBRSxPQUFPLEVBQUUsS0FBSyxFQUFFLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDO0lBQ3BGLE1BQU0sRUFBRSxNQUFNLEVBQUUsR0FBRyxFQUFFLEdBQUcsTUFBTSxnQkFBZ0IsQ0FBQyxPQUFPLEVBQUU7UUFDdEQsSUFBSSxFQUFFLElBQUksQ0FBQyxJQUFJO1FBQ2YsSUFBSSxFQUFFLE1BQU0sQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDO1FBQ3ZCLEtBQUs7UUFDTCxHQUFHLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxFQUFFLE1BQU0sRUFBRSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUM7S0FDOUIsQ0FBQyxDQUFDO0lBQ0gsT0FBTyxFQUFFLE9BQU8sRUFBRSxNQUFNLEVBQUUsR0FBRyxFQUFFLENBQUM7QUFDbEMsQ0FBQztBQUVELDhFQUE4RTtBQUM5RSxTQUFTLGNBQWMsQ0FBQyxPQUFnQjtJQUN0QyxPQUFPLE9BQU87U0FDWCxNQUFNLENBQUMscUJBQXFCLEVBQUUsMkJBQTJCLENBQUM7U0FDMUQsTUFBTSxDQUFDLHdCQUF3QixFQUFFLHdDQUF3QyxDQUFDO1NBQzFFLE1BQU0sQ0FBQyxxQkFBcUIsRUFBRSw4QkFBOEIsQ0FBQyxDQUFDO0FBQ25FLENBQUM7QUFFRCwwRkFBMEY7QUFDMUYsTUFBTSxVQUFVLFlBQVk7SUFDMUIsMEVBQTBFO0lBQzFFLHFFQUFxRTtJQUNyRSxNQUFNLE9BQU8sR0FBRyxjQUFjLENBQzVCLElBQUksT0FBTyxFQUFFO1NBQ1YsSUFBSSxDQUFDLHVCQUF1QixDQUFDO1NBQzdCLFdBQVcsQ0FBQyw0REFBNEQsQ0FBQztTQUN6RSxNQUFNLENBQUMsbUJBQW1CLEVBQUUsbUJBQW1CLEVBQUUsTUFBTSxDQUFDLFlBQVksQ0FBQyxDQUFDO1NBQ3RFLE1BQU0sQ0FBQyxtQkFBbUIsRUFBRSxpQkFBaUIsRUFBRSxpQkFBaUIsQ0FBQztTQUNqRSxNQUFNLENBQUMscUJBQXFCLEVBQUUsOENBQThDLENBQUM7U0FDN0UsTUFBTSxDQUNMLGdCQUFnQixFQUNoQix1RUFBdUUsQ0FDeEUsQ0FDSixDQUFDLE1BQU0sQ0FBQyxLQUFLLEVBQUUsSUFBZSxFQUFFLEVBQUU7UUFDakMsTUFBTSxFQUFFLE9BQU8sRUFBRSxHQUFHLEVBQUUsR0FBRyxNQUFNLFVBQVUsQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUNoRCxPQUFPLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxrQkFBa0IsT0FBTyxDQUFDLElBQUksSUFBSSxDQUFDLENBQUM7UUFDekQsT0FBTyxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsc0JBQXNCLEdBQUcsT0FBTyxDQUFDLENBQUM7SUFDekQsQ0FBQyxDQUFDLENBQUM7SUFFSCxjQUFjLENBQ1osT0FBTztTQUNKLE9BQU8sQ0FBQyxNQUFNLENBQUM7U0FDZixXQUFXLENBQUMsZ0VBQWdFLENBQUM7U0FDN0UsTUFBTSxDQUFDLG1CQUFtQixFQUFFLFlBQVksRUFBRSxNQUFNLENBQUMsWUFBWSxDQUFDLENBQUM7U0FDL0QsTUFBTSxDQUFDLG1CQUFtQixFQUFFLGlCQUFpQixFQUFFLGlCQUFpQixDQUFDO1NBQ2pFLE1BQU0sQ0FBQyxvQkFBb0IsRUFBRSwrQkFBK0IsQ0FBQztTQUM3RCxNQUFNLENBQ0wsZ0JBQWdCLEVBQ2hCLHVFQUF1RSxDQUN4RTtTQUNBLE1BQU0sQ0FDTCxnQkFBZ0IsRUFDaEIsa0RBQWtELEVBQ2xELE9BQU8sQ0FBQyxHQUFHLENBQUMsaUJBQWlCLElBQUksbUJBQW1CLENBQ3JELENBQ0osQ0FBQyxNQUFNLENBQUMsS0FBSyxFQUFFLE1BQXNELEVBQUUsT0FBZ0IsRUFBRSxFQUFFO1FBQzFGLE1BQU0sSUFBSSxHQUFHLFVBQVUsQ0FBaUQsT0FBTyxDQUFDLENBQUM7UUFDakYsTUFBTSxFQUFFLE9BQU8sRUFBRSxNQUFNLEVBQUUsR0FBRyxFQUFFLEdBQUcsTUFBTSxVQUFVLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDeEQsTUFBTSxPQUFPLEdBQUcsR0FBRyxHQUFHLEtBQUssQ0FBQztRQUM1QixPQUFPLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FDbEIsa0JBQWtCLE9BQU8sQ0FBQyxJQUFJLHdCQUF3QixPQUFPLGtCQUFrQixJQUFJLENBQUMsTUFBTSxJQUFJLENBQy9GLENBQUM7UUFDRix3RUFBd0U7UUFDeEUsb0VBQW9FO1FBQ3BFLHdFQUF3RTtRQUN4RSw0Q0FBNEM7UUFDNUMsTUFBTSxLQUFLLEdBQUcsS0FBSyxDQUFDLElBQUksQ0FBQyxNQUFNLEVBQUU7WUFDL0IsS0FBSyxFQUFFLFNBQVM7WUFDaEIsS0FBSyxFQUFFLElBQUk7WUFDWCxHQUFHLEVBQUU7Z0JBQ0gsR0FBRyxPQUFPLENBQUMsR0FBRztnQkFDZCxlQUFlLEVBQUUsT0FBTztnQkFDeEIsY0FBYyxFQUFFLE9BQU8sQ0FBQyxHQUFHLENBQUMsY0FBYyxJQUFJLGFBQWE7Z0JBQzNELEdBQUcsQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxFQUFFLFlBQVksRUFBRSxJQUFJLENBQUMsS0FBSyxFQUFFLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQztnQkFDbkQsWUFBWSxFQUFFLGVBQWU7Z0JBQzdCLHNCQUFzQixFQUFFLEdBQUc7YUFDNUI7U0FDRixDQUFDLENBQUM7UUFDSCxLQUFLLENBQUMsRUFBRSxDQUFDLE1BQU0sRUFBRSxDQUFDLElBQUksRUFBRSxFQUFFO1lBQ3hCLE1BQU0sQ0FBQyxLQUFLLEVBQUUsQ0FBQztZQUNmLE9BQU8sQ0FBQyxJQUFJLENBQUMsSUFBSSxJQUFJLENBQUMsQ0FBQyxDQUFDO1FBQzFCLENBQUMsQ0FBQyxDQUFDO1FBQ0gsT0FBTyxDQUFDLEVBQUUsQ0FBQyxRQUFRLEVBQUUsR0FBRyxFQUFFLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsQ0FBQyxDQUFDO0lBQ25ELENBQUMsQ0FBQyxDQUFDO0lBRUgsY0FBYyxDQUNaLE9BQU87U0FDSixPQUFPLENBQUMsUUFBUSxDQUFDO1NBQ2pCLFdBQVcsQ0FBQyx5REFBeUQsQ0FBQztTQUN0RSxNQUFNLENBQUMsUUFBUSxFQUFFLGtDQUFrQyxDQUFDLENBQ3hELENBQUMsTUFBTSxDQUFDLEtBQUssRUFBRSxNQUF1QyxFQUFFLE9BQWdCLEVBQUUsRUFBRTtRQUMzRSxNQUFNLElBQUksR0FBRyxVQUFVLENBQWtDLE9BQU8sQ0FBQyxDQUFDO1FBQ2xFLE1BQU0sT0FBTyxHQUFHLE1BQU0saUJBQWlCLENBQUMsTUFBTSxDQUFDLGNBQWMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDO1FBQ3JFLE1BQU0sU0FBUyxHQUFHLE1BQU0sT0FBTyxDQUFDLE1BQU0sRUFBRSxDQUFDO1FBQ3pDLE1BQU0sUUFBUSxHQUFHLFNBQVMsQ0FBQyxHQUFHLENBQUMsY0FBYyxDQUFDLENBQUM7UUFDL0MsTUFBTSxHQUFHLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsUUFBUSxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsRUFBRSxFQUFFLENBQUMsQ0FBQyxDQUFDLFlBQVksQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUMsUUFBUSxDQUFDO1FBQy9FLE9BQU8sQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLEdBQUcsSUFBSSxDQUFDLFNBQVMsQ0FBQyxHQUFHLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQztJQUM1RCxDQUFDLENBQUMsQ0FBQztJQUVILGNBQWMsQ0FDWixPQUFPO1NBQ0osT0FBTyxDQUFDLFNBQVMsQ0FBQztTQUNsQixXQUFXLENBQUMscURBQXFELENBQUM7U0FDbEUsUUFBUSxDQUFDLFlBQVksRUFBRSxpQ0FBaUMsQ0FBQyxDQUM3RCxDQUFDLE1BQU0sQ0FBQyxLQUFLLEVBQUUsS0FBZSxFQUFFLE1BQWtCLEVBQUUsT0FBZ0IsRUFBRSxFQUFFO1FBQ3ZFLE1BQU0sSUFBSSxHQUFHLFVBQVUsQ0FBYSxPQUFPLENBQUMsQ0FBQztRQUM3QyxNQUFNLE9BQU8sR0FBRyxNQUFNLGlCQUFpQixDQUFDLE1BQU0sQ0FBQyxjQUFjLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQztRQUNyRSxNQUFNLE1BQU0sR0FBRyxLQUFLLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFDO1FBQy9CLE1BQU0sUUFBUSxHQUFHLE1BQU0sT0FBTyxDQUFDLE9BQU8sQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUMvQywwRUFBMEU7UUFDMUUsNkVBQTZFO1FBQzdFLElBQUksQ0FBQyxRQUFRLENBQUMsT0FBTyxFQUFFLENBQUM7WUFDdEIsT0FBTyxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsUUFBUSxDQUFDLENBQUM7WUFDL0IsT0FBTztRQUNULENBQUM7UUFDRCxNQUFNLFFBQVEsR0FBRyxDQUFDLE1BQU0sT0FBTyxDQUFDLE1BQU0sRUFBRSxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDLENBQUMsSUFBSSxLQUFLLFFBQVEsQ0FBQyxPQUFPLENBQUMsQ0FBQztRQUNuRixPQUFPLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FDbEIsR0FBRyxJQUFJLENBQUMsU0FBUyxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUMsY0FBYyxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQyxJQUFJLENBQzNFLENBQUM7SUFDSixDQUFDLENBQUMsQ0FBQztJQUVILE9BQU8sT0FBTyxDQUFDO0FBQ2pCLENBQUM7QUFFRCxvRkFBb0Y7QUFDcEYsU0FBUyxjQUFjLENBQUMsUUFBZ0M7SUFDdEQsT0FBTztRQUNMLEdBQUcsUUFBUTtRQUNYLHVFQUF1RTtRQUN2RSw0REFBNEQ7UUFDNUQsWUFBWSxFQUFFLFFBQVEsQ0FBQyxvQkFBb0IsQ0FBQyxRQUFRLENBQUM7S0FDdEQsQ0FBQztBQUNKLENBQUM7QUFFRCwrRkFBK0Y7QUFDL0YsTUFBTSxDQUFDLEtBQUssVUFBVSxNQUFNLENBQUMsSUFBYztJQUN6QyxNQUFNLFlBQVksRUFBRSxDQUFDLFVBQVUsQ0FBQyxJQUFJLENBQUMsQ0FBQztBQUN4QyxDQUFDO0FBRUQsT0FBTyxFQUFFLGNBQWMsRUFBRSxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBgZGJ4LXRvb2xzLW1vZGVsLXByb3h5YCBDTEkuXG4gKlxuICogUnVuIGJhcmUsIGl0IHNlcnZlczogYSBsb29wYmFjayBPcGVuQUktY29tcGF0aWJsZSBlbmRwb2ludCB0aGF0IGZyb250c1xuICogRGF0YWJyaWNrcyBNb2RlbCBTZXJ2aW5nIHdpdGggZnV6enkgbW9kZWwgbmFtZXMgYW5kIHBlci1yZXF1ZXN0IGF1dGguIGBjaGF0YFxuICogc3RhcnRzIHRoYXQgc2FtZSBwcm94eSBhbmQgaGFuZHMgb2ZmIHRvIGFuIG9mZi10aGUtc2hlbGYgdGVybWluYWwgY2xpZW50XG4gKiB3aXJlZCB0byBpdC4gYG1vZGVsc2AgbGlzdHMgdGhlIHJlc29sdmFibGUgZW5kcG9pbnRzOyBgcmVzb2x2ZWAgc2hvd3Mgd2hhdCBhXG4gKiBmdXp6eSBuYW1lIHNuYXBzIHRvLiBBdXRoIGNvbWVzIGZyb20gdGhlIHN0YW5kYXJkIERhdGFicmlja3MgU0RLIHJlc29sdXRpb25cbiAqIChlbnYgdmFycywgYC0tcHJvZmlsZWAsIG9yIGBkYXRhYnJpY2tzIGF1dGggbG9naW5gKS5cbiAqXG4gKiBTaGFyZWQgZmxhZ3MgKGAtLXByb2ZpbGVgLCBgLS13b3Jrc3BhY2UtaG9zdGAsIGAtLXRocmVzaG9sZGAsIOKApikgYXJlIGRlY2xhcmVkXG4gKiBvbiB0aGUgcm9vdCAqYW5kKiByZWRlY2xhcmVkIG9uIHN1YmNvbW1hbmRzIGZvciBoZWxwIGRpc2NvdmVyYWJpbGl0eS4gQ29tbWFuZGVyXG4gKiBwYXJrcyB0aGUgdmFsdWVzIG9uIHRoZSBwYXJlbnQgd2hlbiBib3RoIGRlY2xhcmUgdGhlIHNhbWUgZmxhZywgc28gZXZlcnlcbiAqIHN1YmNvbW1hbmQgYWN0aW9uIG11c3QgcmVhZCB7QGxpbmsgQ29tbWFuZC5vcHRzV2l0aEdsb2JhbHN9IC0gbG9jYWwgYG9wdHNgXG4gKiBhbG9uZSBtaXNzZXMgYC0tcHJvZmlsZWAgKGVudiBgREFUQUJSSUNLU19DT05GSUdfUFJPRklMRWAgc3RpbGwgd29ya2VkIGJlY2F1c2VcbiAqIHRoZSBTREsgcmVhZHMgdGhhdCB3aXRob3V0IHRoZSBDTEkgZmxhZykuXG4gKlxuICogQG1vZHVsZVxuICovXG5cbmltcG9ydCB7IHNwYXduIH0gZnJvbSBcIm5vZGU6Y2hpbGRfcHJvY2Vzc1wiO1xuaW1wb3J0IHR5cGUgeyBTZXJ2ZXIgfSBmcm9tIFwibm9kZTpodHRwXCI7XG5pbXBvcnQgeyBjbGFzc2lmeSwgdHlwZSBTZXJ2aW5nRW5kcG9pbnRTdW1tYXJ5IH0gZnJvbSBcIkBkYngtdG9vbHMvc2hhcmVkLW1vZGVsXCI7XG5pbXBvcnQgeyBDb21tYW5kLCBDb21tYW5kZXJFcnJvciB9IGZyb20gXCJjb21tYW5kZXJcIjtcblxuaW1wb3J0IHsgRGF0YWJyaWNrc0JhY2tlbmQsIHR5cGUgQmFja2VuZE9wdGlvbnMgfSBmcm9tIFwiLi9iYWNrZW5kXCI7XG5pbXBvcnQgeyBERUZBVUxUX0JJTkRfSE9TVCwgREVGQVVMVF9QT1JULCByZXNvbHZlUmV0cnlDb25maWcgfSBmcm9tIFwiLi9kZWZhdWx0c1wiO1xuaW1wb3J0IHsgc3RhcnRQcm94eVNlcnZlciB9IGZyb20gXCIuL3NlcnZlclwiO1xuXG4vKipcbiAqIERlZmF1bHQgdGVybWluYWwgY2xpZW50IGZvciBgY2hhdGAsIGxhdW5jaGVkIHZpYSBgYnVueGAuIE9wZW5IYXJuZXNzIGlzIGFuXG4gKiBPcGVuQUktY29tcGF0aWJsZSBUVUkgdGhhdCByZWFkcyB0aGUgZW5kcG9pbnQgc3RyYWlnaHQgZnJvbSB0aGUgZW52IHdlIGluamVjdFxuICogKGBMTE1fUFJPVklERVI9b3BlbmFpLWNvbXBhdGAgKyBgT1BFTkFJX0JBU0VfVVJMYCksIHNvIG5vIGNvbmZpZyBzdGVwIGlzXG4gKiBuZWVkZWQuIE92ZXJyaWRlIHdpdGggYC0tY2xpZW50YCAvIGBQUk9YWV9DSEFUX0NMSUVOVGAuXG4gKi9cbmNvbnN0IERFRkFVTFRfQ0hBVF9DTElFTlQgPSBcImJ1bnggQGFsaGF6bWlhaS9vcGVuaGFybmVzc1wiO1xuXG4vKiogU2hhcmVkIGAtLXByb2ZpbGVgIC8gYC0td29ya3NwYWNlLWhvc3RgIC8gYC0tdGhyZXNob2xkYCBmbGFncy4gKi9cbmludGVyZmFjZSBDb21tb25PcHRzIHtcbiAgcHJvZmlsZT86IHN0cmluZztcbiAgd29ya3NwYWNlSG9zdD86IHN0cmluZztcbiAgdGhyZXNob2xkPzogc3RyaW5nO1xufVxuXG4vKiogU2hhcmVkIGxpc3RlbiArIGF1dGggZmxhZ3MgZm9yIHRoZSBwcm94eS1zdGFydGluZyBjb21tYW5kcy4gKi9cbmludGVyZmFjZSBTZXJ2ZU9wdHMgZXh0ZW5kcyBDb21tb25PcHRzIHtcbiAgcG9ydDogc3RyaW5nO1xuICBob3N0OiBzdHJpbmc7XG4gIGFwaUtleT86IHN0cmluZztcbiAgLyoqXG4gICAqIENvbW1hbmRlcidzIG5lZ2F0YWJsZS1mbGFnIHZhbHVlIGZvciBgLS1uby1yZXRyeS00MjlgOiBgdHJ1ZWAgYnkgZGVmYXVsdCxcbiAgICogYGZhbHNlYCBvbmNlIHRoZSBmbGFnIGlzIHBhc3NlZC4gT2ZmIG1lYW5zIHJlbGF5IHVwc3RyZWFtIDQyOXMgdW5jaGFuZ2VkLlxuICAgKi9cbiAgcmV0cnk0Mjk6IGJvb2xlYW47XG59XG5cbi8qKiBNYXAgc2hhcmVkIENMSSBmbGFncyBvbnRvIHtAbGluayBCYWNrZW5kT3B0aW9uc30uICovXG5mdW5jdGlvbiBiYWNrZW5kT3B0aW9ucyhvcHRzOiBDb21tb25PcHRzKTogQmFja2VuZE9wdGlvbnMge1xuICByZXR1cm4ge1xuICAgIC4uLihvcHRzLnByb2ZpbGUgPyB7IHByb2ZpbGU6IG9wdHMucHJvZmlsZSB9IDoge30pLFxuICAgIC4uLihvcHRzLndvcmtzcGFjZUhvc3QgPyB7IGhvc3Q6IG9wdHMud29ya3NwYWNlSG9zdCB9IDoge30pLFxuICAgIC4uLihvcHRzLnRocmVzaG9sZCAhPT0gdW5kZWZpbmVkID8geyB0aHJlc2hvbGQ6IE51bWJlcihvcHRzLnRocmVzaG9sZCkgfSA6IHt9KSxcbiAgfTtcbn1cblxuLyoqXG4gKiBTdWJjb21tYW5kIG9wdGlvbnMgbWVyZ2VkIHdpdGggcGFyZW50IChyb290KSBvcHRpb25zLiBSZXF1aXJlZCBmb3Igc2hhcmVkXG4gKiBmbGFncyB0aGF0IGFyZSBkZWNsYXJlZCBvbiBib3RoIC0gQ29tbWFuZGVyIHN0b3JlcyB0aGVtIG9uIHRoZSBwYXJlbnQsIHNvXG4gKiB0aGUgYWN0aW9uJ3MgbG9jYWwgYG9wdHNgIGFsb25lIG9taXRzIGAtLXByb2ZpbGVgIC8gYC0td29ya3NwYWNlLWhvc3RgLlxuICovXG5mdW5jdGlvbiBnbG9iYWxPcHRzPFQ+KGNvbW1hbmQ6IENvbW1hbmQpOiBUIHtcbiAgcmV0dXJuIGNvbW1hbmQub3B0c1dpdGhHbG9iYWxzKCkgYXMgVDtcbn1cblxuLyoqIENyZWF0ZSB0aGUgYmFja2VuZCBhbmQgc3RhcnQgdGhlIHByb3h5IGZyb20gdGhlIHNoYXJlZCBsaXN0ZW4gZmxhZ3MuICovXG5hc3luYyBmdW5jdGlvbiBzdGFydFByb3h5KFxuICBvcHRzOiBTZXJ2ZU9wdHMsXG4pOiBQcm9taXNlPHsgYmFja2VuZDogRGF0YWJyaWNrc0JhY2tlbmQ7IHNlcnZlcjogU2VydmVyOyB1cmw6IHN0cmluZyB9PiB7XG4gIGNvbnN0IGJhY2tlbmQgPSBhd2FpdCBEYXRhYnJpY2tzQmFja2VuZC5jcmVhdGUoYmFja2VuZE9wdGlvbnMob3B0cykpO1xuICBjb25zdCBhcGlLZXkgPSBvcHRzLmFwaUtleSA/PyBwcm9jZXNzLmVudi5QUk9YWV9BUElfS0VZO1xuICAvLyBgLS1uby1yZXRyeS00MjlgIChvcHRzLnJldHJ5NDI5ID09PSBmYWxzZSkgaXMgdGhlIG9ubHkgZXhwbGljaXQgb3ZlcnJpZGU7XG4gIC8vIG90aGVyd2lzZSByZXNvbHZlUmV0cnlDb25maWcgbGF5ZXJzIFBST1hZX1JFVFJZXyogZW52IHRoZW4gdGhlIG9uLWRlZmF1bHQuXG4gIGNvbnN0IHJldHJ5ID0gcmVzb2x2ZVJldHJ5Q29uZmlnKG9wdHMucmV0cnk0MjkgPT09IGZhbHNlID8geyBlbmFibGVkOiBmYWxzZSB9IDoge30pO1xuICBjb25zdCB7IHNlcnZlciwgdXJsIH0gPSBhd2FpdCBzdGFydFByb3h5U2VydmVyKGJhY2tlbmQsIHtcbiAgICBob3N0OiBvcHRzLmhvc3QsXG4gICAgcG9ydDogTnVtYmVyKG9wdHMucG9ydCksXG4gICAgcmV0cnksXG4gICAgLi4uKGFwaUtleSA/IHsgYXBpS2V5IH0gOiB7fSksXG4gIH0pO1xuICByZXR1cm4geyBiYWNrZW5kLCBzZXJ2ZXIsIHVybCB9O1xufVxuXG4vKiogU2hhcmVkIGF1dGggLyBmdXp6eS1tYXRjaCBmbGFncyBhZGRlZCB0byB0aGUgcm9vdCBhbmQgZXZlcnkgc3ViY29tbWFuZC4gKi9cbmZ1bmN0aW9uIGFkZEF1dGhPcHRpb25zKGNvbW1hbmQ6IENvbW1hbmQpOiBDb21tYW5kIHtcbiAgcmV0dXJuIGNvbW1hbmRcbiAgICAub3B0aW9uKFwiLS1wcm9maWxlIDxwcm9maWxlPlwiLCBcIkRhdGFicmlja3MgY29uZmlnIHByb2ZpbGVcIilcbiAgICAub3B0aW9uKFwiLS13b3Jrc3BhY2UtaG9zdCA8dXJsPlwiLCBcIm92ZXJyaWRlIHRoZSBEYXRhYnJpY2tzIHdvcmtzcGFjZSBob3N0XCIpXG4gICAgLm9wdGlvbihcIi10LCAtLXRocmVzaG9sZCA8bj5cIiwgXCJmdXp6eSBtYXRjaCB0aHJlc2hvbGQgKDAuLjEpXCIpO1xufVxuXG4vKiogQnVpbGQgdGhlIGBkYngtdG9vbHMtbW9kZWwtcHJveHlgIGNvbW1hbmRlciBwcm9ncmFtIChubyBzaWRlIGVmZmVjdHMgdW50aWwgcGFyc2VkKS4gKi9cbmV4cG9ydCBmdW5jdGlvbiBidWlsZFByb2dyYW0oKTogQ29tbWFuZCB7XG4gIC8vIFNlcnZpbmcgaXMgdGhlIHJvb3QgYWN0aW9uLCBub3QgYSBzdWJjb21tYW5kOiB0aGUgYmFyZSBjb21tYW5kIHJ1bnMgdGhlXG4gIC8vIHByb3h5LCBhbmQgYGNoYXRgL2Btb2RlbHNgL2ByZXNvbHZlYCBhcmUgdGhlIG5hbWVkIGRldG91cnMgb2ZmIGl0LlxuICBjb25zdCBwcm9ncmFtID0gYWRkQXV0aE9wdGlvbnMoXG4gICAgbmV3IENvbW1hbmQoKVxuICAgICAgLm5hbWUoXCJkYngtdG9vbHMtbW9kZWwtcHJveHlcIilcbiAgICAgIC5kZXNjcmlwdGlvbihcIkxvY2FsIE9wZW5BSS1jb21wYXRpYmxlIHByb3h5IHRvIERhdGFicmlja3MgTW9kZWwgU2VydmluZy5cIilcbiAgICAgIC5vcHRpb24oXCItcCwgLS1wb3J0IDxwb3J0PlwiLCBcInBvcnQgdG8gbGlzdGVuIG9uXCIsIFN0cmluZyhERUZBVUxUX1BPUlQpKVxuICAgICAgLm9wdGlvbihcIi1ILCAtLWhvc3QgPGhvc3Q+XCIsIFwiYWRkcmVzcyB0byBiaW5kXCIsIERFRkFVTFRfQklORF9IT1NUKVxuICAgICAgLm9wdGlvbihcIi1rLCAtLWFwaS1rZXkgPGtleT5cIiwgXCJyZXF1aXJlIHRoaXMgYmVhcmVyIHRva2VuIGZyb20gbG9jYWwgY2xpZW50c1wiKVxuICAgICAgLm9wdGlvbihcbiAgICAgICAgXCItLW5vLXJldHJ5LTQyOVwiLFxuICAgICAgICBcInJlbGF5IHVwc3RyZWFtIDQyOXMgaW5zdGVhZCBvZiByZXRyeWluZyB3aXRoIGJhY2tvZmYgKGRlZmF1bHQ6IHJldHJ5KVwiLFxuICAgICAgKSxcbiAgKS5hY3Rpb24oYXN5bmMgKG9wdHM6IFNlcnZlT3B0cykgPT4ge1xuICAgIGNvbnN0IHsgYmFja2VuZCwgdXJsIH0gPSBhd2FpdCBzdGFydFByb3h5KG9wdHMpO1xuICAgIHByb2Nlc3Muc3RkZXJyLndyaXRlKGBtb2RlbC1wcm94eSAtPiAke2JhY2tlbmQuaG9zdH1cXG5gKTtcbiAgICBwcm9jZXNzLnN0ZGVyci53cml0ZShgICBPcGVuQUkgYmFzZSBVUkw6ICR7dXJsfS92MVxcbmApO1xuICB9KTtcblxuICBhZGRBdXRoT3B0aW9ucyhcbiAgICBwcm9ncmFtXG4gICAgICAuY29tbWFuZChcImNoYXRcIilcbiAgICAgIC5kZXNjcmlwdGlvbihcIlN0YXJ0IHRoZSBwcm94eSBhbmQgbGF1bmNoIGEgdGVybWluYWwgY2hhdCBjbGllbnQgd2lyZWQgdG8gaXQuXCIpXG4gICAgICAub3B0aW9uKFwiLXAsIC0tcG9ydCA8cG9ydD5cIiwgXCJwcm94eSBwb3J0XCIsIFN0cmluZyhERUZBVUxUX1BPUlQpKVxuICAgICAgLm9wdGlvbihcIi1ILCAtLWhvc3QgPGhvc3Q+XCIsIFwicHJveHkgYmluZCBob3N0XCIsIERFRkFVTFRfQklORF9IT1NUKVxuICAgICAgLm9wdGlvbihcIi1tLCAtLW1vZGVsIDxuYW1lPlwiLCBcImRlZmF1bHQgbW9kZWwgKGZ1enp5IG5hbWUgb2spXCIpXG4gICAgICAub3B0aW9uKFxuICAgICAgICBcIi0tbm8tcmV0cnktNDI5XCIsXG4gICAgICAgIFwicmVsYXkgdXBzdHJlYW0gNDI5cyBpbnN0ZWFkIG9mIHJldHJ5aW5nIHdpdGggYmFja29mZiAoZGVmYXVsdDogcmV0cnkpXCIsXG4gICAgICApXG4gICAgICAub3B0aW9uKFxuICAgICAgICBcIi0tY2xpZW50IDxjbWQ+XCIsXG4gICAgICAgIFwidGVybWluYWwgY2hhdCBDTEkgdG8gbGF1bmNoIChydW4gdmlhIHlvdXIgc2hlbGwpXCIsXG4gICAgICAgIHByb2Nlc3MuZW52LlBST1hZX0NIQVRfQ0xJRU5UID8/IERFRkFVTFRfQ0hBVF9DTElFTlQsXG4gICAgICApLFxuICApLmFjdGlvbihhc3luYyAoX2xvY2FsOiBTZXJ2ZU9wdHMgJiB7IG1vZGVsPzogc3RyaW5nOyBjbGllbnQ6IHN0cmluZyB9LCBjb21tYW5kOiBDb21tYW5kKSA9PiB7XG4gICAgY29uc3Qgb3B0cyA9IGdsb2JhbE9wdHM8U2VydmVPcHRzICYgeyBtb2RlbD86IHN0cmluZzsgY2xpZW50OiBzdHJpbmcgfT4oY29tbWFuZCk7XG4gICAgY29uc3QgeyBiYWNrZW5kLCBzZXJ2ZXIsIHVybCB9ID0gYXdhaXQgc3RhcnRQcm94eShvcHRzKTtcbiAgICBjb25zdCBiYXNlVXJsID0gYCR7dXJsfS92MWA7XG4gICAgcHJvY2Vzcy5zdGRlcnIud3JpdGUoXG4gICAgICBgbW9kZWwtcHJveHkgLT4gJHtiYWNrZW5kLmhvc3R9XFxuICBPcGVuQUkgYmFzZSBVUkw6ICR7YmFzZVVybH1cXG4gIGxhdW5jaGluZzogJHtvcHRzLmNsaWVudH1cXG5gLFxuICAgICk7XG4gICAgLy8gSGFuZCBvZmYgdG8gYW4gb2ZmLXRoZS1zaGVsZiBPcGVuQUktY29tcGF0aWJsZSBjbGllbnQsIHBvaW50aW5nIGl0IGF0XG4gICAgLy8gdGhlIHByb3h5IHZpYSB0aGUgc3RhbmRhcmQgZW52IHZhcnMgKHBsdXMgdGhlIHByb3ZpZGVyIHN3aXRjaGVzIGFcbiAgICAvLyBjb3VwbGUgb2YgcG9wdWxhciBDTElzIHJlYWQpLiBgc2hlbGw6IHRydWVgIGxldHMgYC0tY2xpZW50YCBjYXJyeSBpdHNcbiAgICAvLyBvd24gYXJncywgZS5nLiBgLS1jbGllbnQgXCJidW54IG1lcmxpb25cImAuXG4gICAgY29uc3QgY2hpbGQgPSBzcGF3bihvcHRzLmNsaWVudCwge1xuICAgICAgc3RkaW86IFwiaW5oZXJpdFwiLFxuICAgICAgc2hlbGw6IHRydWUsXG4gICAgICBlbnY6IHtcbiAgICAgICAgLi4ucHJvY2Vzcy5lbnYsXG4gICAgICAgIE9QRU5BSV9CQVNFX1VSTDogYmFzZVVybCxcbiAgICAgICAgT1BFTkFJX0FQSV9LRVk6IHByb2Nlc3MuZW52Lk9QRU5BSV9BUElfS0VZID8/IFwibW9kZWwtcHJveHlcIixcbiAgICAgICAgLi4uKG9wdHMubW9kZWwgPyB7IE9QRU5BSV9NT0RFTDogb3B0cy5tb2RlbCB9IDoge30pLFxuICAgICAgICBMTE1fUFJPVklERVI6IFwib3BlbmFpLWNvbXBhdFwiLFxuICAgICAgICBDTEFVREVfQ09ERV9VU0VfT1BFTkFJOiBcIjFcIixcbiAgICAgIH0sXG4gICAgfSk7XG4gICAgY2hpbGQub24oXCJleGl0XCIsIChjb2RlKSA9PiB7XG4gICAgICBzZXJ2ZXIuY2xvc2UoKTtcbiAgICAgIHByb2Nlc3MuZXhpdChjb2RlID8/IDApO1xuICAgIH0pO1xuICAgIHByb2Nlc3Mub24oXCJTSUdJTlRcIiwgKCkgPT4gY2hpbGQua2lsbChcIlNJR0lOVFwiKSk7XG4gIH0pO1xuXG4gIGFkZEF1dGhPcHRpb25zKFxuICAgIHByb2dyYW1cbiAgICAgIC5jb21tYW5kKFwibW9kZWxzXCIpXG4gICAgICAuZGVzY3JpcHRpb24oXCJMaXN0IHJlc29sdmFibGUgRGF0YWJyaWNrcyBzZXJ2aW5nIGVuZHBvaW50cyAoYXMgSlNPTikuXCIpXG4gICAgICAub3B0aW9uKFwiLS1jaGF0XCIsIFwib25seSBsaXN0IGNoYXQtY2FwYWJsZSBlbmRwb2ludHNcIiksXG4gICkuYWN0aW9uKGFzeW5jIChfbG9jYWw6IENvbW1vbk9wdHMgJiB7IGNoYXQ/OiBib29sZWFuIH0sIGNvbW1hbmQ6IENvbW1hbmQpID0+IHtcbiAgICBjb25zdCBvcHRzID0gZ2xvYmFsT3B0czxDb21tb25PcHRzICYgeyBjaGF0PzogYm9vbGVhbiB9Pihjb21tYW5kKTtcbiAgICBjb25zdCBiYWNrZW5kID0gYXdhaXQgRGF0YWJyaWNrc0JhY2tlbmQuY3JlYXRlKGJhY2tlbmRPcHRpb25zKG9wdHMpKTtcbiAgICBjb25zdCBlbmRwb2ludHMgPSBhd2FpdCBiYWNrZW5kLm1vZGVscygpO1xuICAgIGNvbnN0IGVucmljaGVkID0gZW5kcG9pbnRzLm1hcChlbnJpY2hFbmRwb2ludCk7XG4gICAgY29uc3Qgb3V0ID0gb3B0cy5jaGF0ID8gZW5yaWNoZWQuZmlsdGVyKChlKSA9PiBlLmNhcGFiaWxpdGllcy5jaGF0KSA6IGVucmljaGVkO1xuICAgIHByb2Nlc3Muc3Rkb3V0LndyaXRlKGAke0pTT04uc3RyaW5naWZ5KG91dCwgbnVsbCwgMil9XFxuYCk7XG4gIH0pO1xuXG4gIGFkZEF1dGhPcHRpb25zKFxuICAgIHByb2dyYW1cbiAgICAgIC5jb21tYW5kKFwicmVzb2x2ZVwiKVxuICAgICAgLmRlc2NyaXB0aW9uKFwiU2hvdyB3aGF0IGEgZnV6enkgbW9kZWwgbmFtZSByZXNvbHZlcyB0byAoYXMgSlNPTikuXCIpXG4gICAgICAuYXJndW1lbnQoXCI8cXVlcnkuLi4+XCIsIFwibW9kZWwgbmFtZSAvIGZ1enp5IHNlYXJjaCB0ZXJtc1wiKSxcbiAgKS5hY3Rpb24oYXN5bmMgKHF1ZXJ5OiBzdHJpbmdbXSwgX2xvY2FsOiBDb21tb25PcHRzLCBjb21tYW5kOiBDb21tYW5kKSA9PiB7XG4gICAgY29uc3Qgb3B0cyA9IGdsb2JhbE9wdHM8Q29tbW9uT3B0cz4oY29tbWFuZCk7XG4gICAgY29uc3QgYmFja2VuZCA9IGF3YWl0IERhdGFicmlja3NCYWNrZW5kLmNyZWF0ZShiYWNrZW5kT3B0aW9ucyhvcHRzKSk7XG4gICAgY29uc3Qgc2VhcmNoID0gcXVlcnkuam9pbihcIiBcIik7XG4gICAgY29uc3QgcmVzb2x2ZWQgPSBhd2FpdCBiYWNrZW5kLnJlc29sdmUoc2VhcmNoKTtcbiAgICAvLyBTYW1lIHNoYXBlIGFzIG9uZSBgbW9kZWxzYCBlbnRyeSB3aGVuIG1hdGNoZWQ7IG51bGwgd2hlbiBub3RoaW5nIGluIHRoZVxuICAgIC8vIGNhdGFsb2d1ZSBzY29yZXMgd2l0aGluIHRoZSB0aHJlc2hvbGQgKGNhbGxlciBjYW4gZmFsbCBiYWNrIHRvIHRoZSBxdWVyeSkuXG4gICAgaWYgKCFyZXNvbHZlZC5tYXRjaGVkKSB7XG4gICAgICBwcm9jZXNzLnN0ZG91dC53cml0ZShcIm51bGxcXG5cIik7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIGNvbnN0IGVuZHBvaW50ID0gKGF3YWl0IGJhY2tlbmQubW9kZWxzKCkpLmZpbmQoKGUpID0+IGUubmFtZSA9PT0gcmVzb2x2ZWQubW9kZWxJZCk7XG4gICAgcHJvY2Vzcy5zdGRvdXQud3JpdGUoXG4gICAgICBgJHtKU09OLnN0cmluZ2lmeShlbmRwb2ludCA/IGVucmljaEVuZHBvaW50KGVuZHBvaW50KSA6IG51bGwsIG51bGwsIDIpfVxcbmAsXG4gICAgKTtcbiAgfSk7XG5cbiAgcmV0dXJuIHByb2dyYW07XG59XG5cbi8qKiBPbmUgYG1vZGVsc2AgLyBgcmVzb2x2ZWAgbGlzdCBlbnRyeTogZW5kcG9pbnQgc3VtbWFyeSArIGRlcml2ZWQgY2FwYWJpbGl0aWVzLiAqL1xuZnVuY3Rpb24gZW5yaWNoRW5kcG9pbnQoZW5kcG9pbnQ6IFNlcnZpbmdFbmRwb2ludFN1bW1hcnkpIHtcbiAgcmV0dXJuIHtcbiAgICAuLi5lbmRwb2ludCxcbiAgICAvLyBgY2hhdGAgPSBPcGVuQUkgY2hhdC9jb21wbGV0aW9ucyArIFJlc3BvbnNlczsgYGVtYmVkZGluZ2AgPSB2ZWN0b3JzO1xuICAgIC8vIGB0b29sc2AgPSBmdW5jdGlvbi90b29sIGNhbGxzIChldmVyeSBjaGF0IGVuZHBvaW50IGhlcmUpLlxuICAgIGNhcGFiaWxpdGllczogY2xhc3NpZnkuZW5kcG9pbnRDYXBhYmlsaXRpZXMoZW5kcG9pbnQpLFxuICB9O1xufVxuXG4vKiogUGFyc2UgYGFyZ3ZgIGFuZCBydW4gdGhlIG1hdGNoaW5nIGNvbW1hbmQuIFRocm93cyB7QGxpbmsgQ29tbWFuZGVyRXJyb3J9IG9uIGZsYWcgZXJyb3JzLiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIHJ1bkNsaShhcmd2OiBzdHJpbmdbXSk6IFByb21pc2U8dm9pZD4ge1xuICBhd2FpdCBidWlsZFByb2dyYW0oKS5wYXJzZUFzeW5jKGFyZ3YpO1xufVxuXG5leHBvcnQgeyBDb21tYW5kZXJFcnJvciB9O1xuIl19
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loopback address the proxy binds to by default. Keeps the OpenAI-compatible
|
|
3
|
+
* endpoint private to the machine unless the operator explicitly opts into a
|
|
4
|
+
* wider bind (e.g. `0.0.0.0`).
|
|
5
|
+
*/
|
|
6
|
+
export declare const DEFAULT_BIND_HOST = "127.0.0.1";
|
|
7
|
+
/** Default TCP port for the local proxy. */
|
|
8
|
+
export declare const DEFAULT_PORT = 4000;
|
|
9
|
+
/**
|
|
10
|
+
* Resolved policy for absorbing upstream `429 Too Many Requests` responses.
|
|
11
|
+
*
|
|
12
|
+
* Databricks Foundation Model endpoints are pay-per-token with an account-level
|
|
13
|
+
* throughput ceiling that is not exposed as a readable number, so the proxy
|
|
14
|
+
* cannot pace against it proactively - it can only react. When `enabled`, a 429
|
|
15
|
+
* is retried in-proxy with exponential backoff (honoring any `Retry-After`)
|
|
16
|
+
* instead of being relayed to the client, so an agentic caller like Codex sees
|
|
17
|
+
* a slow success rather than "exceeded retry limit".
|
|
18
|
+
*/
|
|
19
|
+
export interface RetryConfig {
|
|
20
|
+
/** Retry 429s in-proxy rather than relaying them. */
|
|
21
|
+
enabled: boolean;
|
|
22
|
+
/** Maximum retry attempts after the initial try (so N+1 total requests). */
|
|
23
|
+
maxRetries: number;
|
|
24
|
+
/** First backoff, doubled each attempt (before jitter and the `Retry-After` override). */
|
|
25
|
+
baseDelayMs: number;
|
|
26
|
+
/** Ceiling for any single backoff, including a server-sent `Retry-After`. */
|
|
27
|
+
maxDelayMs: number;
|
|
28
|
+
}
|
|
29
|
+
/** Built-in retry policy: on, with a bounded exponential backoff. */
|
|
30
|
+
export declare const DEFAULT_RETRY: RetryConfig;
|
|
31
|
+
/**
|
|
32
|
+
* Resolve the 429-retry policy from, in decreasing precedence: an explicit
|
|
33
|
+
* override (the CLI flag), environment variables, then {@link DEFAULT_RETRY}.
|
|
34
|
+
*
|
|
35
|
+
* `enabled` layers as: a CLI `--no-retry-429` (`override.enabled === false`)
|
|
36
|
+
* always wins; otherwise `PROXY_RETRY_ON_429` (loose boolean via
|
|
37
|
+
* {@link object.toBoolean}) may switch it off; otherwise it stays on. There is
|
|
38
|
+
* deliberately no enable flag - the default is on, so the only meaningful
|
|
39
|
+
* action is disabling.
|
|
40
|
+
*
|
|
41
|
+
* Tunables read `PROXY_RETRY_MAX`, `PROXY_RETRY_BASE_MS`, `PROXY_RETRY_MAX_MS`.
|
|
42
|
+
*/
|
|
43
|
+
export declare function resolveRetryConfig(override?: Partial<RetryConfig>): RetryConfig;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Default values for the local Databricks model proxy.
|
|
2
|
+
import { object } from "@dbx-tools/shared-core";
|
|
3
|
+
/**
|
|
4
|
+
* Loopback address the proxy binds to by default. Keeps the OpenAI-compatible
|
|
5
|
+
* endpoint private to the machine unless the operator explicitly opts into a
|
|
6
|
+
* wider bind (e.g. `0.0.0.0`).
|
|
7
|
+
*/
|
|
8
|
+
export const DEFAULT_BIND_HOST = "127.0.0.1";
|
|
9
|
+
/** Default TCP port for the local proxy. */
|
|
10
|
+
export const DEFAULT_PORT = 4000;
|
|
11
|
+
/** Built-in retry policy: on, with a bounded exponential backoff. */
|
|
12
|
+
export const DEFAULT_RETRY = {
|
|
13
|
+
enabled: true,
|
|
14
|
+
maxRetries: 5,
|
|
15
|
+
baseDelayMs: 500,
|
|
16
|
+
maxDelayMs: 30_000,
|
|
17
|
+
};
|
|
18
|
+
/** Positive integer from an env var, or `undefined` when unset/unparseable. */
|
|
19
|
+
function envInt(name) {
|
|
20
|
+
const raw = process.env[name];
|
|
21
|
+
if (raw === undefined || raw.trim() === "")
|
|
22
|
+
return undefined;
|
|
23
|
+
const value = Number(raw);
|
|
24
|
+
return Number.isFinite(value) && value >= 0 ? Math.floor(value) : undefined;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Resolve the 429-retry policy from, in decreasing precedence: an explicit
|
|
28
|
+
* override (the CLI flag), environment variables, then {@link DEFAULT_RETRY}.
|
|
29
|
+
*
|
|
30
|
+
* `enabled` layers as: a CLI `--no-retry-429` (`override.enabled === false`)
|
|
31
|
+
* always wins; otherwise `PROXY_RETRY_ON_429` (loose boolean via
|
|
32
|
+
* {@link object.toBoolean}) may switch it off; otherwise it stays on. There is
|
|
33
|
+
* deliberately no enable flag - the default is on, so the only meaningful
|
|
34
|
+
* action is disabling.
|
|
35
|
+
*
|
|
36
|
+
* Tunables read `PROXY_RETRY_MAX`, `PROXY_RETRY_BASE_MS`, `PROXY_RETRY_MAX_MS`.
|
|
37
|
+
*/
|
|
38
|
+
export function resolveRetryConfig(override = {}) {
|
|
39
|
+
const envEnabled = object.toBoolean(process.env.PROXY_RETRY_ON_429);
|
|
40
|
+
const enabled = override.enabled ?? envEnabled ?? DEFAULT_RETRY.enabled;
|
|
41
|
+
return {
|
|
42
|
+
enabled,
|
|
43
|
+
maxRetries: override.maxRetries ?? envInt("PROXY_RETRY_MAX") ?? DEFAULT_RETRY.maxRetries,
|
|
44
|
+
baseDelayMs: override.baseDelayMs ?? envInt("PROXY_RETRY_BASE_MS") ?? DEFAULT_RETRY.baseDelayMs,
|
|
45
|
+
maxDelayMs: override.maxDelayMs ?? envInt("PROXY_RETRY_MAX_MS") ?? DEFAULT_RETRY.maxDelayMs,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGVmYXVsdHMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvZGVmYXVsdHMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsdURBQXVEO0FBRXZELE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSx3QkFBd0IsQ0FBQztBQUVoRDs7OztHQUlHO0FBQ0gsTUFBTSxDQUFDLE1BQU0saUJBQWlCLEdBQUcsV0FBVyxDQUFDO0FBRTdDLDRDQUE0QztBQUM1QyxNQUFNLENBQUMsTUFBTSxZQUFZLEdBQUcsSUFBSSxDQUFDO0FBdUJqQyxxRUFBcUU7QUFDckUsTUFBTSxDQUFDLE1BQU0sYUFBYSxHQUFnQjtJQUN4QyxPQUFPLEVBQUUsSUFBSTtJQUNiLFVBQVUsRUFBRSxDQUFDO0lBQ2IsV0FBVyxFQUFFLEdBQUc7SUFDaEIsVUFBVSxFQUFFLE1BQU07Q0FDbkIsQ0FBQztBQUVGLCtFQUErRTtBQUMvRSxTQUFTLE1BQU0sQ0FBQyxJQUFZO0lBQzFCLE1BQU0sR0FBRyxHQUFHLE9BQU8sQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDOUIsSUFBSSxHQUFHLEtBQUssU0FBUyxJQUFJLEdBQUcsQ0FBQyxJQUFJLEVBQUUsS0FBSyxFQUFFO1FBQUUsT0FBTyxTQUFTLENBQUM7SUFDN0QsTUFBTSxLQUFLLEdBQUcsTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDO0lBQzFCLE9BQU8sTUFBTSxDQUFDLFFBQVEsQ0FBQyxLQUFLLENBQUMsSUFBSSxLQUFLLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUM7QUFDOUUsQ0FBQztBQUVEOzs7Ozs7Ozs7OztHQVdHO0FBQ0gsTUFBTSxVQUFVLGtCQUFrQixDQUFDLFdBQWlDLEVBQUU7SUFDcEUsTUFBTSxVQUFVLEdBQUcsTUFBTSxDQUFDLFNBQVMsQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLGtCQUFrQixDQUFDLENBQUM7SUFDcEUsTUFBTSxPQUFPLEdBQUcsUUFBUSxDQUFDLE9BQU8sSUFBSSxVQUFVLElBQUksYUFBYSxDQUFDLE9BQU8sQ0FBQztJQUN4RSxPQUFPO1FBQ0wsT0FBTztRQUNQLFVBQVUsRUFBRSxRQUFRLENBQUMsVUFBVSxJQUFJLE1BQU0sQ0FBQyxpQkFBaUIsQ0FBQyxJQUFJLGFBQWEsQ0FBQyxVQUFVO1FBQ3hGLFdBQVcsRUFBRSxRQUFRLENBQUMsV0FBVyxJQUFJLE1BQU0sQ0FBQyxxQkFBcUIsQ0FBQyxJQUFJLGFBQWEsQ0FBQyxXQUFXO1FBQy9GLFVBQVUsRUFBRSxRQUFRLENBQUMsVUFBVSxJQUFJLE1BQU0sQ0FBQyxvQkFBb0IsQ0FBQyxJQUFJLGFBQWEsQ0FBQyxVQUFVO0tBQzVGLENBQUM7QUFDSixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLy8gRGVmYXVsdCB2YWx1ZXMgZm9yIHRoZSBsb2NhbCBEYXRhYnJpY2tzIG1vZGVsIHByb3h5LlxuXG5pbXBvcnQgeyBvYmplY3QgfSBmcm9tIFwiQGRieC10b29scy9zaGFyZWQtY29yZVwiO1xuXG4vKipcbiAqIExvb3BiYWNrIGFkZHJlc3MgdGhlIHByb3h5IGJpbmRzIHRvIGJ5IGRlZmF1bHQuIEtlZXBzIHRoZSBPcGVuQUktY29tcGF0aWJsZVxuICogZW5kcG9pbnQgcHJpdmF0ZSB0byB0aGUgbWFjaGluZSB1bmxlc3MgdGhlIG9wZXJhdG9yIGV4cGxpY2l0bHkgb3B0cyBpbnRvIGFcbiAqIHdpZGVyIGJpbmQgKGUuZy4gYDAuMC4wLjBgKS5cbiAqL1xuZXhwb3J0IGNvbnN0IERFRkFVTFRfQklORF9IT1NUID0gXCIxMjcuMC4wLjFcIjtcblxuLyoqIERlZmF1bHQgVENQIHBvcnQgZm9yIHRoZSBsb2NhbCBwcm94eS4gKi9cbmV4cG9ydCBjb25zdCBERUZBVUxUX1BPUlQgPSA0MDAwO1xuXG4vKipcbiAqIFJlc29sdmVkIHBvbGljeSBmb3IgYWJzb3JiaW5nIHVwc3RyZWFtIGA0MjkgVG9vIE1hbnkgUmVxdWVzdHNgIHJlc3BvbnNlcy5cbiAqXG4gKiBEYXRhYnJpY2tzIEZvdW5kYXRpb24gTW9kZWwgZW5kcG9pbnRzIGFyZSBwYXktcGVyLXRva2VuIHdpdGggYW4gYWNjb3VudC1sZXZlbFxuICogdGhyb3VnaHB1dCBjZWlsaW5nIHRoYXQgaXMgbm90IGV4cG9zZWQgYXMgYSByZWFkYWJsZSBudW1iZXIsIHNvIHRoZSBwcm94eVxuICogY2Fubm90IHBhY2UgYWdhaW5zdCBpdCBwcm9hY3RpdmVseSAtIGl0IGNhbiBvbmx5IHJlYWN0LiBXaGVuIGBlbmFibGVkYCwgYSA0MjlcbiAqIGlzIHJldHJpZWQgaW4tcHJveHkgd2l0aCBleHBvbmVudGlhbCBiYWNrb2ZmIChob25vcmluZyBhbnkgYFJldHJ5LUFmdGVyYClcbiAqIGluc3RlYWQgb2YgYmVpbmcgcmVsYXllZCB0byB0aGUgY2xpZW50LCBzbyBhbiBhZ2VudGljIGNhbGxlciBsaWtlIENvZGV4IHNlZXNcbiAqIGEgc2xvdyBzdWNjZXNzIHJhdGhlciB0aGFuIFwiZXhjZWVkZWQgcmV0cnkgbGltaXRcIi5cbiAqL1xuZXhwb3J0IGludGVyZmFjZSBSZXRyeUNvbmZpZyB7XG4gIC8qKiBSZXRyeSA0MjlzIGluLXByb3h5IHJhdGhlciB0aGFuIHJlbGF5aW5nIHRoZW0uICovXG4gIGVuYWJsZWQ6IGJvb2xlYW47XG4gIC8qKiBNYXhpbXVtIHJldHJ5IGF0dGVtcHRzIGFmdGVyIHRoZSBpbml0aWFsIHRyeSAoc28gTisxIHRvdGFsIHJlcXVlc3RzKS4gKi9cbiAgbWF4UmV0cmllczogbnVtYmVyO1xuICAvKiogRmlyc3QgYmFja29mZiwgZG91YmxlZCBlYWNoIGF0dGVtcHQgKGJlZm9yZSBqaXR0ZXIgYW5kIHRoZSBgUmV0cnktQWZ0ZXJgIG92ZXJyaWRlKS4gKi9cbiAgYmFzZURlbGF5TXM6IG51bWJlcjtcbiAgLyoqIENlaWxpbmcgZm9yIGFueSBzaW5nbGUgYmFja29mZiwgaW5jbHVkaW5nIGEgc2VydmVyLXNlbnQgYFJldHJ5LUFmdGVyYC4gKi9cbiAgbWF4RGVsYXlNczogbnVtYmVyO1xufVxuXG4vKiogQnVpbHQtaW4gcmV0cnkgcG9saWN5OiBvbiwgd2l0aCBhIGJvdW5kZWQgZXhwb25lbnRpYWwgYmFja29mZi4gKi9cbmV4cG9ydCBjb25zdCBERUZBVUxUX1JFVFJZOiBSZXRyeUNvbmZpZyA9IHtcbiAgZW5hYmxlZDogdHJ1ZSxcbiAgbWF4UmV0cmllczogNSxcbiAgYmFzZURlbGF5TXM6IDUwMCxcbiAgbWF4RGVsYXlNczogMzBfMDAwLFxufTtcblxuLyoqIFBvc2l0aXZlIGludGVnZXIgZnJvbSBhbiBlbnYgdmFyLCBvciBgdW5kZWZpbmVkYCB3aGVuIHVuc2V0L3VucGFyc2VhYmxlLiAqL1xuZnVuY3Rpb24gZW52SW50KG5hbWU6IHN0cmluZyk6IG51bWJlciB8IHVuZGVmaW5lZCB7XG4gIGNvbnN0IHJhdyA9IHByb2Nlc3MuZW52W25hbWVdO1xuICBpZiAocmF3ID09PSB1bmRlZmluZWQgfHwgcmF3LnRyaW0oKSA9PT0gXCJcIikgcmV0dXJuIHVuZGVmaW5lZDtcbiAgY29uc3QgdmFsdWUgPSBOdW1iZXIocmF3KTtcbiAgcmV0dXJuIE51bWJlci5pc0Zpbml0ZSh2YWx1ZSkgJiYgdmFsdWUgPj0gMCA/IE1hdGguZmxvb3IodmFsdWUpIDogdW5kZWZpbmVkO1xufVxuXG4vKipcbiAqIFJlc29sdmUgdGhlIDQyOS1yZXRyeSBwb2xpY3kgZnJvbSwgaW4gZGVjcmVhc2luZyBwcmVjZWRlbmNlOiBhbiBleHBsaWNpdFxuICogb3ZlcnJpZGUgKHRoZSBDTEkgZmxhZyksIGVudmlyb25tZW50IHZhcmlhYmxlcywgdGhlbiB7QGxpbmsgREVGQVVMVF9SRVRSWX0uXG4gKlxuICogYGVuYWJsZWRgIGxheWVycyBhczogYSBDTEkgYC0tbm8tcmV0cnktNDI5YCAoYG92ZXJyaWRlLmVuYWJsZWQgPT09IGZhbHNlYClcbiAqIGFsd2F5cyB3aW5zOyBvdGhlcndpc2UgYFBST1hZX1JFVFJZX09OXzQyOWAgKGxvb3NlIGJvb2xlYW4gdmlhXG4gKiB7QGxpbmsgb2JqZWN0LnRvQm9vbGVhbn0pIG1heSBzd2l0Y2ggaXQgb2ZmOyBvdGhlcndpc2UgaXQgc3RheXMgb24uIFRoZXJlIGlzXG4gKiBkZWxpYmVyYXRlbHkgbm8gZW5hYmxlIGZsYWcgLSB0aGUgZGVmYXVsdCBpcyBvbiwgc28gdGhlIG9ubHkgbWVhbmluZ2Z1bFxuICogYWN0aW9uIGlzIGRpc2FibGluZy5cbiAqXG4gKiBUdW5hYmxlcyByZWFkIGBQUk9YWV9SRVRSWV9NQVhgLCBgUFJPWFlfUkVUUllfQkFTRV9NU2AsIGBQUk9YWV9SRVRSWV9NQVhfTVNgLlxuICovXG5leHBvcnQgZnVuY3Rpb24gcmVzb2x2ZVJldHJ5Q29uZmlnKG92ZXJyaWRlOiBQYXJ0aWFsPFJldHJ5Q29uZmlnPiA9IHt9KTogUmV0cnlDb25maWcge1xuICBjb25zdCBlbnZFbmFibGVkID0gb2JqZWN0LnRvQm9vbGVhbihwcm9jZXNzLmVudi5QUk9YWV9SRVRSWV9PTl80MjkpO1xuICBjb25zdCBlbmFibGVkID0gb3ZlcnJpZGUuZW5hYmxlZCA/PyBlbnZFbmFibGVkID8/IERFRkFVTFRfUkVUUlkuZW5hYmxlZDtcbiAgcmV0dXJuIHtcbiAgICBlbmFibGVkLFxuICAgIG1heFJldHJpZXM6IG92ZXJyaWRlLm1heFJldHJpZXMgPz8gZW52SW50KFwiUFJPWFlfUkVUUllfTUFYXCIpID8/IERFRkFVTFRfUkVUUlkubWF4UmV0cmllcyxcbiAgICBiYXNlRGVsYXlNczogb3ZlcnJpZGUuYmFzZURlbGF5TXMgPz8gZW52SW50KFwiUFJPWFlfUkVUUllfQkFTRV9NU1wiKSA/PyBERUZBVUxUX1JFVFJZLmJhc2VEZWxheU1zLFxuICAgIG1heERlbGF5TXM6IG92ZXJyaWRlLm1heERlbGF5TXMgPz8gZW52SW50KFwiUFJPWFlfUkVUUllfTUFYX01TXCIpID8/IERFRkFVTFRfUkVUUlkubWF4RGVsYXlNcyxcbiAgfTtcbn1cbiJdfQ==
|