@dbx-tools/cli-model-proxy 0.3.22 → 0.3.23

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/package.json CHANGED
@@ -18,16 +18,16 @@
18
18
  "@databricks/sdk-experimental": "^0.17.0",
19
19
  "commander": "^15.0.0",
20
20
  "tsx": "^4.23.0",
21
- "@dbx-tools/shared-core": "0.3.22",
22
- "@dbx-tools/model": "0.3.22",
23
- "@dbx-tools/shared-model": "0.3.22"
21
+ "@dbx-tools/model": "0.3.23",
22
+ "@dbx-tools/shared-core": "0.3.23",
23
+ "@dbx-tools/shared-model": "0.3.23"
24
24
  },
25
25
  "main": "index.ts",
26
26
  "license": "UNLICENSED",
27
27
  "publishConfig": {
28
28
  "access": "public"
29
29
  },
30
- "version": "0.3.22",
30
+ "version": "0.3.23",
31
31
  "types": "index.ts",
32
32
  "type": "module",
33
33
  "exports": {
package/src/cli.ts CHANGED
@@ -8,6 +8,13 @@
8
8
  * fuzzy name snaps to. Auth comes from the standard Databricks SDK resolution
9
9
  * (env vars, `--profile`, or `databricks auth login`).
10
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
+ *
11
18
  * @module
12
19
  */
13
20
 
@@ -51,6 +58,15 @@ function backendOptions(opts: CommonOpts): BackendOptions {
51
58
  };
52
59
  }
53
60
 
61
+ /**
62
+ * Subcommand options merged with parent (root) options. Required for shared
63
+ * flags that are declared on both - Commander stores them on the parent, so
64
+ * the action's local `opts` alone omits `--profile` / `--workspace-host`.
65
+ */
66
+ function globalOpts<T>(command: Command): T {
67
+ return command.optsWithGlobals() as T;
68
+ }
69
+
54
70
  /** Create the backend and start the proxy from the shared listen flags. */
