@dbx-tools/cli-model-proxy 0.3.24 → 0.3.26
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 +24 -12
- package/src/server.ts +20 -4
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/model": "0.3.
|
|
22
|
-
"@dbx-tools/shared-
|
|
23
|
-
"@dbx-tools/shared-
|
|
21
|
+
"@dbx-tools/model": "0.3.26",
|
|
22
|
+
"@dbx-tools/shared-core": "0.3.26",
|
|
23
|
+
"@dbx-tools/shared-model": "0.3.26"
|
|
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.26",
|
|
31
31
|
"types": "index.ts",
|
|
32
32
|
"type": "module",
|
|
33
33
|
"exports": {
|
package/src/cli.ts
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
|
|
21
21
|
import { spawn } from "node:child_process";
|
|
22
22
|
import type { Server } from "node:http";
|
|
23
|
-
import { classify } from "@dbx-tools/shared-model";
|
|
23
|
+
import { classify, type ServingEndpointSummary } from "@dbx-tools/shared-model";
|
|
24
24
|
import { Command, CommanderError } from "commander";
|
|
25
25
|
|
|
26
26
|
import { DatabricksBackend, type BackendOptions } from "./backend";
|
|
@@ -157,15 +157,7 @@ export function buildProgram(): Command {
|
|
|
157
157
|
const opts = globalOpts<CommonOpts & { chat?: boolean }>(command);
|
|
158
158
|
const backend = await DatabricksBackend.create(backendOptions(opts));
|
|
159
159
|
const endpoints = await backend.models();
|
|
160
|
-
|
|
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
|
-
}));
|
|
160
|
+
const enriched = endpoints.map(enrichEndpoint);
|
|
169
161
|
const out = opts.chat ? enriched.filter((e) => e.capabilities.chat) : enriched;
|
|
170
162
|
process.stdout.write(`${JSON.stringify(out, null, 2)}\n`);
|
|
171
163
|
});
|
|
@@ -178,13 +170,33 @@ export function buildProgram(): Command {
|
|
|
178
170
|
).action(async (query: string[], _local: CommonOpts, command: Command) => {
|
|
179
171
|
const opts = globalOpts<CommonOpts>(command);
|
|
180
172
|
const backend = await DatabricksBackend.create(backendOptions(opts));
|
|
181
|
-
const
|
|
182
|
-
|
|
173
|
+
const search = query.join(" ");
|
|
174
|
+
const resolved = await backend.resolve(search);
|
|
175
|
+
// Same shape as one `models` entry when matched; null when nothing in the
|
|
176
|
+
// catalogue scores within the threshold (caller can fall back to the query).
|
|
177
|
+
if (!resolved.matched) {
|
|
178
|
+
process.stdout.write("null\n");
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
const endpoint = (await backend.models()).find((e) => e.name === resolved.modelId);
|
|
182
|
+
process.stdout.write(
|
|
183
|
+
`${JSON.stringify(endpoint ? enrichEndpoint(endpoint) : null, null, 2)}\n`,
|
|
184
|
+
);
|
|
183
185
|
});
|
|
184
186
|
|
|
185
187
|
return program;
|
|
186
188
|
}
|
|
187
189
|
|
|
190
|
+
/** One `models` / `resolve` list entry: endpoint summary + derived capabilities. */
|
|
191
|
+
function enrichEndpoint(endpoint: ServingEndpointSummary) {
|
|
192
|
+
return {
|
|
193
|
+
...endpoint,
|
|
194
|
+
// `chat` = OpenAI chat/completions + Responses; `embedding` = vectors;
|
|
195
|
+
// `tools` = function/tool calls (every chat endpoint here).
|
|
196
|
+
capabilities: classify.endpointCapabilities(endpoint),
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
188
200
|
/** Parse `argv` and run the matching command. Throws {@link CommanderError} on flag errors. */
|
|
189
201
|
export async function runCli(argv: string[]): Promise<void> {
|
|
190
202
|
await buildProgram().parseAsync(argv);
|
package/src/server.ts
CHANGED
|
@@ -38,7 +38,8 @@ import { classify, openaiChat, openaiResponses } from "@dbx-tools/shared-model";
|
|
|
38
38
|
import type { DatabricksBackend } from "./backend";
|
|
39
39
|
import { DEFAULT_BIND_HOST, DEFAULT_PORT } from "./defaults";
|
|
40
40
|
|
|
41
|
-
const { chatToResponsesRequest, responseToChatCompletion } =
|
|
41
|
+
const { chatToResponsesRequest, responseToChatCompletion, sanitizeResponsesTools } =
|
|
42
|
+
openaiResponses;
|
|
42
43
|
|
|
43
44
|
const logger = log.logger("model-proxy/server");
|
|
44
45
|
|
|
@@ -329,6 +330,10 @@ async function proxyChatViaResponses(
|
|
|
329
330
|
* `POST /v1/responses`: forward the Responses body to Databricks' native
|
|
330
331
|
* Responses / Open Responses surface (model stays in the body; URL is
|
|
331
332
|
* workspace-level). Streaming and non-streaming both pass through as-is.
|
|
333
|
+
*
|
|
334
|
+
* Open Responses (Claude/Gemini/…) only accepts `function` tools, so Codex's
|
|
335
|
+
* built-in `web_search` / `local_shell` / … are stripped there. The OpenAI
|
|
336
|
+
* `/responses` path keeps them - GPT models support those tool types.
|
|
332
337
|
*/
|
|
333
338
|
async function handleResponses(
|
|
334
339
|
backend: DatabricksBackend,
|
|
@@ -345,24 +350,35 @@ async function handleResponses(
|
|
|
345
350
|
const resolved = await backend.resolve(requested);
|
|
346
351
|
body.model = resolved.modelId;
|
|
347
352
|
|
|
348
|
-
const
|
|
353
|
+
const upstreamUrl = backend.responsesUrl(resolved.modelId);
|
|
354
|
+
// Cross-provider Open Responses rejects non-function tool types; OpenAI
|
|
355
|
+
// `/responses` keeps Codex built-ins (web_search, local_shell, custom, …).
|
|
356
|
+
const forward = upstreamUrl.includes("/open-responses")
|
|
357
|
+
? sanitizeResponsesTools(body)
|
|
358
|
+
: body;
|
|
359
|
+
|
|
360
|
+
const wantsStream = forward.stream === true;
|
|
349
361
|
const headers = await backend.authHeaders();
|
|
350
362
|
headers["content-type"] = "application/json";
|
|
351
363
|
headers.accept = wantsStream ? "text/event-stream" : "application/json";
|
|
352
364
|
|
|
353
|
-
const
|
|
365
|
+
const stripped =
|
|
366
|
+
Array.isArray(body.tools) &&
|
|
367
|
+
(!Array.isArray(forward.tools) || forward.tools.length !== body.tools.length);
|
|
368
|
+
|
|
354
369
|
logger.info("responses", {
|
|
355
370
|
requested,
|
|
356
371
|
resolved: resolved.modelId,
|
|
357
372
|
matched: resolved.matched,
|
|
358
373
|
upstream: upstreamUrl,
|
|
359
374
|
stream: wantsStream,
|
|
375
|
+
...(stripped ? { strippedNonFunctionTools: true } : {}),
|
|
360
376
|
});
|
|
361
377
|
|
|
362
378
|
const upstream = await fetch(upstreamUrl, {
|
|
363
379
|
method: "POST",
|
|
364
380
|
headers,
|
|
365
|
-
body: JSON.stringify(
|
|
381
|
+
body: JSON.stringify(forward),
|
|
366
382
|
});
|
|
367
383
|
|
|
368
384
|
res.writeHead(upstream.status, {
|