@dbx-tools/appkit-model-gateway 0.9.99 → 0.9.105

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.
@@ -1,515 +0,0 @@
1
- /**
2
- * Streaming and buffered protocol encoders for AI SDK fallback results.
3
- *
4
- * @module
5
- */
6
-
7
- import type { ClientProtocol } from "@dbx-tools/shared-model-gateway";
8
- import type { FinishReason, LanguageModelUsage, TextStreamPart, ToolSet, TypedToolCall } from "ai";
9
-
10
- /** Completed AI SDK generation data used by non-streaming encoders. */
11
- export interface CompletedGeneration {
12
- readonly text: string;
13
- readonly toolCalls: readonly TypedToolCall<ToolSet>[];
14
- readonly usage: LanguageModelUsage;
15
- readonly finishReason: FinishReason;
16
- }
17
-
18
- /** Encode AI SDK events as the requested protocol while preserving backpressure. */
19
- export function encodeGatewayStream(
20
- protocol: ClientProtocol,
21
- model: string,
22
- source: AsyncIterable<TextStreamPart<ToolSet>>,
23
- ): ReadableStream<Uint8Array> {
24
- const encoder = new TextEncoder();
25
- return new ReadableStream<Uint8Array>({
26
- async start(controller) {
27
- try {
28
- const events =
29
- protocol === "openai-responses"
30
- ? responsesEvents(model, source)
31
- : protocol === "anthropic-messages"
32
- ? anthropicEvents(model, source)
33
- : chatEvents(model, source);
34
- for await (const event of events) controller.enqueue(encoder.encode(event));
35
- controller.close();
36
- } catch (error) {
37
- controller.error(error);
38
- }
39
- },
40
- });
41
- }
42
-
43
- /** Encode a completed AI SDK generation as one protocol-native JSON response. */
44
- export function encodeGatewayResponse(
45
- protocol: ClientProtocol,
46
- model: string,
47
- generation: CompletedGeneration,
48
- ): Record<string, unknown> {
49
- if (protocol === "openai-responses") return responsesObject(model, generation);
50
- if (protocol === "anthropic-messages") return anthropicObject(model, generation);
51
- return chatObject(model, generation);
52
- }
53
-
54
- async function* responsesEvents(
55
- model: string,
56
- source: AsyncIterable<TextStreamPart<ToolSet>>,
57
- ): AsyncGenerator<string> {
58
- const responseId = `resp_${crypto.randomUUID().replaceAll("-", "")}`;
59
- const createdAt = Math.floor(Date.now() / 1000);
60
- const output: Record<string, unknown>[] = [];
61
- const response = {
62
- id: responseId,
63
- object: "response",
64
- created_at: createdAt,
65
- model,
66
- status: "in_progress",
67
- background: false,
68
- completed_at: null,
69
- error: null,
70
- incomplete_details: null,
71
- instructions: null,
72
- max_output_tokens: null,
73
- output,
74
- parallel_tool_calls: true,
75
- previous_response_id: null,
76
- reasoning: null,
77
- store: false,
78
- temperature: null,
79
- text: { format: { type: "text" } },
80
- tool_choice: "auto",
81
- tools: [],
82
- top_p: null,
83
- truncation: "disabled",
84
- usage: null,
85
- metadata: {},
86
- };
87
- let sequenceNumber = 0;
88
- const event = (name: string, payload: Record<string, unknown>) =>
89
- sse(name, { ...payload, sequence_number: sequenceNumber++ });
90
- yield event("response.created", { type: "response.created", response });
91
- yield event("response.in_progress", { type: "response.in_progress", response });
92
- let outputIndex = 0;
93
- let messageId: string | undefined;
94
- let textValue = "";
95
- const toolIndexes = new Map<string, number>();
96
- let usage: LanguageModelUsage | undefined;
97
-
98
- for await (const part of source) {
99
- if (part.type === "text-start") {
100
- messageId = `msg_${crypto.randomUUID().replaceAll("-", "")}`;
101
- yield event("response.output_item.added", {
102
- type: "response.output_item.added",
103
- output_index: outputIndex,
104
- item: {
105
- id: messageId,
106
- type: "message",
107
- role: "assistant",
108
- status: "in_progress",
109
- phase: "final_answer",
110
- content: [],
111
- },
112
- });
113
- yield event("response.content_part.added", {
114
- type: "response.content_part.added",
115
- item_id: messageId,
116
- output_index: outputIndex,
117
- content_index: 0,
118
- part: { type: "output_text", text: "", annotations: [], logprobs: [] },
119
- });
120
- } else if (part.type === "text-delta") {
121
- textValue += part.text;
122
- yield event("response.output_text.delta", {
123
- type: "response.output_text.delta",
124
- item_id: messageId,
125
- output_index: outputIndex,
126
- content_index: 0,
127
- delta: part.text,
128
- logprobs: [],
129
- });
130
- } else if (part.type === "text-end") {
131
- yield event("response.output_text.done", {
132
- type: "response.output_text.done",
133
- item_id: messageId,
134
- output_index: outputIndex,
135
- content_index: 0,
136
- text: textValue,
137
- logprobs: [],
138
- });
139
- yield event("response.content_part.done", {
140
- type: "response.content_part.done",
141
- item_id: messageId,
142
- output_index: outputIndex,
143
- content_index: 0,
144
- part: { type: "output_text", text: textValue, annotations: [], logprobs: [] },
145
- });
146
- const item = {
147
- id: messageId,
148
- type: "message",
149
- role: "assistant",
150
- status: "completed",
151
- phase: "final_answer",
152
- content: [{ type: "output_text", text: textValue, annotations: [], logprobs: [] }],
153
- };
154
- output.push(item);
155
- yield event("response.output_item.done", {
156
- type: "response.output_item.done",
157
- output_index: outputIndex,
158
- item,
159
- });
160
- outputIndex++;
161
- textValue = "";
162
- } else if (part.type === "reasoning-delta") {
163
- yield event("response.reasoning_summary_text.delta", {
164
- type: "response.reasoning_summary_text.delta",
165
- output_index: outputIndex,
166
- delta: part.text,
167
- });
168
- } else if (part.type === "tool-input-start") {
169
- toolIndexes.set(part.id, outputIndex);
170
- yield event("response.output_item.added", {
171
- type: "response.output_item.added",
172
- output_index: outputIndex,
173
- item: {
174
- id: `fc_${part.id}`,
175
- type: "function_call",
176
- call_id: part.id,
177
- name: part.toolName,
178
- arguments: "",
179
- status: "in_progress",
180
- },
181
- });
182
- outputIndex++;
183
- } else if (part.type === "tool-input-delta") {
184
- yield event("response.function_call_arguments.delta", {
185
- type: "response.function_call_arguments.delta",
186
- item_id: `fc_${part.id}`,
187
- output_index: toolIndexes.get(part.id) ?? outputIndex,
188
- delta: part.delta,
189
- });
190
- } else if (part.type === "tool-call") {
191
- const index = toolIndexes.get(part.toolCallId) ?? outputIndex++;
192
- const item = {
193
- id: `fc_${part.toolCallId}`,
194
- type: "function_call",
195
- call_id: part.toolCallId,
196
- name: part.toolName,
197
- arguments: JSON.stringify(part.input),
198
- status: "completed",
199
- };
200
- yield event("response.function_call_arguments.done", {
201
- type: "response.function_call_arguments.done",
202
- item_id: `fc_${part.toolCallId}`,
203
- output_index: index,
204
- arguments: JSON.stringify(part.input),
205
- });
206
- yield event("response.output_item.done", {
207
- type: "response.output_item.done",
208
- output_index: index,
209
- item,
210
- });
211
- output.push(item);
212
- } else if (part.type === "finish") {
213
- usage = part.totalUsage;
214
- } else if (part.type === "error") {
215
- yield event("error", protocolError(part.error));
216
- }
217
- }
218
- yield event("response.completed", {
219
- type: "response.completed",
220
- response: {
221
- ...response,
222
- status: "completed",
223
- completed_at: Math.floor(Date.now() / 1000),
224
- usage: responsesUsage(usage),
225
- },
226
- });
227
- }
228
-
229
- async function* chatEvents(
230
- model: string,
231
- source: AsyncIterable<TextStreamPart<ToolSet>>,
232
- ): AsyncGenerator<string> {
233
- const id = `chatcmpl-${crypto.randomUUID().replaceAll("-", "")}`;
234
- const created = Math.floor(Date.now() / 1000);
235
- yield data({
236
- id,
237
- object: "chat.completion.chunk",
238
- created,
239
- model,
240
- choices: [{ index: 0, delta: { role: "assistant", content: "" }, finish_reason: null }],
241
- });
242
- const toolIndexes = new Map<string, number>();
243
- let nextToolIndex = 0;
244
- for await (const part of source) {
245
- if (part.type === "text-delta") {
246
- yield data({
247
- id,
248
- object: "chat.completion.chunk",
249
- created,
250
- model,
251
- choices: [{ index: 0, delta: { content: part.text }, finish_reason: null }],
252
- });
253
- } else if (part.type === "tool-input-start") {
254
- const index = nextToolIndex++;
255
- toolIndexes.set(part.id, index);
256
- yield data({
257
- id,
258
- object: "chat.completion.chunk",
259
- created,
260
- model,
261
- choices: [
262
- {
263
- index: 0,
264
- delta: {
265
- tool_calls: [
266
- {
267
- index,
268
- id: part.id,
269
- type: "function",
270
- function: { name: part.toolName, arguments: "" },
271
- },
272
- ],
273
- },
274
- finish_reason: null,
275
- },
276
- ],
277
- });
278
- } else if (part.type === "tool-input-delta") {
279
- yield data({
280
- id,
281
- object: "chat.completion.chunk",
282
- created,
283
- model,
284
- choices: [
285
- {
286
- index: 0,
287
- delta: {
288
- tool_calls: [
289
- {
290
- index: toolIndexes.get(part.id) ?? 0,
291
- function: { arguments: part.delta },
292
- },
293
- ],
294
- },
295
- finish_reason: null,
296
- },
297
- ],
298
- });
299
- } else if (part.type === "finish") {
300
- yield data({
301
- id,
302
- object: "chat.completion.chunk",
303
- created,
304
- model,
305
- choices: [{ index: 0, delta: {}, finish_reason: chatFinishReason(part.finishReason) }],
306
- usage: chatUsage(part.totalUsage),
307
- });
308
- } else if (part.type === "error") {
309
- yield data(protocolError(part.error));
310
- }
311
- }
312
- yield "data: [DONE]\n\n";
313
- }
314
-
315
- async function* anthropicEvents(
316
- model: string,
317
- source: AsyncIterable<TextStreamPart<ToolSet>>,
318
- ): AsyncGenerator<string> {
319
- const id = `msg_${crypto.randomUUID().replaceAll("-", "")}`;
320
- yield sse("message_start", {
321
- type: "message_start",
322
- message: {
323
- id,
324
- type: "message",
325
- role: "assistant",
326
- model,
327
- content: [],
328
- stop_reason: null,
329
- stop_sequence: null,
330
- usage: { input_tokens: 0, output_tokens: 0 },
331
- },
332
- });
333
- let contentIndex = 0;
334
- const toolIndexes = new Map<string, number>();
335
- for await (const part of source) {
336
- if (part.type === "text-start") {
337
- yield sse("content_block_start", {
338
- type: "content_block_start",
339
- index: contentIndex,
340
- content_block: { type: "text", text: "" },
341
- });
342
- } else if (part.type === "text-delta") {
343
- yield sse("content_block_delta", {
344
- type: "content_block_delta",
345
- index: contentIndex,
346
- delta: { type: "text_delta", text: part.text },
347
- });
348
- } else if (part.type === "text-end") {
349
- yield sse("content_block_stop", { type: "content_block_stop", index: contentIndex++ });
350
- } else if (part.type === "tool-input-start") {
351
- toolIndexes.set(part.id, contentIndex);
352
- yield sse("content_block_start", {
353
- type: "content_block_start",
354
- index: contentIndex++,
355
- content_block: { type: "tool_use", id: part.id, name: part.toolName, input: {} },
356
- });
357
- } else if (part.type === "tool-input-delta") {
358
- yield sse("content_block_delta", {
359
- type: "content_block_delta",
360
- index: toolIndexes.get(part.id) ?? 0,
361
- delta: { type: "input_json_delta", partial_json: part.delta },
362
- });
363
- } else if (part.type === "tool-input-end") {
364
- yield sse("content_block_stop", {
365
- type: "content_block_stop",
366
- index: toolIndexes.get(part.id) ?? 0,
367
- });
368
- } else if (part.type === "finish") {
369
- yield sse("message_delta", {
370
- type: "message_delta",
371
- delta: { stop_reason: anthropicFinishReason(part.finishReason), stop_sequence: null },
372
- usage: { output_tokens: part.totalUsage.outputTokens ?? 0 },
373
- });
374
- } else if (part.type === "error") {
375
- yield sse("error", protocolError(part.error));
376
- }
377
- }
378
- yield sse("message_stop", { type: "message_stop" });
379
- }
380
-
381
- function responsesObject(model: string, generation: CompletedGeneration): Record<string, unknown> {
382
- const output: Record<string, unknown>[] = [];
383
- if (generation.text) {
384
- output.push({
385
- id: `msg_${crypto.randomUUID().replaceAll("-", "")}`,
386
- type: "message",
387
- role: "assistant",
388
- status: "completed",
389
- content: [{ type: "output_text", text: generation.text, annotations: [] }],
390
- });
391
- }
392
- for (const call of generation.toolCalls) {
393
- output.push({
394
- id: `fc_${call.toolCallId}`,
395
- type: "function_call",
396
- call_id: call.toolCallId,
397
- name: call.toolName,
398
- arguments: JSON.stringify(call.input),
399
- status: "completed",
400
- });
401
- }
402
- return {
403
- id: `resp_${crypto.randomUUID().replaceAll("-", "")}`,
404
- object: "response",
405
- created_at: Math.floor(Date.now() / 1000),
406
- status: "completed",
407
- model,
408
- output,
409
- usage: responsesUsage(generation.usage),
410
- };
411
- }
412
-
413
- function chatObject(model: string, generation: CompletedGeneration): Record<string, unknown> {
414
- return {
415
- id: `chatcmpl-${crypto.randomUUID().replaceAll("-", "")}`,
416
- object: "chat.completion",
417
- created: Math.floor(Date.now() / 1000),
418
- model,
419
- choices: [
420
- {
421
- index: 0,
422
- message: {
423
- role: "assistant",
424
- content: generation.text || null,
425
- ...(generation.toolCalls.length > 0
426
- ? {
427
- tool_calls: generation.toolCalls.map((call) => ({
428
- id: call.toolCallId,
429
- type: "function",
430
- function: { name: call.toolName, arguments: JSON.stringify(call.input) },
431
- })),
432
- }
433
- : {}),
434
- },
435
- finish_reason: chatFinishReason(generation.finishReason),
436
- },
437
- ],
438
- usage: chatUsage(generation.usage),
439
- };
440
- }
441
-
442
- function anthropicObject(model: string, generation: CompletedGeneration): Record<string, unknown> {
443
- return {
444
- id: `msg_${crypto.randomUUID().replaceAll("-", "")}`,
445
- type: "message",
446
- role: "assistant",
447
- model,
448
- content: [
449
- ...(generation.text ? [{ type: "text", text: generation.text }] : []),
450
- ...generation.toolCalls.map((call) => ({
451
- type: "tool_use",
452
- id: call.toolCallId,
453
- name: call.toolName,
454
- input: call.input,
455
- })),
456
- ],
457
- stop_reason: anthropicFinishReason(generation.finishReason),
458
- stop_sequence: null,
459
- usage: {
460
- input_tokens: generation.usage.inputTokens ?? 0,
461
- output_tokens: generation.usage.outputTokens ?? 0,
462
- },
463
- };
464
- }
465
-
466
- function responsesUsage(usage: LanguageModelUsage | undefined): Record<string, unknown> {
467
- const input = usage?.inputTokens ?? 0;
468
- const output = usage?.outputTokens ?? 0;
469
- return {
470
- input_tokens: input,
471
- output_tokens: output,
472
- total_tokens: input + output,
473
- input_tokens_details: { cached_tokens: usage?.inputTokenDetails.cacheReadTokens ?? 0 },
474
- output_tokens_details: {
475
- reasoning_tokens: usage?.outputTokenDetails.reasoningTokens ?? 0,
476
- },
477
- };
478
- }
479
-
480
- function chatUsage(usage: LanguageModelUsage): Record<string, number> {
481
- const input = usage.inputTokens ?? 0;
482
- const output = usage.outputTokens ?? 0;
483
- return { prompt_tokens: input, completion_tokens: output, total_tokens: input + output };
484
- }
485
-
486
- function chatFinishReason(reason: FinishReason): string {
487
- if (reason === "tool-calls") return "tool_calls";
488
- if (reason === "length") return "length";
489
- if (reason === "content-filter") return "content_filter";
490
- return "stop";
491
- }
492
-
493
- function anthropicFinishReason(reason: FinishReason): string {
494
- if (reason === "tool-calls") return "tool_use";
495
- if (reason === "length") return "max_tokens";
496
- return "end_turn";
497
- }
498
-
499
- function protocolError(error: unknown): Record<string, unknown> {
500
- return {
501
- type: "error",
502
- error: {
503
- type: "api_error",
504
- message: error instanceof Error ? error.message : String(error),
505
- },
506
- };
507
- }
508
-
509
- function sse(event: string, payload: unknown): string {
510
- return `event: ${event}\ndata: ${JSON.stringify(payload)}\n\n`;
511
- }
512
-
513
- function data(payload: unknown): string {
514
- return `data: ${JSON.stringify(payload)}\n\n`;
515
- }