@dbx-tools/cli-model-proxy 0.3.25 → 0.3.27

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/README.md CHANGED
@@ -134,8 +134,10 @@ Use this when tests or local developer tools need a managed proxy lifecycle.
134
134
  Responses-only models (Codex) which are translated and posted to
135
135
  `/serving-endpoints/responses`.
136
136
  - Responses → `/serving-endpoints/responses` (OpenAI-family) or
137
- `/serving-endpoints/open-responses` (Claude/Gemini/…), body forwarded
138
- as-is.
137
+ `/serving-endpoints/open-responses` (Claude/Gemini/…). For Open
138
+ Responses the proxy strips non-`function` tools and rewrites prior-turn
139
+ `output_*` content parts to `input_*` (Claude/Gemini reject
140
+ `output_text` in `input`).
139
141
  5. JSON or SSE response bodies are piped back (with a chat↔Responses
140
142
  translation only when a chat client hit a Responses-only model).
141
143
 
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.25",
22
- "@dbx-tools/shared-model": "0.3.25",
23
- "@dbx-tools/shared-core": "0.3.25"
21
+ "@dbx-tools/model": "0.3.27",
22
+ "@dbx-tools/shared-core": "0.3.27",
23
+ "@dbx-tools/shared-model": "0.3.27"
24
24
  },
25
25
  "main": "index.ts",
26
26
  "license": "UNLICENSED",
27
27
  "publishConfig": {
28
28
  "access": "public"
29
29
  },
30
- "version": "0.3.25",
30
+ "version": "0.3.27",
31
31
  "types": "index.ts",
32
32
  "type": "module",
33
33
  "exports": {
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 } = openaiResponses;
41
+ const { chatToResponsesRequest, responseToChatCompletion, sanitizeOpenResponsesRequest } =
42
+ openaiResponses;
42
43
 
43
44
  const logger = log.logger("model-proxy/server");
44
45
 
@@ -300,18 +301,23 @@ async function proxyChatViaResponses(
300
301
  headers["content-type"] = "application/json";
301
302
  headers.accept = "application/json";
302
303
 
304
+ const upstreamUrl = backend.responsesUrl(modelId);
305
+ const forward = upstreamUrl.includes("/open-responses")
306
+ ? sanitizeOpenResponsesRequest(responses)
307
+ : responses;
308
+
303
309
  logger.info("proxy", {
304
310
  requested,
305
311
  resolved: modelId,
306
312
  matched,
307
- upstream: "responses",
313
+ upstream: upstreamUrl,
308
314
  stream: false,
309
315
  });
310
316
 
311
- const upstream = await fetch(backend.responsesUrl(modelId), {
317
+ const upstream = await fetch(upstreamUrl, {
312
318
  method: "POST",
313
319
  headers,
314
- body: JSON.stringify(responses),
320
+ body: JSON.stringify(forward),
315
321
  });
316
322
 
317
323
  if (!upstream.ok) {
@@ -329,6 +335,12 @@ async function proxyChatViaResponses(
329
335
  * `POST /v1/responses`: forward the Responses body to Databricks' native
330
336
  * Responses / Open Responses surface (model stays in the body; URL is
331
337
  * workspace-level). Streaming and non-streaming both pass through as-is.
338
+ *
339
+ * Open Responses (Claude/Gemini/…) only accepts `function` tools and
340
+ * `input_*` content part types, so Codex's built-in `web_search` /
341
+ * `local_shell` / … and prior-turn `output_text` parts are rewritten/
342
+ * stripped there. The OpenAI `/responses` path keeps them - GPT supports
343
+ * those shapes.
332
344
  */
333
345
  async function handleResponses(
334
346
  backend: DatabricksBackend,
@@ -345,24 +357,35 @@ async function handleResponses(
345
357
  const resolved = await backend.resolve(requested);
346
358
  body.model = resolved.modelId;
347
359
 
348
- const wantsStream = body.stream === true;
360
+ const upstreamUrl = backend.responsesUrl(resolved.modelId);
361
+ // Cross-provider Open Responses rejects non-function tools and `output_*`
362
+ // content parts in `input`; OpenAI `/responses` keeps Codex's native shapes.
363
+ const forward = upstreamUrl.includes("/open-responses")
364
+ ? sanitizeOpenResponsesRequest(body)
365
+ : body;
366
+
367
+ const wantsStream = forward.stream === true;
349
368
  const headers = await backend.authHeaders();
350
369
  headers["content-type"] = "application/json";
351
370
  headers.accept = wantsStream ? "text/event-stream" : "application/json";
352
371
 
353
- const upstreamUrl = backend.responsesUrl(resolved.modelId);
372
+ const stripped =
373
+ Array.isArray(body.tools) &&
374
+ (!Array.isArray(forward.tools) || forward.tools.length !== body.tools.length);
375
+
354
376
  logger.info("responses", {
355
377
  requested,
356
378
  resolved: resolved.modelId,
357
379
  matched: resolved.matched,
358
380
  upstream: upstreamUrl,
359
381
  stream: wantsStream,
382
+ ...(stripped ? { strippedNonFunctionTools: true } : {}),
360
383
  });
361
384
 
362
385
  const upstream = await fetch(upstreamUrl, {
363
386
  method: "POST",
364
387
  headers,
365
- body: JSON.stringify(body),
388
+ body: JSON.stringify(forward),
366
389
  });
367
390
 
368
391
  res.writeHead(upstream.status, {