@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 +4 -4
- package/src/cli.ts +108 -88
- package/test/tsconfig.json +14 -0
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/
|
|
22
|
-
"@dbx-tools/
|
|
23
|
-
"@dbx-tools/shared-model": "0.3.
|
|
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.
|
|
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 =
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
.
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
)
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
env
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
131
|
-
|
|
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
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
.
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
+
}
|