55
71
  async function startProxy(
56
72
  opts: ServeOpts,
@@ -65,102 +81,106 @@ async function startProxy(
65
81
  return { backend, server, url };
66
82
  }
67
83
 
84
+ /** Shared auth / fuzzy-match flags added to the root and every subcommand. */
85
+ function addAuthOptions(command: Command): Command {
86
+ return command
87
+ .option("--profile <profile>", "Databricks config profile")
88
+ .option("--workspace-host <url>", "override the Databricks workspace host")
89
+ .option("-t, --threshold <n>", "fuzzy match threshold (0..1)");
90
+ }
91
+
68
92
  /** Build the `dbx-tools-model-proxy` commander program (no side effects until parsed). */
69
93
  export function buildProgram(): Command {
70
94
  // Serving is the root action, not a subcommand: the bare command runs the
71
95
  // proxy, and `chat`/`models`/`resolve` are the named detours off it.
72
- const program = new Command()
73
- .name("dbx-tools-model-proxy")
74
- .description("Local OpenAI-compatible proxy to Databricks Model Serving.")
75
- .option("-p, --port <port>", "port to listen on", String(DEFAULT_PORT))
76
- .option("-H, --host <host>", "address to bind", DEFAULT_BIND_HOST)
77
- .option("--profile <profile>", "Databricks config profile")
78
- .option("--workspace-host <url>", "override the Databricks workspace host")
79
- .option("-t, --threshold <n>", "fuzzy match threshold (0..1)")
80
- .option("-k, --api-key <key>", "require this bearer token from local clients")
81
- .action(async (opts: ServeOpts) => {
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
- });
96
+ const program = addAuthOptions(
97
+ new Command()
98
+ .name("dbx-tools-model-proxy")
99
+ .description("Local OpenAI-compatible proxy to Databricks Model Serving.")
100
+ .option("-p, --port <port>", "port to listen on", String(DEFAULT_PORT))
101
+ .option("-H, --host <host>", "address to bind", DEFAULT_BIND_HOST)
102
+ .option("-k, --api-key <key>", "require this bearer token from local clients"),
103
+ ).action(async (opts: ServeOpts) => {
104
+ const { backend, url } = await startProxy(opts);
105
+ process.stderr.write(`model-proxy -> ${backend.host}\n`);
106
+ process.stderr.write(` OpenAI base URL: ${url}/v1\n`);
107
+ });
86
108
 
87
- program
88
- .command("chat")
89
- .description("Start the proxy and launch a terminal chat client wired to it.")
90
- .option("-p, --port <port>", "proxy port", String(DEFAULT_PORT))
91
- .option("-H, --host <host>", "proxy bind host", DEFAULT_BIND_HOST)
92
- .option("--profile <profile>", "Databricks config profile")
93
- .option("--workspace-host <url>", "override the Databricks workspace host")
94
- .option("-t, --threshold <n>", "fuzzy match threshold (0..1)")
95
- .option("-m, --model <name>", "default model (fuzzy name ok)")
96
- .option(
97
- "--client <cmd>",
98
- "terminal chat CLI to launch (run via your shell)",
99
- process.env.PROXY_CHAT_CLIENT ?? DEFAULT_CHAT_CLIENT,
100
- )
101
- .action(async (opts: ServeOpts & { model?: string; client: string }) => {
102
- const { backend, server, url } = await startProxy(opts);
103
- const baseUrl = `${url}/v1`;
104
- process.stderr.write(
105
- `model-proxy -> ${backend.host}\n OpenAI base URL: ${baseUrl}\n launching: ${opts.client}\n`,
106
- );
107
- // Hand off to an off-the-shelf OpenAI-compatible client, pointing it at
108
- // the proxy via the standard env vars (plus the provider switches a
109
- // couple of popular CLIs read). `shell: true` lets `--client` carry its
110
- // own args, e.g. `--client "bunx merlion"`.
111
- const child = spawn(opts.client, {
112
- stdio: "inherit",
113
- shell: true,
114
- env: {
115
- ...process.env,
116
- OPENAI_BASE_URL: baseUrl,
117
- OPENAI_API_KEY: process.env.OPENAI_API_KEY ?? "model-proxy",
118
- ...(opts.model ? { OPENAI_MODEL: opts.model } : {}),
119
- LLM_PROVIDER: "openai-compat",
120
- CLAUDE_CODE_USE_OPENAI: "1",
121
- },
122
- });
123
- child.on("exit", (code) => {
124
- server.close();
125
- process.exit(code ?? 0);
126
- });
127
- process.on("SIGINT", () => child.kill("SIGINT"));
109
+ addAuthOptions(
110
+ program
111
+ .command("chat")
112
+ .description("Start the proxy and launch a terminal chat client wired to it.")
113
+ .option("-p, --port <port>", "proxy port", String(DEFAULT_PORT))
114
+ .option("-H, --host <host>", "proxy bind host", DEFAULT_BIND_HOST)
115
+ .option("-m, --model <name>", "default model (fuzzy name ok)")
116
+ .option(
117
+ "--client <cmd>",
118
+ "terminal chat CLI to launch (run via your shell)",
119
+ process.env.PROXY_CHAT_CLIENT ?? DEFAULT_CHAT_CLIENT,
120
+ ),
121
+ ).action(async (_local: ServeOpts & { model?: string; client: string }, command: Command) => {
122
+ const opts = globalOpts<ServeOpts & { model?: string; client: string }>(command);
123
+ const { backend, server, url } = await startProxy(opts);
124
+ const baseUrl = `${url}/v1`;
125
+ process.stderr.write(
126
+ `model-proxy -> ${backend.host}\n OpenAI base URL: ${baseUrl}\n launching: ${opts.client}\n`,
127
+ );
128
+ // Hand off to an off-the-shelf OpenAI-compatible client, pointing it at
129
+ // the proxy via the standard env vars (plus the provider switches a
130
+ // couple of popular CLIs read). `shell: true` lets `--client` carry its
131
+ // own args, e.g. `--client "bunx merlion"`.
132
+ const child = spawn(opts.client, {
133
+ stdio: "inherit",
134
+ shell: true,
135
+ env: {
136
+ ...process.env,
137
+ OPENAI_BASE_URL: baseUrl,
138
+ OPENAI_API_KEY: process.env.OPENAI_API_KEY ?? "model-proxy",
139
+ ...(opts.model ? { OPENAI_MODEL: opts.model } : {}),
140
+ LLM_PROVIDER: "openai-compat",
141
+ CLAUDE_CODE_USE_OPENAI: "1",
142
+ },
128
143
  });
129
-
130
- program
131
- .command("models")
132
- .description("List resolvable Databricks serving endpoints (as JSON).")
133
- .option("--profile <profile>", "Databricks config profile")
134
- .option("--workspace-host <url>", "override the Databricks workspace host")
135
- .option("--chat", "only list chat-capable endpoints")
136
- .action(async (opts: CommonOpts & { chat?: boolean }) => {
137
- const backend = await DatabricksBackend.create(backendOptions(opts));
138
- const endpoints = await backend.models();
139
- // Enrich each endpoint with a derived `capabilities` object so consumers
140
- // filter on capability rather than re-deriving it from raw `task`/`class`
141
- // strings. `chat` = OpenAI chat/completions + Responses (what an agent
142
- // like Codex needs); `embedding` = vector endpoints; `tools` = whether the
143
- // endpoint supports function/tool calls (all chat endpoints here do).
144
- const enriched = endpoints.map((endpoint) => ({
145
- ...endpoint,
146
- capabilities: classify.endpointCapabilities(endpoint),
147
- }));
148
- const out = opts.chat ? enriched.filter((e) => e.capabilities.chat) : enriched;
149
- process.stdout.write(`${JSON.stringify(out, null, 2)}\n`);
144
+ child.on("exit", (code) => {
145
+ server.close();
146
+ process.exit(code ?? 0);
150
147
  });
148
+ process.on("SIGINT", () => child.kill("SIGINT"));
149
+ });
151
150
 
152
- program
153
- .command("resolve")
154
- .description("Show what a fuzzy model name resolves to (as JSON).")
155
- .argument("<query...>", "model name / fuzzy search terms")
156
- .option("--profile <profile>", "Databricks config profile")
157
- .option("--workspace-host <url>", "override the Databricks workspace host")
158
- .option("-t, --threshold <n>", "fuzzy match threshold (0..1)")
159
- .action(async (query: string[], opts: CommonOpts) => {
160
- const backend = await DatabricksBackend.create(backendOptions(opts));
161
- const resolved = await backend.resolve(query.join(" "));
162
- process.stdout.write(`${JSON.stringify(resolved, null, 2)}\n`);
163
- });
151
+ addAuthOptions(
152
+ program
153
+ .command("models")
154
+ .description("List resolvable Databricks serving endpoints (as JSON).")
155
+ .option("--chat", "only list chat-capable endpoints"),
156
+ ).action(async (_local: CommonOpts & { chat?: boolean }, command: Command) => {
157
+ const opts = globalOpts<CommonOpts & { chat?: boolean }>(command);
158
+ const backend = await DatabricksBackend.create(backendOptions(opts));
159
+ const endpoints = await backend.models();
160
+ // Enrich each endpoint with a derived `capabilities` object so consumers
161
+ // filter on capability rather than re-deriving it from raw `task`/`class`
162
+ // strings. `chat` = OpenAI chat/completions + Responses (what an agent
163
+ // like Codex needs); `embedding` = vector endpoints; `tools` = whether the
164
+ // endpoint supports function/tool calls (all chat endpoints here do).
165
+ const enriched = endpoints.map((endpoint) => ({
166
+ ...endpoint,
167
+ capabilities: classify.endpointCapabilities(endpoint),
168
+ }));
169
+ const out = opts.chat ? enriched.filter((e) => e.capabilities.chat) : enriched;
170
+ process.stdout.write(`${JSON.stringify(out, null, 2)}\n`);
171
+ });
172
+
173
+ addAuthOptions(
174
+ program
175
+ .command("resolve")
176
+ .description("Show what a fuzzy model name resolves to (as JSON).")
177
+ .argument("<query...>", "model name / fuzzy search terms"),
178
+ ).action(async (query: string[], _local: CommonOpts, command: Command) => {
179
+ const opts = globalOpts<CommonOpts>(command);
180
+ const backend = await DatabricksBackend.create(backendOptions(opts));
181
+ const resolved = await backend.resolve(query.join(" "));
182
+ process.stdout.write(`${JSON.stringify(resolved, null, 2)}\n`);
183
+ });
164
184
 
165
185
  return program;
166
186
  }
@@ -0,0 +1,14 @@
1
+ // ~~ Generated by projen. To modify, edit .projenrc.js and run "pnpm exec projen".
2
+ {
3
+ "extends": "../tsconfig.json",
4
+ "compilerOptions": {
5
+ "noEmit": true,
6
+ "rootDir": ".."
7
+ },
8
+ "include": [
9
+ "**/*.ts"
10
+ ],
11
+ "exclude": [
12
+ "node_modules"
13
+ ]
14
+ }