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

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
@@ -135,9 +135,10 @@ Use this when tests or local developer tools need a managed proxy lifecycle.
135
135
  `/serving-endpoints/responses`.
136
136
  - Responses → `/serving-endpoints/responses` (OpenAI-family) or
137
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`).
138
+ Responses the proxy strips non-`function` tools, rewrites prior-turn
139
+ `output_*` content parts to `input_*`, and drops Claude
140
+ `thinking` / `redacted_thinking` / `reasoning` blocks (replay of those
141
+ signed blobs fails with "Invalid `data` in `redacted_thinking`").
141
142
  5. JSON or SSE response bodies are piped back (with a chat↔Responses
142
143
  translation only when a chat client hit a Responses-only model).
143
144
 
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.27",
22
- "@dbx-tools/shared-core": "0.3.27",
23
- "@dbx-tools/shared-model": "0.3.27"
21
+ "@dbx-tools/shared-core": "0.3.29",
22
+ "@dbx-tools/shared-model": "0.3.29",
23
+ "@dbx-tools/model": "0.3.29"
24
24
  },
25
25
  "main": "index.ts",
26
26
  "license": "UNLICENSED",
27
27
  "publishConfig": {
28
28
  "access": "public"
29
29
  },
30
- "version": "0.3.27",
30
+ "version": "0.3.29",
31
31
  "types": "index.ts",
32
32
  "type": "module",
33
33
  "exports": {
package/src/server.ts CHANGED
@@ -32,7 +32,7 @@
32
32
  */
33
33
 
34
34
  import { createServer, type IncomingMessage, type Server, type ServerResponse } from "node:http";
35
- import { error, log } from "@dbx-tools/shared-core";
35
+ import { error, json, log } from "@dbx-tools/shared-core";
36
36
  import { classify, openaiChat, openaiResponses } from "@dbx-tools/shared-model";
37
37
 
38
38
  import type { DatabricksBackend } from "./backend";
@@ -222,7 +222,7 @@ async function handleProxy(
222
222
  req: IncomingMessage,
223
223
  res: ServerResponse,
224
224
  ): Promise<void> {
225
- const body = (await readJsonBody(req)) as Record<string, unknown>;
225
+ const body = await readJsonBody(req);
226
226
  const requested = typeof body.model === "string" ? body.model : undefined;
227
227
  if (!requested) {
228
228
  sendJson(res, 400, errorBody("missing 'model' in request body", "invalid_request_error"));
@@ -337,8 +337,9 @@ async function proxyChatViaResponses(
337
337
  * workspace-level). Streaming and non-streaming both pass through as-is.
338
338
  *
339
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/
340
+ * `input_*` content part types, and rejects replayed Claude thinking
341
+ * blocks so Codex's built-in tools, prior-turn `output_text`, and
342
+ * `redacted_thinking` / `thinking` / `reasoning` parts are rewritten or
342
343
  * stripped there. The OpenAI `/responses` path keeps them - GPT supports
343
344
  * those shapes.
344
345
  */
@@ -347,7 +348,7 @@ async function handleResponses(
347
348
  req: IncomingMessage,
348
349
  res: ServerResponse,
349
350
  ): Promise<void> {
350
- const body = (await readJsonBody(req)) as Record<string, unknown>;
351
+ const body = await readJsonBody(req);
351
352
  const requested = typeof body.model === "string" ? body.model : undefined;
352
353
  if (!requested) {
353
354
  sendJson(res, 400, errorBody("missing 'model' in request body", "invalid_request_error"));
@@ -423,14 +424,16 @@ function isAuthorized(req: IncomingMessage, apiKey: string): boolean {
423
424
  return header.replace(/^Bearer\s+/i, "").trim() === apiKey;
424
425
  }
425
426
 
426
- /** Drain a request body and parse it as JSON (empty body parses to `{}`). */
427
- async function readJsonBody(req: IncomingMessage): Promise<unknown> {
427
+ /**
428
+ * Drain a request body and parse it as a JSON object. An empty, malformed, or
429
+ * non-object body yields `{}`, which the callers reject as a missing `model`.
430
+ */
431
+ async function readJsonBody(req: IncomingMessage): Promise<Record<string, unknown>> {
428
432
  const chunks: Buffer[] = [];
429
433
  for await (const chunk of req) {
430
434
  chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk as Uint8Array));
431
435
  }
432
- const raw = Buffer.concat(chunks).toString("utf8");
433
- return raw ? JSON.parse(raw) : {};
436
+ return json.parseRecord(Buffer.concat(chunks).toString("utf8")) ?? {};
434
437
  }
435
438
 
436
439
  /** Serialize and send a JSON response. */