@hebo-ai/gateway 0.9.1 → 0.9.2

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.
Files changed (212) hide show
  1. package/README.md +82 -4
  2. package/package.json +31 -31
  3. package/dist/config.d.ts +0 -2
  4. package/dist/config.js +0 -111
  5. package/dist/endpoints/chat-completions/converters.d.ts +0 -26
  6. package/dist/endpoints/chat-completions/converters.js +0 -524
  7. package/dist/endpoints/chat-completions/handler.d.ts +0 -2
  8. package/dist/endpoints/chat-completions/handler.js +0 -149
  9. package/dist/endpoints/chat-completions/index.d.ts +0 -4
  10. package/dist/endpoints/chat-completions/index.js +0 -4
  11. package/dist/endpoints/chat-completions/otel.d.ts +0 -5
  12. package/dist/endpoints/chat-completions/otel.js +0 -175
  13. package/dist/endpoints/chat-completions/schema.d.ts +0 -1170
  14. package/dist/endpoints/chat-completions/schema.js +0 -252
  15. package/dist/endpoints/conversations/converters.d.ts +0 -8
  16. package/dist/endpoints/conversations/converters.js +0 -29
  17. package/dist/endpoints/conversations/handler.d.ts +0 -2
  18. package/dist/endpoints/conversations/handler.js +0 -276
  19. package/dist/endpoints/conversations/index.d.ts +0 -3
  20. package/dist/endpoints/conversations/index.js +0 -3
  21. package/dist/endpoints/conversations/schema.d.ts +0 -1511
  22. package/dist/endpoints/conversations/schema.js +0 -74
  23. package/dist/endpoints/conversations/storage/dialects/greptime.d.ts +0 -10
  24. package/dist/endpoints/conversations/storage/dialects/greptime.js +0 -87
  25. package/dist/endpoints/conversations/storage/dialects/mysql.d.ts +0 -12
  26. package/dist/endpoints/conversations/storage/dialects/mysql.js +0 -118
  27. package/dist/endpoints/conversations/storage/dialects/postgres.d.ts +0 -16
  28. package/dist/endpoints/conversations/storage/dialects/postgres.js +0 -185
  29. package/dist/endpoints/conversations/storage/dialects/sqlite.d.ts +0 -11
  30. package/dist/endpoints/conversations/storage/dialects/sqlite.js +0 -176
  31. package/dist/endpoints/conversations/storage/dialects/types.d.ts +0 -42
  32. package/dist/endpoints/conversations/storage/dialects/types.js +0 -0
  33. package/dist/endpoints/conversations/storage/dialects/utils.d.ts +0 -25
  34. package/dist/endpoints/conversations/storage/dialects/utils.js +0 -80
  35. package/dist/endpoints/conversations/storage/memory.d.ts +0 -25
  36. package/dist/endpoints/conversations/storage/memory.js +0 -200
  37. package/dist/endpoints/conversations/storage/sql.d.ts +0 -33
  38. package/dist/endpoints/conversations/storage/sql.js +0 -278
  39. package/dist/endpoints/conversations/storage/types.d.ts +0 -39
  40. package/dist/endpoints/conversations/storage/types.js +0 -0
  41. package/dist/endpoints/embeddings/converters.d.ts +0 -10
  42. package/dist/endpoints/embeddings/converters.js +0 -31
  43. package/dist/endpoints/embeddings/handler.d.ts +0 -2
  44. package/dist/endpoints/embeddings/handler.js +0 -104
  45. package/dist/endpoints/embeddings/index.d.ts +0 -4
  46. package/dist/endpoints/embeddings/index.js +0 -4
  47. package/dist/endpoints/embeddings/otel.d.ts +0 -5
  48. package/dist/endpoints/embeddings/otel.js +0 -29
  49. package/dist/endpoints/embeddings/schema.d.ts +0 -44
  50. package/dist/endpoints/embeddings/schema.js +0 -29
  51. package/dist/endpoints/models/converters.d.ts +0 -6
  52. package/dist/endpoints/models/converters.js +0 -42
  53. package/dist/endpoints/models/handler.d.ts +0 -2
  54. package/dist/endpoints/models/handler.js +0 -29
  55. package/dist/endpoints/models/index.d.ts +0 -3
  56. package/dist/endpoints/models/index.js +0 -3
  57. package/dist/endpoints/models/schema.d.ts +0 -42
  58. package/dist/endpoints/models/schema.js +0 -31
  59. package/dist/endpoints/responses/converters.d.ts +0 -17
  60. package/dist/endpoints/responses/converters.js +0 -1034
  61. package/dist/endpoints/responses/handler.d.ts +0 -2
  62. package/dist/endpoints/responses/handler.js +0 -137
  63. package/dist/endpoints/responses/index.d.ts +0 -4
  64. package/dist/endpoints/responses/index.js +0 -4
  65. package/dist/endpoints/responses/otel.d.ts +0 -6
  66. package/dist/endpoints/responses/otel.js +0 -221
  67. package/dist/endpoints/responses/schema.d.ts +0 -2109
  68. package/dist/endpoints/responses/schema.js +0 -314
  69. package/dist/endpoints/shared/converters.d.ts +0 -56
  70. package/dist/endpoints/shared/converters.js +0 -179
  71. package/dist/endpoints/shared/schema.d.ts +0 -70
  72. package/dist/endpoints/shared/schema.js +0 -46
  73. package/dist/errors/ai-sdk.d.ts +0 -2
  74. package/dist/errors/ai-sdk.js +0 -52
  75. package/dist/errors/gateway.d.ts +0 -5
  76. package/dist/errors/gateway.js +0 -13
  77. package/dist/errors/openai.d.ts +0 -15
  78. package/dist/errors/openai.js +0 -40
  79. package/dist/errors/utils.d.ts +0 -22
  80. package/dist/errors/utils.js +0 -44
  81. package/dist/gateway.d.ts +0 -11
  82. package/dist/gateway.js +0 -44
  83. package/dist/index.d.ts +0 -11
  84. package/dist/index.js +0 -10
  85. package/dist/lifecycle.d.ts +0 -3
  86. package/dist/lifecycle.js +0 -113
  87. package/dist/logger/default.d.ts +0 -4
  88. package/dist/logger/default.js +0 -81
  89. package/dist/logger/index.d.ts +0 -11
  90. package/dist/logger/index.js +0 -25
  91. package/dist/middleware/common.d.ts +0 -12
  92. package/dist/middleware/common.js +0 -146
  93. package/dist/middleware/debug.d.ts +0 -3
  94. package/dist/middleware/debug.js +0 -27
  95. package/dist/middleware/matcher.d.ts +0 -28
  96. package/dist/middleware/matcher.js +0 -118
  97. package/dist/middleware/utils.d.ts +0 -2
  98. package/dist/middleware/utils.js +0 -24
  99. package/dist/models/amazon/index.d.ts +0 -2
  100. package/dist/models/amazon/index.js +0 -2
  101. package/dist/models/amazon/middleware.d.ts +0 -3
  102. package/dist/models/amazon/middleware.js +0 -68
  103. package/dist/models/amazon/presets.d.ts +0 -345
  104. package/dist/models/amazon/presets.js +0 -80
  105. package/dist/models/anthropic/index.d.ts +0 -2
  106. package/dist/models/anthropic/index.js +0 -2
  107. package/dist/models/anthropic/middleware.d.ts +0 -5
  108. package/dist/models/anthropic/middleware.js +0 -127
  109. package/dist/models/anthropic/presets.d.ts +0 -711
  110. package/dist/models/anthropic/presets.js +0 -140
  111. package/dist/models/catalog.d.ts +0 -4
  112. package/dist/models/catalog.js +0 -8
  113. package/dist/models/cohere/index.d.ts +0 -2
  114. package/dist/models/cohere/index.js +0 -2
  115. package/dist/models/cohere/middleware.d.ts +0 -3
  116. package/dist/models/cohere/middleware.js +0 -62
  117. package/dist/models/cohere/presets.d.ts +0 -411
  118. package/dist/models/cohere/presets.js +0 -134
  119. package/dist/models/google/index.d.ts +0 -2
  120. package/dist/models/google/index.js +0 -2
  121. package/dist/models/google/middleware.d.ts +0 -8
  122. package/dist/models/google/middleware.js +0 -117
  123. package/dist/models/google/presets.d.ts +0 -403
  124. package/dist/models/google/presets.js +0 -88
  125. package/dist/models/meta/index.d.ts +0 -1
  126. package/dist/models/meta/index.js +0 -1
  127. package/dist/models/meta/presets.d.ts +0 -483
  128. package/dist/models/meta/presets.js +0 -105
  129. package/dist/models/openai/index.d.ts +0 -2
  130. package/dist/models/openai/index.js +0 -2
  131. package/dist/models/openai/middleware.d.ts +0 -4
  132. package/dist/models/openai/middleware.js +0 -88
  133. package/dist/models/openai/presets.d.ts +0 -1319
  134. package/dist/models/openai/presets.js +0 -277
  135. package/dist/models/types.d.ts +0 -20
  136. package/dist/models/types.js +0 -92
  137. package/dist/models/voyage/index.d.ts +0 -2
  138. package/dist/models/voyage/index.js +0 -2
  139. package/dist/models/voyage/middleware.d.ts +0 -2
  140. package/dist/models/voyage/middleware.js +0 -19
  141. package/dist/models/voyage/presets.d.ts +0 -436
  142. package/dist/models/voyage/presets.js +0 -85
  143. package/dist/providers/anthropic/canonical.d.ts +0 -3
  144. package/dist/providers/anthropic/canonical.js +0 -9
  145. package/dist/providers/anthropic/index.d.ts +0 -1
  146. package/dist/providers/anthropic/index.js +0 -1
  147. package/dist/providers/bedrock/canonical.d.ts +0 -17
  148. package/dist/providers/bedrock/canonical.js +0 -61
  149. package/dist/providers/bedrock/index.d.ts +0 -2
  150. package/dist/providers/bedrock/index.js +0 -2
  151. package/dist/providers/bedrock/middleware.d.ts +0 -5
  152. package/dist/providers/bedrock/middleware.js +0 -137
  153. package/dist/providers/cohere/canonical.d.ts +0 -3
  154. package/dist/providers/cohere/canonical.js +0 -17
  155. package/dist/providers/cohere/index.d.ts +0 -1
  156. package/dist/providers/cohere/index.js +0 -1
  157. package/dist/providers/groq/canonical.d.ts +0 -3
  158. package/dist/providers/groq/canonical.js +0 -12
  159. package/dist/providers/groq/index.d.ts +0 -2
  160. package/dist/providers/groq/index.js +0 -2
  161. package/dist/providers/groq/middleware.d.ts +0 -2
  162. package/dist/providers/groq/middleware.js +0 -31
  163. package/dist/providers/openai/canonical.d.ts +0 -3
  164. package/dist/providers/openai/canonical.js +0 -8
  165. package/dist/providers/openai/index.d.ts +0 -1
  166. package/dist/providers/openai/index.js +0 -1
  167. package/dist/providers/registry.d.ts +0 -24
  168. package/dist/providers/registry.js +0 -103
  169. package/dist/providers/types.d.ts +0 -7
  170. package/dist/providers/types.js +0 -11
  171. package/dist/providers/vertex/canonical.d.ts +0 -3
  172. package/dist/providers/vertex/canonical.js +0 -8
  173. package/dist/providers/vertex/index.d.ts +0 -2
  174. package/dist/providers/vertex/index.js +0 -2
  175. package/dist/providers/vertex/middleware.d.ts +0 -2
  176. package/dist/providers/vertex/middleware.js +0 -47
  177. package/dist/providers/voyage/canonical.d.ts +0 -3
  178. package/dist/providers/voyage/canonical.js +0 -7
  179. package/dist/providers/voyage/index.d.ts +0 -1
  180. package/dist/providers/voyage/index.js +0 -1
  181. package/dist/telemetry/ai-sdk.d.ts +0 -2
  182. package/dist/telemetry/ai-sdk.js +0 -31
  183. package/dist/telemetry/baggage.d.ts +0 -1
  184. package/dist/telemetry/baggage.js +0 -24
  185. package/dist/telemetry/fetch.d.ts +0 -2
  186. package/dist/telemetry/fetch.js +0 -49
  187. package/dist/telemetry/gen-ai.d.ts +0 -6
  188. package/dist/telemetry/gen-ai.js +0 -78
  189. package/dist/telemetry/http.d.ts +0 -3
  190. package/dist/telemetry/http.js +0 -54
  191. package/dist/telemetry/index.d.ts +0 -1
  192. package/dist/telemetry/index.js +0 -1
  193. package/dist/telemetry/memory.d.ts +0 -2
  194. package/dist/telemetry/memory.js +0 -43
  195. package/dist/telemetry/span.d.ts +0 -13
  196. package/dist/telemetry/span.js +0 -60
  197. package/dist/types.d.ts +0 -216
  198. package/dist/types.js +0 -2
  199. package/dist/utils/env.d.ts +0 -2
  200. package/dist/utils/env.js +0 -7
  201. package/dist/utils/headers.d.ts +0 -4
  202. package/dist/utils/headers.js +0 -22
  203. package/dist/utils/preset.d.ts +0 -10
  204. package/dist/utils/preset.js +0 -41
  205. package/dist/utils/request.d.ts +0 -2
  206. package/dist/utils/request.js +0 -43
  207. package/dist/utils/response.d.ts +0 -6
  208. package/dist/utils/response.js +0 -55
  209. package/dist/utils/stream.d.ts +0 -9
  210. package/dist/utils/stream.js +0 -100
  211. package/dist/utils/url.d.ts +0 -4
  212. package/dist/utils/url.js +0 -21
