@dbx-tools/shared-model 0.3.23 → 0.3.24

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
@@ -12,14 +12,14 @@
12
12
  },
13
13
  "dependencies": {
14
14
  "zod": "^4.3.6",
15
- "@dbx-tools/shared-core": "0.3.23"
15
+ "@dbx-tools/shared-core": "0.3.24"
16
16
  },
17
17
  "main": "index.ts",
18
18
  "license": "UNLICENSED",
19
19
  "publishConfig": {
20
20
  "access": "public"
21
21
  },
22
- "version": "0.3.23",
22
+ "version": "0.3.24",
23
23
  "types": "index.ts",
24
24
  "type": "module",
25
25
  "exports": {
@@ -2,16 +2,22 @@
2
2
  * OpenAI Responses API <-> Chat Completions translation.
3
3
  *
4
4
  * Some clients speak only the Responses API (`POST /v1/responses`) - the Codex
5
- * CLI is the motivating one - while Databricks serving endpoints speak only
6
- * Chat Completions (`messages`) at their `invocations` URL. This module bridges
7
- * the two, in both directions:
5
+ * CLI is the motivating one. Databricks now also exposes native Responses
6
+ * surfaces (`/serving-endpoints/responses` and `/serving-endpoints/open-responses`),
7
+ * while most endpoints still speak Chat Completions at `/invocations`. This
8
+ * module bridges the two, in both directions:
8
9
  *
9
10
  * - {@link responsesToChat} lowers a Responses request body to a chat
10
11
  * completions body (instructions -> system message, typed `input` items ->
11
12
  * `messages`, `tools`/`tool_choice` carried through, function-call outputs
12
13
  * folded back into the transcript).
14
+ * - {@link chatToResponsesRequest} raises a chat-completions request into a
15
+ * Responses request (the inverse of {@link responsesToChat}), for routing a
16
+ * chat client at a Responses-only model like Codex.
13
17
  * - {@link chatToResponse} lifts a non-streaming chat completion back into a
14
18
  * Responses `response` object.
19
+ * - {@link responseToChatCompletion} lifts a native Responses `response`
20
+ * back into a chat-completions body (the inverse of {@link chatToResponse}).
15
21
  * - {@link createResponsesStreamTranslator} lifts a streaming chat completion
16
22
  * (an OpenAI SSE `chat.completion.chunk` stream) into the Responses SSE
17
23
  * event stream those clients consume (`response.created`,
@@ -139,6 +145,110 @@ export function responsesToChat(body: Record<string, unknown>): {
139
145
  return { chat, stream: body.stream === true };
140
146
  }
141
147
 
148
+ /**
149
+ * Raise a chat-completions request into a Responses request body. Inverse of
150
+ * {@link responsesToChat}: used when a chat client hits a Responses-only
151
+ * Databricks model (e.g. Codex) so the proxy can POST to
152
+ * `/serving-endpoints/responses` without the client knowing.
153
+ */
154
+ export function chatToResponsesRequest(body: Record<string, unknown>): {
155
+ responses: Record<string, unknown>;
156
+ stream: boolean;
157
+ } {
158
+ const messages = Array.isArray(body.messages) ? body.messages : [];
159
+ const input: unknown[] = [];
160
+ let instructions: string | undefined;
161
+
162
+ for (const raw of messages) {
163
+ if (!raw || typeof raw !== "object") continue;
164
+ const message = raw as Record<string, unknown>;
165
+ const role = typeof message.role === "string" ? message.role : "user";
166
+
167
+ // Leading system / developer messages become top-level `instructions`.
168
+ if ((role === "system" || role === "developer") && instructions === undefined && input.length === 0) {
169
+ const text = chatContentToText(message.content);
170
+ if (text) instructions = text;
171
+ continue;
172
+ }
173
+
174
+ if (role === "tool") {
175
+ input.push({
176
+ type: "function_call_output",
177
+ call_id: String(message.tool_call_id ?? ""),
178
+ output: chatContentToText(message.content),
179
+ });
180
+ continue;
181
+ }
182
+
183
+ if (role === "assistant" && Array.isArray(message.tool_calls) && message.tool_calls.length > 0) {
184
+ for (const rawCall of message.tool_calls) {
185
+ if (!rawCall || typeof rawCall !== "object") continue;
186
+ const call = rawCall as Record<string, unknown>;
187
+ const fn = (call.function ?? {}) as Record<string, unknown>;
188
+ input.push({
189
+ type: "function_call",
190
+ call_id: String(call.id ?? ""),
191
+ name: String(fn.name ?? ""),
192
+ arguments: typeof fn.arguments === "string" ? fn.arguments : "{}",
193
+ });
194
+ }
195
+ const text = chatContentToText(message.content);
196
+ if (text) {
197
+ input.push({ type: "message", role: "assistant", content: text });
198
+ }
199
+ continue;
200
+ }
201
+
202
+ input.push({
203
+ type: "message",
204
+ role,
205
+ content: chatContentToText(message.content),
206
+ });
207
+ }
208
+
209
+ const responses: Record<string, unknown> = {
210
+ model: body.model,
211
+ input,
212
+ stream: body.stream === true,
213
+ };
214
+ if (instructions) responses.instructions = instructions;
215
+
216
+ // Chat tools are nested (`function: { name, … }`); Responses wants them flat.
217
+ if (Array.isArray(body.tools) && body.tools.length > 0) {
218
+ const tools = body.tools
219
+ .map((t) => {
220
+ if (!t || typeof t !== "object") return undefined;
221
+ const tool = t as Record<string, unknown>;
222
+ if (tool.type !== "function") return undefined;
223
+ if (tool.function && typeof tool.function === "object") {
224
+ const fn = tool.function as Record<string, unknown>;
225
+ return {
226
+ type: "function",
227
+ name: fn.name,
228
+ ...(fn.description ? { description: fn.description } : {}),
229
+ ...(fn.parameters ? { parameters: fn.parameters } : {}),
230
+ };
231
+ }
232
+ if (typeof tool.name === "string") return tool;
233
+ return undefined;
234
+ })
235
+ .filter((t): t is Record<string, unknown> => t !== undefined);
236
+ if (tools.length > 0) {
237
+ responses.tools = tools;
238
+ if (body.tool_choice !== undefined) responses.tool_choice = body.tool_choice;
239
+ }
240
+ }
241
+
242
+ // Prefer Responses' max_output_tokens naming; fall back from chat max_tokens.
243
+ if (typeof body.max_output_tokens === "number") {
244
+ responses.max_output_tokens = body.max_output_tokens;
245
+ } else if (typeof body.max_tokens === "number") {
246
+ responses.max_output_tokens = body.max_tokens;
247
+ }
248
+
249
+ return { responses, stream: body.stream === true };
250
+ }
251
+
142
252
  /** Lift a non-streaming chat completion JSON into a Responses `response` object. */
143
253
  export function chatToResponse(chat: Record<string, unknown>, model: string): unknown {
144
254
  const choices = Array.isArray(chat.choices) ? chat.choices : [];
@@ -184,6 +294,59 @@ export function chatToResponse(chat: Record<string, unknown>, model: string): un
184
294
  };
185
295
  }
186
296
 
297
+ /**
298
+ * Lift a native Responses `response` object into a chat-completions body.
299
+ * Inverse of {@link chatToResponse}: used after a Responses-only upstream call
300
+ * when the client spoke Chat Completions.
301
+ */
302
+ export function responseToChatCompletion(
303
+ response: Record<string, unknown>,
304
+ model: string,
305
+ ): Record<string, unknown> {
306
+ const { text } = readResponsesOutput(response);
307
+ const toolCalls: unknown[] = [];
308
+ const output = Array.isArray(response.output) ? response.output : [];
309
+ for (const raw of output) {
310
+ if (!raw || typeof raw !== "object") continue;
311
+ const item = raw as Record<string, unknown>;
312
+ if (item.type !== "function_call") continue;
313
+ toolCalls.push({
314
+ id: String(item.call_id ?? item.id ?? ""),
315
+ type: "function",
316
+ function: {
317
+ name: String(item.name ?? ""),
318
+ arguments: typeof item.arguments === "string" ? item.arguments : "{}",
319
+ },
320
+ });
321
+ }
322
+
323
+ const message: Record<string, unknown> = {
324
+ role: "assistant",
325
+ content: text || null,
326
+ };
327
+ if (toolCalls.length > 0) message.tool_calls = toolCalls;
328
+
329
+ const usage = (response.usage ?? {}) as Record<string, unknown>;
330
+ return {
331
+ id: String(response.id ?? "chatcmpl"),
332
+ object: "chat.completion",
333
+ created: typeof response.created_at === "number" ? response.created_at : 0,
334
+ model,
335
+ choices: [
336
+ {
337
+ index: 0,
338
+ message,
339
+ finish_reason: toolCalls.length > 0 ? "tool_calls" : "stop",
340
+ },
341
+ ],
342
+ usage: {
343
+ prompt_tokens: Number(usage.input_tokens ?? 0),
344
+ completion_tokens: Number(usage.output_tokens ?? 0),
345
+ total_tokens: Number(usage.total_tokens ?? 0),
346
+ },
347
+ };
348
+ }
349
+
187
350
  /** One Server-Sent Event, ready to write to the client. */
188
351
  function sse(event: string, data: unknown): string {
189
352
  return `event: ${event}\ndata: ${JSON.stringify(data)}\n\n`;