@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.
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env -S npx tsx
2
+ export {};
@@ -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
@@ -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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xpLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2NsaS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBa0JHO0FBRUgsT0FBTyxFQUFFLEtBQUssRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBRTNDLE9BQU8sRUFBRSxRQUFRLEVBQStCLE1BQU0seUJBQXlCLENBQUM7QUFDaEYsT0FBTyxFQUFFLE9BQU8sRUFBRSxjQUFjLEVBQUUsTUFBTSxXQUFXLENBQUM7QUFFcEQsT0FBTyxFQUFFLGlCQUFpQixFQUF1QixNQUFNLFdBQVcsQ0FBQztBQUNuRSxPQUFPLEVBQUUsaUJBQWlCLEVBQUUsWUFBWSxFQUFFLGtCQUFrQixFQUFFLE1BQU0sWUFBWSxDQUFDO0FBQ2pGLE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLFVBQVUsQ0FBQztBQUU1Qzs7Ozs7R0FLRztBQUNILE1BQU0sbUJBQW1CLEdBQUcsNkJBQTZCLENBQUM7QUFxQjFELHdEQUF3RDtBQUN4RCxTQUFTLGNBQWMsQ0FBQyxJQUFnQjtJQUN0QyxPQUFPO1FBQ0wsR0FBRyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLEVBQUUsT0FBTyxFQUFFLElBQUksQ0FBQyxPQUFPLEVBQUUsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDO1FBQ2xELEdBQUcsQ0FBQyxJQUFJLENBQUMsYUFBYSxDQUFDLENBQUMsQ0FBQyxFQUFFLElBQUksRUFBRSxJQUFJLENBQUMsYUFBYSxFQUFFLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQztRQUMzRCxHQUFHLENBQUMsSUFBSSxDQUFDLFNBQVMsS0FBSyxTQUFTLENBQUMsQ0FBQyxDQUFDLEVBQUUsU0FBUyxFQUFFLE1BQU0sQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDO0tBQy9FLENBQUM7QUFDSixDQUFDO0FBRUQ7Ozs7R0FJRztBQUNILFNB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QU8sT0FBTyxDQUFDO0FBQ2pCLENBQUM7QUFFRCxvRkFBb0Y7QUFDcEYsU0FBUyxjQUFjLENBQUMsUUFBZ0M7SUFDdEQsT0FBTztRQUNMLEdBQUcsUUFBUTtRQUNYLHVFQUF1RTtRQUN2RSw0REFBNEQ7UUFDNUQsWUFBWSxFQUFFLFFBQVEsQ0FBQyxvQkFBb0IsQ0FBQyxRQUFRLENBQUM7S0FDdEQsQ0FBQztBQUNKLENBQUM7QUFFRCwrRkFBK0Y7QUFDL0YsTUFBTSxDQUFDLEtBQUssVUFBVSxNQUFNLENBQUMsSUFBYztJQUN6QyxNQUFNLFlBQVksRUFBRSxDQUFDLFVBQVUsQ0FBQyxJQUFJLENBQUMsQ0FBQztBQUN4QyxDQUFDO0FBRUQsT0FBTyxFQUFFLGNBQWMsRUFBRSxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBgZGJ4LXRvb2xzLW1vZGVsLXByb3h5YCBDTEkuXG4gKlxuICogUnVuIGJhcmUsIGl0IHNlcnZlczogYSBsb29wYmFjayBPcGVuQUktY29tcGF0aWJsZSBlbmRwb2ludCB0aGF0IGZyb250c1xuICogRGF0YWJyaWNrcyBNb2RlbCBTZXJ2aW5nIHdpdGggZnV6enkgbW9kZWwgbmFtZXMgYW5kIHBlci1yZXF1ZXN0IGF1dGguIGBjaGF0YFxuICogc3RhcnRzIHRoYXQgc2FtZSBwcm94eSBhbmQgaGFuZHMgb2ZmIHRvIGFuIG9mZi10aGUtc2hlbGYgdGVybWluYWwgY2xpZW50XG4gKiB3aXJlZCB0byBpdC4gYG1vZGVsc2AgbGlzdHMgdGhlIHJlc29sdmFibGUgZW5kcG9pbnRzOyBgcmVzb2x2ZWAgc2hvd3Mgd2hhdCBhXG4gKiBmdXp6eSBuYW1lIHNuYXBzIHRvLiBBdXRoIGNvbWVzIGZyb20gdGhlIHN0YW5kYXJkIERhdGFicmlja3MgU0RLIHJlc29sdXRpb25cbiAqIChlbnYgdmFycywgYC0tcHJvZmlsZWAsIG9yIGBkYXRhYnJpY2tzIGF1dGggbG9naW5gKS5cbiAqXG4gKiBTaGFyZWQgZmxhZ3MgKGAtLXByb2ZpbGVgLCBgLS13b3Jrc3BhY2UtaG9zdGAsIGAtLXRocmVzaG9sZGAsIOKApikgYXJlIGRlY2xhcmVkXG4gKiBvbiB0aGUgcm9vdCAqYW5kKiByZWRlY2xhcmVkIG9uIHN1YmNvbW1hbmRzIGZvciBoZWxwIGRpc2NvdmVyYWJpbGl0eS4gQ29tbWFuZGVyXG4gKiBwYXJrcyB0aGUgdmFsdWVzIG9uIHRoZSBwYXJlbnQgd2hlbiBib3RoIGRlY2xhcmUgdGhlIHNhbWUgZmxhZywgc28gZXZlcnlcbiAqIHN1YmNvbW1hbmQgYWN0aW9uIG11c3QgcmVhZCB7QGxpbmsgQ29tbWFuZC5vcHRzV2l0aEdsb2JhbHN9IC0gbG9jYWwgYG9wdHNgXG4gKiBhbG9uZSBtaXNzZXMgYC0tcHJvZmlsZWAgKGVudiBgREFUQUJSSUNLU19DT05GSUdfUFJPRklMRWAgc3RpbGwgd29ya2VkIGJlY2F1c2VcbiAqIHRoZSBTREsgcmVhZHMgdGhhdCB3aXRob3V0IHRoZSBDTEkgZmxhZykuXG4gKlxuICogQG1vZHVsZVxuICovXG5cbmltcG9ydCB7IHNwYXduIH0gZnJvbSBcIm5vZGU6Y2hpbGRfcHJvY2Vzc1wiO1xuaW1wb3J0IHR5cGUgeyBTZXJ2ZXIgfSBmcm9tIFwibm9kZTpodHRwXCI7XG5pbXBvcnQgeyBjbGFzc2lmeSwgdHlwZSBTZXJ2aW5nRW5kcG9pbnRTdW1tYXJ5IH0gZnJvbSBcIkBkYngtdG9vbHMvc2hhcmVkLW1vZGVsXCI7XG5pbXBvcnQgeyBDb21tYW5kLCBDb21tYW5kZXJFcnJvciB9IGZyb20gXCJjb21tYW5kZXJcIjtcblxuaW1wb3J0IHsgRGF0YWJyaWNrc0JhY2tlbmQsIHR5cGUgQmFja2VuZE9wdGlvbnMgfSBmcm9tIFwiLi9iYWNrZW5kXCI7XG5pbXBvcnQgeyBERUZBVUxUX0JJTkRfSE9TVCwgREVGQVVMVF9QT1JULCByZXNvbHZlUmV0cnlDb25maWcgfSBmcm9tIFwiLi9kZWZhdWx0c1wiO1xuaW1wb3J0IHsgc3RhcnRQcm94eVNlcnZlciB9IGZyb20gXCIuL3NlcnZlclwiO1xuXG4vKipcbiAqIERlZmF1bHQgdGVybWluYWwgY2xpZW50IGZvciBgY2hhdGAsIGxhdW5jaGVkIHZpYSBgYnVueGAuIE9wZW5IYXJuZXNzIGlzIGFuXG4gKiBPcGVuQUktY29tcGF0aWJsZSBUVUkgdGhhdCByZWFkcyB0aGUgZW5kcG9pbnQgc3RyYWlnaHQgZnJvbSB0aGUgZW52IHdlIGluamVjdFxuICogKGBMTE1fUFJPVklERVI9b3BlbmFpLWNvbXBhdGAgKyBgT1BFTkFJX0JBU0VfVVJMYCksIHNvIG5vIGNvbmZpZyBzdGVwIGlzXG4gKiBuZWVkZWQuIE92ZXJyaWRlIHdpdGggYC0tY2xpZW50YCAvIGBQUk9YWV9DSEFUX0NMSUVOVGAuXG4gKi9cbmNvbnN0IERFRkFVTFRfQ0hBVF9DTElFTlQgPSBcImJ1bnggQGFsaGF6bWlhaS9vcGVuaGFybmVzc1wiO1xuXG4vKiogU2hhcmVkIGAtLXByb2ZpbGVgIC8gYC0td29ya3NwYWNlLWhvc3RgIC8gYC0tdGhyZXNob2xkYCBmbGFncy4gKi9cbmludGVyZmFjZSBDb21tb25PcHRzIHtcbiAgcHJvZmlsZT86IHN0cmluZztcbiAgd29ya3NwYWNlSG9zdD86IHN0cmluZztcbiAgdGhyZXNob2xkPzogc3RyaW5nO1xufVxuXG4vKiogU2hhcmVkIGxpc3RlbiArIGF1dGggZmxhZ3MgZm9yIHRoZSBwcm94eS1zdGFydGluZyBjb21tYW5kcy4gKi9cbmludGVyZmFjZSBTZXJ2ZU9wdHMgZXh0ZW5kcyBDb21tb25PcHRzIHtcbiAgcG9ydDogc3RyaW5nO1xuICBob3N0OiBzdHJpbmc7XG4gIGFwaUtleT86IHN0cmluZztcbiAgLyoqXG4gICAqIENvbW1hbmRlcidzIG5lZ2F0YWJsZS1mbGFnIHZhbHVlIGZvciBgLS1uby1yZXRyeS00MjlgOiBgdHJ1ZWAgYnkgZGVmYXVsdCxcbiAgICogYGZhbHNlYCBvbmNlIHRoZSBmbGFnIGlzIHBhc3NlZC4gT2ZmIG1lYW5zIHJlbGF5IHVwc3RyZWFtIDQyOXMgdW5jaGFuZ2VkLlxuICAgKi9cbiAgcmV0cnk0Mjk6IGJvb2xlYW47XG59XG5cbi8qKiBNYXAgc2hhcmVkIENMSSBmbGFncyBvbnRvIHtAbGluayBCYWNrZW5kT3B0aW9uc30uICovXG5mdW5jdGlvbiBiYWNrZW5kT3B0aW9ucyhvcHRzOiBDb21tb25PcHRzKTogQmFja2VuZE9wdGlvbnMge1xuICByZXR1cm4ge1xuICAgIC4uLihvcHRzLnByb2ZpbGUgPyB7IHByb2ZpbGU6IG9wdHMucHJvZmlsZSB9IDoge30pLFxuICAgIC4uLihvcHRzLndvcmtzcGFjZUhvc3QgPyB7IGhvc3Q6IG9wdHMud29ya3NwYWNlSG9zdCB9IDoge30pLFxuICAgIC4uLihvcHRzLnRocmVzaG9sZCAhPT0gdW5kZWZpbmVkID8geyB0aHJlc2hvbGQ6IE51bWJlcihvcHRzLnRocmVzaG9sZCkgfSA6IHt9KSxcbiAgfTtcbn1cblxuLyoqXG4gKiBTdWJjb21tYW5kIG9wdGlvbnMgbWVyZ2VkIHdpdGggcGFyZW50IChyb290KSBvcHRpb25zLiBSZXF1aXJlZCBmb3Igc2hhcmVkXG4gKiBmbGFncyB0aGF0IGFyZSBkZWNsYXJlZCBvbiBib3RoIC0gQ29tbWFuZGVyIHN0b3JlcyB0aGVtIG9uIHRoZSBwYXJlbnQsIHNvXG4gKiB0aGUgYWN0aW9uJ3MgbG9jYWwgYG9wdHNgIGFsb25lIG9taXRzIGAtLXByb2ZpbGVgIC8gYC0td29ya3NwYWNlLWhvc3RgLlxuICovXG5mdW5jdGlvbiBnbG9iYWxPcHRzPFQ+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==