@@ -1,524 +0,0 @@
1
- import { Output, jsonSchema, tool } from "ai";
2
- import { toResponse } from "../../utils/response";
3
- import { parseJsonOrText, parseReasoningOptions, parsePromptCachingOptions, resolveResponseServiceTier, normalizeToolName, stripEmptyKeys, parseBase64, parseImageInput, extractReasoningMetadata, } from "../shared/converters";
4
- // --- Request Flow ---
5
- export function convertToTextCallOptions(params) {
6
- const { messages, tools, tool_choice, temperature, max_tokens, max_completion_tokens, response_format, reasoning_effort, reasoning, prompt_cache_key, prompt_cache_retention, extra_body, cache_control, frequency_penalty, presence_penalty, seed, stop, top_p, ...rest } = params;
7
- Object.assign(rest, parseReasoningOptions(reasoning_effort, reasoning));
8
- Object.assign(rest, parsePromptCachingOptions(prompt_cache_key, prompt_cache_retention, cache_control));
9
- if (extra_body) {
10
- for (const v of Object.values(extra_body)) {
11
- Object.assign(rest, v);
12
- }
13
- }
14
- const { toolChoice, activeTools } = convertToToolChoiceOptions(tool_choice);
15
- return {
16
- messages: convertToModelMessages(messages),
17
- tools: convertToToolSet(tools),
18
- toolChoice,
19
- activeTools,
20
- output: convertToOutput(response_format),
21
- temperature,
22
- maxOutputTokens: max_completion_tokens ?? max_tokens,
23
- frequencyPenalty: frequency_penalty,
24
- presencePenalty: presence_penalty,
25
- seed,
26
- stopSequences: stop ? (Array.isArray(stop) ? stop : [stop]) : undefined,
27
- topP: top_p,
28
- providerOptions: {
29
- unknown: rest,
30
- },
31
- };
32
- }
33
- function convertToOutput(responseFormat) {
34
- if (!responseFormat || responseFormat.type === "text") {
35
- return;
36
- }
37
- const { name, description, schema } = responseFormat.json_schema;
38
- return Output.object({
39
- name,
40
- description,
41
- schema: jsonSchema(schema),
42
- });
43
- }
44
- export function convertToModelMessages(messages) {
45
- const modelMessages = [];
46
- const toolById = indexToolMessages(messages);
47
- for (const message of messages) {
48
- if (message.role === "tool")
49
- continue;
50
- if (message.role === "system") {
51
- if (message.cache_control) {
52
- message.providerOptions = {
53
- unknown: { cache_control: message.cache_control },
54
- };
55
- }
56
- modelMessages.push(message);
57
- continue;
58
- }
59
- if (message.role === "user") {
60
- modelMessages.push(fromChatCompletionsUserMessage(message));
61
- continue;
62
- }
63
- modelMessages.push(fromChatCompletionsAssistantMessage(message));
64
- const toolResult = fromChatCompletionsToolResultMessage(message, toolById);
65
- if (toolResult)
66
- modelMessages.push(toolResult);
67
- }
68
- return modelMessages;
69
- }
70
- function indexToolMessages(messages) {
71
- const map = new Map();
72
- for (const m of messages) {
73
- if (m.role === "tool")
74
- map.set(m.tool_call_id, m);
75
- }
76
- return map;
77
- }
78
- export function fromChatCompletionsUserMessage(message) {
79
- const out = {
80
- role: "user",
81
- content: Array.isArray(message.content)
82
- ? fromChatCompletionsContent(message.content)
83
- : message.content,
84
- };
85
- if (message.cache_control) {
86
- out.providerOptions = {
87
- unknown: { cache_control: message.cache_control },
88
- };
89
- }
90
- return out;
91
- }
92
- export function fromChatCompletionsAssistantMessage(message) {
93
- const { tool_calls, role, content, extra_content, reasoning_details, cache_control } = message;
94
- const parts = [];
95
- if (reasoning_details?.length) {
96
- for (const detail of reasoning_details) {
97
- if (detail.text && detail.type === "reasoning.text") {
98
- parts.push({
99
- type: "reasoning",
100
- text: detail.text,
101
- providerOptions: detail.signature
102
- ? {
103
- unknown: {
104
- signature: detail.signature,
105
- },
106
- }
107
- : undefined,
108
- });
109
- }
110
- else if (detail.type === "reasoning.encrypted" && detail.data) {
111
- parts.push({
112
- type: "reasoning",
113
- text: "",
114
- providerOptions: {
115
- unknown: {
116
- redactedData: detail.data,
117
- },
118
- },
119
- });
120
- }
121
- }
122
- }
123
- if (content !== undefined && content !== null) {
124
- const inputContent = typeof content === "string"
125
- ? [{ type: "text", text: content }]
126
- : content;
127
- for (const part of inputContent) {
128
- if (part.type === "text") {
129
- const textPart = {
130
- type: "text",
131
- text: part.text,
132
- };
133
- if (part.cache_control) {
134
- textPart.providerOptions = {
135
- unknown: { cache_control: part.cache_control },
136
- };
137
- }
138
- parts.push(textPart);
139
- }
140
- }
141
- }
142
- if (tool_calls?.length) {
143
- for (const tc of tool_calls) {
144
- // oxlint-disable-next-line no-shadow
145
- const { id, function: fn, extra_content } = tc;
146
- const out = {
147
- type: "tool-call",
148
- toolCallId: id,
149
- toolName: fn.name,
150
- input: parseJsonOrText(fn.arguments).value,
151
- };
152
- if (extra_content) {
153
- out.providerOptions = extra_content;
154
- }
155
- parts.push(out);
156
- }
157
- }
158
- const out = {
159
- role,
160
- content: parts.length > 0 ? parts : (content ?? ""),
161
- };
162
- if (extra_content) {
163
- out.providerOptions = extra_content;
164
- }
165
- if (cache_control) {
166
- (out.providerOptions ??= {})["unknown"] = { cache_control };
167
- }
168
- return out;
169
- }
170
- export function fromChatCompletionsToolResultMessage(message, toolById) {
171
- const toolCalls = message.tool_calls ?? [];
172
- if (toolCalls.length === 0)
173
- return undefined;
174
- const toolResultParts = [];
175
- for (const tc of toolCalls) {
176
- const toolMsg = toolById.get(tc.id);
177
- if (!toolMsg)
178
- continue;
179
- toolResultParts.push({
180
- type: "tool-result",
181
- toolCallId: tc.id,
182
- toolName: tc.function.name,
183
- output: parseToolResult(toolMsg.content),
184
- });
185
- }
186
- return toolResultParts.length > 0 ? { role: "tool", content: toolResultParts } : undefined;
187
- }
188
- export function fromChatCompletionsContent(content) {
189
- return content.map((part) => {
190
- switch (part.type) {
191
- case "image_url":
192
- return fromImageUrlPart(part.image_url.url, part.cache_control);
193
- case "file":
194
- return fromFilePart(part.file.data, part.file.media_type, part.file.filename, part.cache_control);
195
- case "input_audio":
196
- return fromFilePart(part.input_audio.data, `audio/${part.input_audio.format}`, undefined, part.cache_control);
197
- case "text": {
198
- const out = {
199
- type: "text",
200
- text: part.text,
201
- };
202
- if (part.cache_control) {
203
- out.providerOptions = {
204
- unknown: { cache_control: part.cache_control },
205
- };
206
- }
207
- return out;
208
- }
209
- default:
210
- throw new Error(`Unhandled content part type: ${part.type}`);
211
- }
212
- });
213
- }
214
- function fromImageUrlPart(url, cacheControl) {
215
- const { image, mediaType } = parseImageInput(url);
216
- if (image instanceof URL) {
217
- const out = {
218
- type: "image",
219
- image,
220
- };
221
- if (cacheControl) {
222
- out.providerOptions = {
223
- unknown: { cache_control: cacheControl },
224
- };
225
- }
226
- return out;
227
- }
228
- return fromFilePart(image, mediaType ?? "image/jpeg", undefined, cacheControl);
229
- }
230
- function fromFilePart(base64Data, mediaType, filename, cacheControl) {
231
- const data = parseBase64(base64Data);
232
- if (mediaType.startsWith("image/")) {
233
- const out = {
234
- type: "image",
235
- image: data,
236
- mediaType,
237
- };
238
- if (cacheControl) {
239
- out.providerOptions = {
240
- unknown: { cache_control: cacheControl },
241
- };
242
- }
243
- return out;
244
- }
245
- const out = {
246
- type: "file",
247
- data: data,
248
- filename,
249
- mediaType,
250
- };
251
- if (cacheControl) {
252
- out.providerOptions = {
253
- unknown: { cache_control: cacheControl },
254
- };
255
- }
256
- return out;
257
- }
258
- export const convertToToolSet = (tools) => {
259
- if (!tools) {
260
- return;
261
- }
262
- const toolSet = {};
263
- for (const t of tools) {
264
- toolSet[t.function.name] = tool({
265
- description: t.function.description,
266
- inputSchema: jsonSchema(t.function.parameters),
267
- strict: t.function.strict,
268
- });
269
- }
270
- return toolSet;
271
- };
272
- export const convertToToolChoiceOptions = (toolChoice) => {
273
- if (!toolChoice) {
274
- return {};
275
- }
276
- if (toolChoice === "none" || toolChoice === "auto" || toolChoice === "required") {
277
- return { toolChoice };
278
- }
279
- // FUTURE: this is right now google specific, which is not supported by AI SDK, until then,
280
- // we temporarily map it to auto for now
281
- // https://docs.cloud.google.com/vertex-ai/generative-ai/docs/migrate/openai/overview
282
- if (toolChoice === "validated") {
283
- return { toolChoice: "auto" };
284
- }
285
- if (toolChoice.type === "allowed_tools") {
286
- return {
287
- toolChoice: toolChoice.allowed_tools.mode,
288
- activeTools: toolChoice.allowed_tools.tools.map((toolRef) => toolRef.function.name),
289
- };
290
- }
291
- return {
292
- toolChoice: {
293
- type: "tool",
294
- toolName: toolChoice.function.name,
295
- },
296
- };
297
- };
298
- function parseToolResult(content) {
299
- if (Array.isArray(content)) {
300
- return {
301
- type: "content",
302
- value: content.map((part) => ({
303
- type: "text",
304
- text: part.text,
305
- })),
306
- };
307
- }
308
- return parseJsonOrText(content);
309
- }
310
- // --- Response Flow ---
311
- export function toChatCompletions(result, model) {
312
- return {
313
- id: "chatcmpl-" + crypto.randomUUID(),
314
- object: "chat.completion",
315
- created: Math.floor(Date.now() / 1000),
316
- model,
317
- choices: [
318
- {
319
- index: 0,
320
- message: toChatCompletionsAssistantMessage(result),
321
- finish_reason: toChatCompletionsFinishReason(result.finishReason),
322
- },
323
- ],
324
- usage: result.totalUsage ? toChatCompletionsUsage(result.totalUsage) : null,
325
- provider_metadata: result.providerMetadata,
326
- service_tier: resolveResponseServiceTier(result.providerMetadata),
327
- };
328
- }
329
- export function toChatCompletionsResponse(result, model, responseInit) {
330
- return toResponse(toChatCompletions(result, model), responseInit);
331
- }
332
- export function toChatCompletionsStream(result, model) {
333
- return result.fullStream.pipeThrough(new ChatCompletionsTransformStream(model));
334
- }
335
- export function toChatCompletionsStreamResponse(result, model, responseInit) {
336
- return toResponse(toChatCompletionsStream(result, model), responseInit);
337
- }
338
- export class ChatCompletionsTransformStream extends TransformStream {
339
- constructor(model) {
340
- const streamId = `chatcmpl-${crypto.randomUUID()}`;
341
- const creationTime = Math.floor(Date.now() / 1000);
342
- let toolCallIndexCounter = 0;
343
- const reasoningIdToIndex = new Map();
344
- let finishProviderMetadata;
345
- const createChunk = (delta, provider_metadata, finish_reason, usage) => {
346
- if (provider_metadata) {
347
- delta.extra_content = provider_metadata;
348
- }
349
- return {
350
- data: {
351
- id: streamId,
352
- object: "chat.completion.chunk",
353
- created: creationTime,
354
- model,
355
- choices: [
356
- {
357
- index: 0,
358
- delta,
359
- finish_reason: finish_reason ?? null,
360
- },
361
- ],
362
- usage: usage ?? null,
363
- service_tier: resolveResponseServiceTier(provider_metadata),
364
- },
365
- };
366
- };
367
- super({
368
- transform(part, controller) {
369
- // Omit lifecycle (start/end) and intermediate events; /chat/completions
370
- // is a stateless stream of deltas. Tool calls are emitted once fully-formed.
371
- // oxlint-disable-next-line switch-exhaustiveness-check
372
- switch (part.type) {
373
- case "text-delta": {
374
- controller.enqueue(createChunk({ role: "assistant", content: part.text }, part.providerMetadata));
375
- break;
376
- }
377
- case "reasoning-delta": {
378
- let index = reasoningIdToIndex.get(part.id);
379
- if (index === undefined) {
380
- index = reasoningIdToIndex.size;
381
- reasoningIdToIndex.set(part.id, index);
382
- }
383
- controller.enqueue(createChunk({
384
- reasoning: part.text,
385
- reasoning_details: [
386
- toReasoningDetail({
387
- type: "reasoning",
388
- text: part.text,
389
- providerMetadata: part.providerMetadata,
390
- }, part.id, index),
391
- ],
392
- }, part.providerMetadata));
393
- break;
394
- }
395
- case "tool-call": {
396
- const toolCall = toChatCompletionsToolCall(part.toolCallId, part.toolName, part.input, part.providerMetadata);
397
- toolCall.index = toolCallIndexCounter++;
398
- controller.enqueue(createChunk({
399
- tool_calls: [toolCall],
400
- }));
401
- break;
402
- }
403
- case "finish-step": {
404
- finishProviderMetadata = part.providerMetadata;
405
- break;
406
- }
407
- case "finish": {
408
- controller.enqueue(createChunk({}, finishProviderMetadata, toChatCompletionsFinishReason(part.finishReason), toChatCompletionsUsage(part.totalUsage)));
409
- break;
410
- }
411
- case "error": {
412
- controller.enqueue({
413
- data: part.error instanceof Error ? part.error : new Error(String(part.error)),
414
- });
415
- }
416
- }
417
- },
418
- });
419
- }
420
- }
421
- export const toChatCompletionsAssistantMessage = (result) => {
422
- const message = {
423
- role: "assistant",
424
- content: null,
425
- };
426
- if (result.toolCalls && result.toolCalls.length > 0) {
427
- message.tool_calls = result.toolCalls.map((toolCall) => toChatCompletionsToolCall(toolCall.toolCallId, toolCall.toolName, toolCall.input, toolCall.providerMetadata));
428
- }
429
- const reasoningDetails = [];
430
- for (const part of result.content) {
431
- if (part.type === "text") {
432
- if (message.content === null) {
433
- message.content = part.text;
434
- }
435
- else {
436
- message.content += part.text;
437
- }
438
- if (part.providerMetadata) {
439
- message.extra_content = part.providerMetadata;
440
- }
441
- }
442
- else if (part.type === "reasoning") {
443
- reasoningDetails.push(toReasoningDetail(part, `reasoning-${crypto.randomUUID()}`, reasoningDetails.length));
444
- }
445
- }
446
- if (result.reasoningText) {
447
- message.reasoning = result.reasoningText;
448
- }
449
- if (reasoningDetails.length > 0) {
450
- message.reasoning_details = reasoningDetails;
451
- }
452
- if (!message.content && !message.tool_calls) {
453
- // some models return just reasoning without tool calls or content
454
- message.content = "";
455
- }
456
- return message;
457
- };
458
- export function toReasoningDetail(reasoning, id, index) {
459
- const { redactedData, signature } = extractReasoningMetadata(reasoning.providerMetadata);
460
- if (redactedData) {
461
- return {
462
- id,
463
- index,
464
- type: "reasoning.encrypted",
465
- data: redactedData,
466
- format: "unknown",
467
- };
468
- }
469
- return {
470
- id,
471
- index,
472
- type: "reasoning.text",
473
- text: reasoning.text,
474
- signature,
475
- format: "unknown",
476
- };
477
- }
478
- export function toChatCompletionsUsage(usage) {
479
- const out = {};
480
- const prompt = usage.inputTokens;
481
- if (prompt !== undefined)
482
- out.prompt_tokens = prompt;
483
- const completion = usage.outputTokens;
484
- if (completion !== undefined)
485
- out.completion_tokens = completion;
486
- if (prompt !== undefined || completion !== undefined || usage.totalTokens !== undefined) {
487
- out.total_tokens = usage.totalTokens ?? (prompt ?? 0) + (completion ?? 0);
488
- }
489
- const reasoning = usage.outputTokenDetails?.reasoningTokens;
490
- if (reasoning !== undefined)
491
- out.completion_tokens_details = { reasoning_tokens: reasoning };
492
- const cached = usage.inputTokenDetails?.cacheReadTokens;
493
- const cacheWrite = usage.inputTokenDetails?.cacheWriteTokens;
494
- if (cached !== undefined || cacheWrite !== undefined) {
495
- out.prompt_tokens_details = {};
496
- if (cached !== undefined) {
497
- out.prompt_tokens_details.cached_tokens = cached;
498
- }
499
- if (cacheWrite !== undefined) {
500
- out.prompt_tokens_details.cache_write_tokens = cacheWrite;
501
- }
502
- }
503
- return out;
504
- }
505
- export function toChatCompletionsToolCall(id, name, args, providerMetadata) {
506
- const out = {
507
- id,
508
- type: "function",
509
- function: {
510
- name: normalizeToolName(name),
511
- arguments: typeof args === "string" ? args : JSON.stringify(stripEmptyKeys(args)),
512
- },
513
- };
514
- if (providerMetadata) {
515
- out.extra_content = providerMetadata;
516
- }
517
- return out;
518
- }
519
- export const toChatCompletionsFinishReason = (finishReason) => {
520
- if (finishReason === "error" || finishReason === "other") {
521
- return "stop";
522
- }
523
- return finishReason.replaceAll("-", "_");
524
- };
@@ -1,2 +0,0 @@
1
- import type { GatewayConfig, Endpoint } from "../../types";
2
- export declare const chatCompletions: (config: GatewayConfig) => Endpoint;
@@ -1,149 +0,0 @@
1
- import { generateText, Output, streamText, wrapLanguageModel, } from "ai";
2
- import * as z from "zod/mini";
3
- import { GatewayError } from "../../errors/gateway";
4
- import { winterCgHandler } from "../../lifecycle";
5
- import { logger } from "../../logger";
6
- import { modelMiddlewareMatcher } from "../../middleware/matcher";
7
- import { resolveProvider } from "../../providers/registry";
8
- import { getGenAiGeneralAttributes, recordTimePerOutputToken, recordTokenUsage, } from "../../telemetry/gen-ai";
9
- import { addSpanEvent, setSpanAttributes } from "../../telemetry/span";
10
- import { prepareForwardHeaders } from "../../utils/request";
11
- import { convertToTextCallOptions, toChatCompletions, toChatCompletionsStream } from "./converters";
12
- import { getChatRequestAttributes, getChatResponseAttributes } from "./otel";
13
- import { ChatCompletionsBodySchema, } from "./schema";
14
- export const chatCompletions = (config) => {
15
- const hooks = config.hooks;
16
- const handler = async (ctx, cfg) => {
17
- const start = performance.now();
18
- ctx.operation = "chat";
19
- setSpanAttributes({ "gen_ai.operation.name": ctx.operation });
20
- addSpanEvent("hebo.handler.started");
21
- // Guard: enforce HTTP method early.
22
- if (!ctx.request || ctx.request.method !== "POST") {
23
- throw new GatewayError("Method Not Allowed", 405);
24
- }
25
- // Parse + validate input.
26
- try {
27
- // oxlint-disable-next-line no-unsafe-assignment
28
- ctx.body = await ctx.request.json();
29
- }
30
- catch {
31
- throw new GatewayError("Invalid JSON", 400);
32
- }
33
- logger.trace({ requestId: ctx.requestId, body: ctx.body }, "[chat] ChatCompletionsBody");
34
- addSpanEvent("hebo.request.deserialized");
35
- const parsed = ChatCompletionsBodySchema.safeParse(ctx.body);
36
- if (!parsed.success) {
37
- // FUTURE: consider adding body shape to metadata
38
- throw new GatewayError(z.prettifyError(parsed.error), 400, undefined, parsed.error);
39
- }
40
- ctx.body = parsed.data;
41
- addSpanEvent("hebo.request.parsed");
42
- if (hooks?.before) {
43
- ctx.body =
44
- (await hooks.before(ctx)) ?? ctx.body;
45
- addSpanEvent("hebo.hooks.before.completed");
46
- }
47
- // Resolve model + provider (hooks may override defaults).
48
- ctx.modelId = ctx.body.model;
49
- ctx.resolvedModelId =
50
- (await hooks?.resolveModelId?.(ctx)) ?? ctx.modelId;
51
- logger.debug(`[chat] resolved ${ctx.modelId} to ${ctx.resolvedModelId}`);
52
- addSpanEvent("hebo.model.resolved");
53
- const override = await hooks?.resolveProvider?.(ctx);
54
- ctx.provider =
55
- override ??
56
- resolveProvider({
57
- providers: ctx.providers,
58
- models: ctx.models,
59
- modelId: ctx.resolvedModelId,
60
- operation: ctx.operation,
61
- });
62
- const languageModel = ctx.provider.languageModel(ctx.resolvedModelId);
63
- ctx.resolvedProviderId = languageModel.provider;
64
- logger.debug(`[chat] using ${languageModel.provider} for ${ctx.resolvedModelId}`);
65
- addSpanEvent("hebo.provider.resolved");
66
- const genAiSignalLevel = cfg.telemetry?.signals?.gen_ai;
67
- const genAiGeneralAttrs = getGenAiGeneralAttributes(ctx, genAiSignalLevel);
68
- setSpanAttributes(genAiGeneralAttrs);
69
- // Convert inputs to AI SDK call options.
70
- const { model: _model, stream, ...inputs } = ctx.body;
71
- const textOptions = convertToTextCallOptions(inputs);
72
- logger.trace({
73
- requestId: ctx.requestId,
74
- options: textOptions,
75
- }, "[chat] AI SDK options");
76
- addSpanEvent("hebo.options.prepared");
77
- setSpanAttributes(getChatRequestAttributes(ctx.body, genAiSignalLevel));
78
- // Build middleware chain (model -> forward params -> provider).
79
- const languageModelWithMiddleware = wrapLanguageModel({
80
- model: languageModel,
81
- middleware: modelMiddlewareMatcher.for(ctx.resolvedModelId, languageModel.provider),
82
- });
83
- // Execute request (streaming vs. non-streaming).
84
- if (stream) {
85
- addSpanEvent("hebo.ai-sdk.started");
86
- const result = streamText({
87
- model: languageModelWithMiddleware,
88
- headers: prepareForwardHeaders(ctx.request),
89
- abortSignal: ctx.request.signal,
90
- timeout: {
91
- totalMs: ctx.body.service_tier === "flex" ? cfg.timeouts.flex : cfg.timeouts.normal,
92
- },
93
- onAbort: () => {
94
- throw new DOMException("The operation was aborted.", "AbortError");
95
- },
96
- onError: () => { },
97
- onFinish: (res) => {
98
- addSpanEvent("hebo.ai-sdk.completed");
99
- const streamResult = toChatCompletions(res, ctx.resolvedModelId);
100
- logger.trace({ requestId: ctx.requestId, result: streamResult }, "[chat] ChatCompletions");
101
- addSpanEvent("hebo.result.transformed");
102
- const genAiResponseAttrs = getChatResponseAttributes(streamResult, genAiSignalLevel);
103
- setSpanAttributes(genAiResponseAttrs);
104
- recordTokenUsage(genAiResponseAttrs, genAiGeneralAttrs, genAiSignalLevel);
105
- recordTimePerOutputToken(start, genAiResponseAttrs, genAiGeneralAttrs, genAiSignalLevel);
106
- },
107
- experimental_include: {
108
- requestBody: false,
109
- },
110
- includeRawChunks: false,
111
- ...textOptions,
112
- });
113
- ctx.result = toChatCompletionsStream(result, ctx.resolvedModelId);
114
- if (hooks?.after) {
115
- ctx.result = (await hooks.after(ctx)) ?? ctx.result;
116
- addSpanEvent("hebo.hooks.after.completed");
117
- }
118
- return ctx.result;
119
- }
120
- addSpanEvent("hebo.ai-sdk.started");
121
- const result = await generateText({
122
- model: languageModelWithMiddleware,
123
- headers: prepareForwardHeaders(ctx.request),
124
- abortSignal: ctx.request.signal,
125
- timeout: ctx.body.service_tier === "flex" ? cfg.timeouts.flex : cfg.timeouts.normal,
126
- experimental_include: {
127
- requestBody: false,
128
- responseBody: false,
129
- },
130
- ...textOptions,
131
- });
132
- logger.trace({ requestId: ctx.requestId, result }, "[chat] AI SDK result");
133
- addSpanEvent("hebo.ai-sdk.completed");
134
- // Transform result.
135
- ctx.result = toChatCompletions(result, ctx.resolvedModelId);
136
- logger.trace({ requestId: ctx.requestId, result: ctx.result }, "[chat] ChatCompletions");
137
- addSpanEvent("hebo.result.transformed");
138
- const genAiResponseAttrs = getChatResponseAttributes(ctx.result, genAiSignalLevel);
139
- setSpanAttributes(genAiResponseAttrs);
140
- recordTokenUsage(genAiResponseAttrs, genAiGeneralAttrs, genAiSignalLevel);
141
- if (hooks?.after) {
142
- ctx.result = (await hooks.after(ctx)) ?? ctx.result;
143
- addSpanEvent("hebo.hooks.after.completed");
144
- }
145
- recordTimePerOutputToken(start, genAiResponseAttrs, genAiGeneralAttrs, genAiSignalLevel);
146
- return ctx.result;
147
- };
148
- return { handler: winterCgHandler(handler, config) };
149
- };
@@ -1,4 +0,0 @@
1
- export * from "./converters";
2
- export * from "./handler";
3
- export * from "./schema";
4
- export * from "./otel";
@@ -1,4 +0,0 @@
1
- export * from "./converters";
2
- export * from "./handler";
3
- export * from "./schema";
4
- export * from "./otel";
@@ -1,5 +0,0 @@
1
- import type { Attributes } from "@opentelemetry/api";
2
- import type { ChatCompletions, ChatCompletionsBody } from "./schema";
3
- import { type TelemetrySignalLevel } from "../../types";
4
- export declare const getChatRequestAttributes: (body: ChatCompletionsBody, signalLevel?: TelemetrySignalLevel) => Attributes;
5
- export declare const getChatResponseAttributes: (completions: ChatCompletions, signalLevel?: TelemetrySignalLevel) => Attributes;