@hebo-ai/gateway 0.1.2 → 0.2.0

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 (202) hide show
  1. package/README.md +172 -67
  2. package/dist/config.js +2 -12
  3. package/dist/endpoints/chat-completions/converters.d.ts +28 -24
  4. package/dist/endpoints/chat-completions/converters.js +99 -73
  5. package/dist/endpoints/chat-completions/handler.js +36 -30
  6. package/dist/endpoints/chat-completions/schema.d.ts +394 -272
  7. package/dist/endpoints/chat-completions/schema.js +124 -57
  8. package/dist/endpoints/embeddings/converters.d.ts +4 -4
  9. package/dist/endpoints/embeddings/converters.js +8 -9
  10. package/dist/endpoints/embeddings/handler.js +32 -26
  11. package/dist/endpoints/embeddings/schema.d.ts +28 -38
  12. package/dist/endpoints/embeddings/schema.js +10 -10
  13. package/dist/endpoints/models/converters.d.ts +2 -2
  14. package/dist/endpoints/models/converters.js +9 -12
  15. package/dist/endpoints/models/handler.js +8 -9
  16. package/dist/endpoints/models/schema.d.ts +37 -31
  17. package/dist/endpoints/models/schema.js +23 -12
  18. package/dist/gateway.d.ts +8 -9
  19. package/dist/gateway.js +7 -10
  20. package/dist/index.d.ts +2 -0
  21. package/dist/index.js +2 -0
  22. package/dist/lifecycle.d.ts +2 -0
  23. package/dist/{utils/hooks.js → lifecycle.js} +16 -8
  24. package/dist/middleware/common.d.ts +4 -0
  25. package/dist/middleware/common.js +44 -0
  26. package/dist/middleware/matcher.d.ts +18 -0
  27. package/dist/middleware/matcher.js +83 -0
  28. package/dist/models/amazon/index.d.ts +2 -0
  29. package/dist/models/amazon/index.js +2 -0
  30. package/dist/models/amazon/middleware.d.ts +2 -0
  31. package/dist/models/amazon/middleware.js +20 -0
  32. package/dist/models/amazon/presets.d.ts +2390 -0
  33. package/dist/models/amazon/presets.js +80 -0
  34. package/dist/models/anthropic/index.d.ts +2 -0
  35. package/dist/models/anthropic/index.js +2 -0
  36. package/dist/models/anthropic/middleware.d.ts +5 -0
  37. package/dist/models/anthropic/middleware.js +67 -0
  38. package/dist/models/anthropic/presets.d.ts +4106 -0
  39. package/dist/models/anthropic/presets.js +113 -0
  40. package/dist/models/catalog.d.ts +3 -1
  41. package/dist/models/catalog.js +3 -2
  42. package/dist/models/cohere/index.d.ts +2 -0
  43. package/dist/models/cohere/index.js +2 -0
  44. package/dist/models/cohere/middleware.d.ts +2 -0
  45. package/dist/models/cohere/middleware.js +18 -0
  46. package/dist/models/cohere/presets.d.ts +2918 -0
  47. package/dist/models/cohere/presets.js +129 -0
  48. package/dist/models/google/index.d.ts +2 -0
  49. package/dist/models/google/index.js +2 -0
  50. package/dist/models/google/middleware.d.ts +2 -0
  51. package/dist/models/google/middleware.js +20 -0
  52. package/dist/models/{presets/gemini.d.ts → google/presets.d.ts} +400 -174
  53. package/dist/models/{presets/gemini.js → google/presets.js} +20 -5
  54. package/dist/models/meta/index.d.ts +1 -0
  55. package/dist/models/meta/index.js +1 -0
  56. package/dist/models/meta/presets.d.ts +3254 -0
  57. package/dist/models/{presets/llama.js → meta/presets.js} +44 -7
  58. package/dist/models/openai/index.d.ts +2 -0
  59. package/dist/models/openai/index.js +2 -0
  60. package/dist/models/openai/middleware.d.ts +2 -0
  61. package/dist/models/openai/middleware.js +20 -0
  62. package/dist/models/openai/presets.d.ts +6252 -0
  63. package/dist/models/openai/presets.js +206 -0
  64. package/dist/models/types.d.ts +3 -3
  65. package/dist/models/types.js +27 -0
  66. package/dist/models/voyage/index.d.ts +2 -0
  67. package/dist/models/voyage/index.js +2 -0
  68. package/dist/models/voyage/middleware.d.ts +2 -0
  69. package/dist/models/voyage/middleware.js +18 -0
  70. package/dist/models/{presets/voyage.d.ts → voyage/presets.d.ts} +322 -323
  71. package/dist/providers/anthropic/canonical.d.ts +3 -0
  72. package/dist/providers/anthropic/canonical.js +9 -0
  73. package/dist/providers/anthropic/index.d.ts +1 -0
  74. package/dist/providers/anthropic/index.js +1 -0
  75. package/dist/providers/bedrock/canonical.d.ts +15 -0
  76. package/dist/providers/{canonical/bedrock.js → bedrock/canonical.js} +13 -15
  77. package/dist/providers/bedrock/index.d.ts +1 -0
  78. package/dist/providers/bedrock/index.js +1 -0
  79. package/dist/providers/cohere/canonical.d.ts +3 -0
  80. package/dist/providers/{canonical/cohere.js → cohere/canonical.js} +6 -6
  81. package/dist/providers/cohere/index.d.ts +1 -0
  82. package/dist/providers/cohere/index.js +1 -0
  83. package/dist/providers/groq/canonical.d.ts +3 -0
  84. package/dist/providers/groq/canonical.js +12 -0
  85. package/dist/providers/groq/index.d.ts +1 -0
  86. package/dist/providers/groq/index.js +1 -0
  87. package/dist/providers/openai/canonical.d.ts +3 -0
  88. package/dist/providers/openai/canonical.js +8 -0
  89. package/dist/providers/openai/index.d.ts +1 -0
  90. package/dist/providers/openai/index.js +1 -0
  91. package/dist/providers/registry.d.ts +16 -26
  92. package/dist/providers/registry.js +19 -26
  93. package/dist/providers/types.d.ts +1 -1
  94. package/dist/providers/types.js +1 -0
  95. package/dist/providers/vertex/canonical.d.ts +3 -0
  96. package/dist/providers/vertex/canonical.js +8 -0
  97. package/dist/providers/vertex/index.d.ts +1 -0
  98. package/dist/providers/vertex/index.js +1 -0
  99. package/dist/providers/voyage/canonical.d.ts +3 -0
  100. package/dist/providers/voyage/canonical.js +7 -0
  101. package/dist/providers/voyage/index.d.ts +1 -0
  102. package/dist/providers/voyage/index.js +1 -0
  103. package/dist/types.d.ts +60 -30
  104. package/dist/utils/errors.js +2 -0
  105. package/dist/utils/preset.d.ts +1 -7
  106. package/dist/utils/preset.js +1 -1
  107. package/dist/utils/response.d.ts +1 -0
  108. package/dist/utils/response.js +10 -0
  109. package/package.json +79 -70
  110. package/src/config.ts +2 -18
  111. package/src/endpoints/chat-completions/converters.test.ts +39 -0
  112. package/src/endpoints/chat-completions/converters.ts +191 -112
  113. package/src/endpoints/chat-completions/handler.test.ts +47 -18
  114. package/src/endpoints/chat-completions/handler.ts +40 -34
  115. package/src/endpoints/chat-completions/schema.ts +161 -88
  116. package/src/endpoints/embeddings/converters.ts +15 -11
  117. package/src/endpoints/embeddings/handler.test.ts +27 -30
  118. package/src/endpoints/embeddings/handler.ts +34 -28
  119. package/src/endpoints/embeddings/schema.ts +10 -10
  120. package/src/endpoints/models/converters.ts +22 -14
  121. package/src/endpoints/models/handler.test.ts +26 -29
  122. package/src/endpoints/models/handler.ts +10 -12
  123. package/src/endpoints/models/schema.ts +26 -20
  124. package/src/gateway.ts +10 -24
  125. package/src/index.ts +3 -0
  126. package/src/{utils/hooks.ts → lifecycle.ts} +21 -11
  127. package/src/middleware/common.ts +68 -0
  128. package/src/middleware/matcher.ts +117 -0
  129. package/src/models/amazon/index.ts +2 -0
  130. package/src/models/amazon/middleware.ts +25 -0
  131. package/src/models/amazon/presets.ts +104 -0
  132. package/src/models/anthropic/index.ts +2 -0
  133. package/src/models/anthropic/middleware.test.ts +184 -0
  134. package/src/models/anthropic/middleware.ts +75 -0
  135. package/src/models/anthropic/presets.ts +161 -0
  136. package/src/models/catalog.ts +10 -2
  137. package/src/models/cohere/index.ts +2 -0
  138. package/src/models/cohere/middleware.ts +23 -0
  139. package/src/models/cohere/presets.ts +181 -0
  140. package/src/models/google/index.ts +2 -0
  141. package/src/models/google/middleware.ts +25 -0
  142. package/src/models/{presets/gemini.ts → google/presets.ts} +25 -5
  143. package/src/models/meta/index.ts +1 -0
  144. package/src/models/{presets/llama.ts → meta/presets.ts} +68 -7
  145. package/src/models/openai/index.ts +2 -0
  146. package/src/models/openai/middleware.ts +25 -0
  147. package/src/models/openai/presets.ts +269 -0
  148. package/src/models/types.ts +29 -2
  149. package/src/models/voyage/index.ts +2 -0
  150. package/src/models/voyage/middleware.ts +23 -0
  151. package/src/providers/anthropic/canonical.ts +17 -0
  152. package/src/providers/anthropic/index.ts +1 -0
  153. package/src/providers/{canonical/bedrock.ts → bedrock/canonical.ts} +22 -32
  154. package/src/providers/bedrock/index.ts +1 -0
  155. package/src/providers/cohere/canonical.ts +26 -0
  156. package/src/providers/cohere/index.ts +1 -0
  157. package/src/providers/groq/canonical.ts +21 -0
  158. package/src/providers/groq/index.ts +1 -0
  159. package/src/providers/openai/canonical.ts +16 -0
  160. package/src/providers/openai/index.ts +1 -0
  161. package/src/providers/registry.test.ts +12 -10
  162. package/src/providers/registry.ts +43 -43
  163. package/src/providers/types.ts +1 -0
  164. package/src/providers/vertex/canonical.ts +17 -0
  165. package/src/providers/vertex/index.ts +1 -0
  166. package/src/providers/voyage/canonical.ts +16 -0
  167. package/src/providers/voyage/index.ts +1 -0
  168. package/src/types.ts +64 -28
  169. package/src/utils/errors.ts +2 -0
  170. package/src/utils/preset.ts +2 -6
  171. package/src/utils/response.ts +15 -0
  172. package/dist/models/presets/claude.d.ts +0 -1165
  173. package/dist/models/presets/claude.js +0 -40
  174. package/dist/models/presets/cohere.d.ts +0 -383
  175. package/dist/models/presets/cohere.js +0 -26
  176. package/dist/models/presets/gpt-oss.d.ts +0 -779
  177. package/dist/models/presets/gpt-oss.js +0 -40
  178. package/dist/models/presets/llama.d.ts +0 -1400
  179. package/dist/providers/canonical/anthropic.d.ts +0 -25
  180. package/dist/providers/canonical/anthropic.js +0 -14
  181. package/dist/providers/canonical/bedrock.d.ts +0 -26
  182. package/dist/providers/canonical/cohere.d.ts +0 -17
  183. package/dist/providers/canonical/groq.d.ts +0 -17
  184. package/dist/providers/canonical/groq.js +0 -10
  185. package/dist/providers/canonical/openai.d.ts +0 -17
  186. package/dist/providers/canonical/openai.js +0 -8
  187. package/dist/providers/canonical/vertex.d.ts +0 -17
  188. package/dist/providers/canonical/vertex.js +0 -10
  189. package/dist/providers/canonical/voyage.d.ts +0 -17
  190. package/dist/providers/canonical/voyage.js +0 -8
  191. package/dist/utils/hooks.d.ts +0 -2
  192. package/src/models/presets/claude.ts +0 -59
  193. package/src/models/presets/cohere.ts +0 -37
  194. package/src/models/presets/gpt-oss.ts +0 -55
  195. package/src/providers/canonical/anthropic.ts +0 -32
  196. package/src/providers/canonical/cohere.ts +0 -36
  197. package/src/providers/canonical/groq.ts +0 -25
  198. package/src/providers/canonical/openai.ts +0 -16
  199. package/src/providers/canonical/vertex.ts +0 -18
  200. package/src/providers/canonical/voyage.ts +0 -16
  201. package/dist/models/{presets/voyage.js → voyage/presets.js} +10 -10
  202. package/src/models/{presets/voyage.ts → voyage/presets.ts} +10 -10
@@ -1,19 +1,29 @@
1
- import { jsonSchema, tool } from "ai";
1
+ import { jsonSchema, JsonToSseTransformStream, tool } from "ai";
2
2
  import { OpenAIError } from "../../utils/errors";
3
+ import { mergeResponseInit } from "../../utils/response";
3
4
  // --- Request Flow ---
4
- export function transformCompletionsInputs(params) {
5
- const { messages, tools, tool_choice, temperature = 1, ...rest } = params;
5
+ export function convertToTextCallOptions(params) {
6
+ const { messages, tools, tool_choice, temperature, max_tokens, max_completion_tokens, reasoning_effort, reasoning, frequency_penalty, presence_penalty, seed, stop, top_p, ...rest } = params;
6
7
  return {
7
- messages: fromCompletionsMessages(messages),
8
- tools: fromCompletionsTools(tools),
9
- toolChoice: fromCompletionsToolChoice(tool_choice),
8
+ messages: convertToModelMessages(messages),
9
+ tools: convertToToolSet(tools),
10
+ toolChoice: convertToToolChoice(tool_choice),
10
11
  temperature,
12
+ maxOutputTokens: max_completion_tokens ?? max_tokens,
13
+ frequencyPenalty: frequency_penalty,
14
+ presencePenalty: presence_penalty,
15
+ seed,
16
+ stopSequences: stop ? (Array.isArray(stop) ? stop : [stop]) : undefined,
17
+ topP: top_p,
11
18
  providerOptions: {
12
- openAICompat: rest,
19
+ unknown: {
20
+ ...rest,
21
+ ...parseReasoningOptions(reasoning_effort, reasoning),
22
+ },
13
23
  },
14
24
  };
15
25
  }
16
- export function fromCompletionsMessages(messages) {
26
+ export function convertToModelMessages(messages) {
17
27
  const modelMessages = [];
18
28
  const toolById = indexToolMessages(messages);
19
29
  for (const message of messages) {
@@ -24,11 +34,11 @@ export function fromCompletionsMessages(messages) {
24
34
  continue;
25
35
  }
26
36
  if (message.role === "user") {
27
- modelMessages.push(fromCompletionsUserMessage(message));
37
+ modelMessages.push(fromChatCompletionsUserMessage(message));
28
38
  continue;
29
39
  }
30
- modelMessages.push(fromCompletionsAssistantMessage(message));
31
- const toolResult = fromCompletionsToolResultMessage(message, toolById);
40
+ modelMessages.push(fromChatCompletionsAssistantMessage(message));
41
+ const toolResult = fromChatCompletionsToolResultMessage(message, toolById);
32
42
  if (toolResult)
33
43
  modelMessages.push(toolResult);
34
44
  }
@@ -42,15 +52,15 @@ function indexToolMessages(messages) {
42
52
  }
43
53
  return map;
44
54
  }
45
- export function fromCompletionsUserMessage(message) {
55
+ export function fromChatCompletionsUserMessage(message) {
46
56
  return {
47
57
  role: "user",
48
58
  content: Array.isArray(message.content)
49
- ? fromCompletionsContent(message.content)
59
+ ? fromChatCompletionsContent(message.content)
50
60
  : message.content,
51
61
  };
52
62
  }
53
- export function fromCompletionsAssistantMessage(message) {
63
+ export function fromChatCompletionsAssistantMessage(message) {
54
64
  const { tool_calls, role, content } = message;
55
65
  if (!tool_calls || tool_calls.length === 0) {
56
66
  return {
@@ -71,7 +81,7 @@ export function fromCompletionsAssistantMessage(message) {
71
81
  }),
72
82
  };
73
83
  }
74
- export function fromCompletionsToolResultMessage(message, toolById) {
84
+ export function fromChatCompletionsToolResultMessage(message, toolById) {
75
85
  const toolCalls = message.tool_calls ?? [];
76
86
  if (toolCalls.length === 0)
77
87
  return undefined;
@@ -89,7 +99,7 @@ export function fromCompletionsToolResultMessage(message, toolById) {
89
99
  }
90
100
  return toolResultParts.length > 0 ? { role: "tool", content: toolResultParts } : undefined;
91
101
  }
92
- export function fromCompletionsContent(content) {
102
+ export function fromChatCompletionsContent(content) {
93
103
  return content.map((part) => {
94
104
  if (part.type === "image_url") {
95
105
  const url = part.image_url.url;
@@ -126,7 +136,7 @@ export function fromCompletionsContent(content) {
126
136
  };
127
137
  }
128
138
  if (part.type === "file") {
129
- const { data, media_type } = part.file;
139
+ let { data, media_type, filename } = part.file;
130
140
  return media_type.startsWith("image/")
131
141
  ? {
132
142
  type: "image",
@@ -136,13 +146,14 @@ export function fromCompletionsContent(content) {
136
146
  : {
137
147
  type: "file",
138
148
  data: Buffer.from(data, "base64"),
149
+ filename,
139
150
  mediaType: media_type,
140
151
  };
141
152
  }
142
153
  return part;
143
154
  });
144
155
  }
145
- export const fromCompletionsTools = (tools) => {
156
+ export const convertToToolSet = (tools) => {
146
157
  if (!tools) {
147
158
  return;
148
159
  }
@@ -155,7 +166,7 @@ export const fromCompletionsTools = (tools) => {
155
166
  }
156
167
  return toolSet;
157
168
  };
158
- export const fromCompletionsToolChoice = (toolChoice) => {
169
+ export const convertToToolChoice = (toolChoice) => {
159
170
  if (!toolChoice) {
160
171
  return undefined;
161
172
  }
@@ -175,9 +186,19 @@ function parseToolOutput(content) {
175
186
  return { type: "text", value: content };
176
187
  }
177
188
  }
189
+ function parseReasoningOptions(reasoning_effort, reasoning) {
190
+ const reasoningOptions = {};
191
+ if (reasoning)
192
+ reasoningOptions["reasoning"] = reasoning;
193
+ if (reasoning_effort !== undefined) {
194
+ (reasoningOptions["reasoning"] ??= {})["effort"] = reasoning_effort;
195
+ reasoningOptions["reasoningEffort"] = reasoning_effort;
196
+ }
197
+ return reasoningOptions;
198
+ }
178
199
  // --- Response Flow ---
179
- export function toCompletions(result, model) {
180
- const finish_reason = toCompletionsFinishReason(result.finishReason);
200
+ export function toChatCompletions(result, model) {
201
+ const finish_reason = toChatCompletionsFinishReason(result.finishReason);
181
202
  return {
182
203
  id: "chatcmpl-" + crypto.randomUUID(),
183
204
  object: "chat.completion",
@@ -186,49 +207,56 @@ export function toCompletions(result, model) {
186
207
  choices: [
187
208
  {
188
209
  index: 0,
189
- message: toCompletionsMessage(result),
210
+ message: toChatCompletionsAssistantMessage(result),
190
211
  finish_reason,
191
212
  },
192
213
  ],
193
- usage: result.usage && toCompletionsUsage(result.usage),
194
- providerMetadata: result.providerMetadata,
214
+ usage: result.totalUsage ? toChatCompletionsUsage(result.totalUsage) : null,
215
+ provider_metadata: result.providerMetadata,
195
216
  };
196
217
  }
197
- export function createCompletionsResponse(result, model) {
198
- return new Response(JSON.stringify(toCompletions(result, model)), {
199
- headers: { "Content-Type": "application/json" },
200
- });
218
+ export function toChatCompletionsResponse(result, model, responseInit) {
219
+ return new Response(JSON.stringify(toChatCompletions(result, model)), mergeResponseInit({ "Content-Type": "application/json" }, responseInit));
201
220
  }
202
- export function toCompletionsStream(result, model) {
221
+ export function toChatCompletionsStream(result, model) {
203
222
  return result.fullStream
204
- .pipeThrough(new CompletionsStream(model))
205
- .pipeThrough(new SSETransformStream())
223
+ .pipeThrough(new ChatCompletionsStream(model))
224
+ .pipeThrough(new JsonToSseTransformStream())
206
225
  .pipeThrough(new TextEncoderStream());
207
226
  }
208
- export function createCompletionsStreamResponse(result, model) {
209
- return new Response(toCompletionsStream(result, model), {
210
- headers: {
211
- "Content-Type": "text/event-stream",
212
- "Cache-Control": "no-cache",
213
- Connection: "keep-alive",
214
- },
215
- });
227
+ export function toChatCompletionsStreamResponse(result, model, responseInit) {
228
+ return new Response(toChatCompletionsStream(result, model), mergeResponseInit({
229
+ "Content-Type": "text/event-stream",
230
+ "Cache-Control": "no-cache",
231
+ Connection: "keep-alive",
232
+ }, responseInit));
216
233
  }
217
- export class CompletionsStream extends TransformStream {
234
+ export class ChatCompletionsStream extends TransformStream {
218
235
  constructor(model) {
219
236
  const streamId = `chatcmpl-${crypto.randomUUID()}`;
220
237
  const creationTime = Math.floor(Date.now() / 1000);
221
238
  let toolCallIndexCounter = 0;
222
- const createChunk = (delta, finish_reason, usage) => ({
239
+ let lastProviderMetadata;
240
+ const createChunk = (delta, finish_reason, usage, provider_metadata) => ({
223
241
  id: streamId,
224
242
  object: "chat.completion.chunk",
225
243
  created: creationTime,
226
244
  model,
227
- choices: [{ index: 0, delta, finish_reason }],
228
- ...(usage ? { usage } : {}),
245
+ choices: [
246
+ {
247
+ index: 0,
248
+ delta,
249
+ finish_reason: finish_reason ?? null,
250
+ },
251
+ ],
252
+ ...(usage ? { usage } : { usage: null }),
253
+ ...(provider_metadata === undefined ? {} : { provider_metadata }),
229
254
  });
230
255
  super({
231
256
  transform(part, controller) {
257
+ if ("providerMetadata" in part && part.providerMetadata !== undefined) {
258
+ lastProviderMetadata = part.providerMetadata;
259
+ }
232
260
  switch (part.type) {
233
261
  case "text-delta": {
234
262
  controller.enqueue(createChunk({ role: "assistant", content: part.text }));
@@ -242,7 +270,7 @@ export class CompletionsStream extends TransformStream {
242
270
  controller.enqueue(createChunk({
243
271
  tool_calls: [
244
272
  {
245
- ...toCompletionsToolCall(part.toolCallId, part.toolName, part.input),
273
+ ...toChatCompletionsToolCall(part.toolCallId, part.toolName, part.input),
246
274
  index: toolCallIndexCounter++,
247
275
  },
248
276
  ],
@@ -250,7 +278,7 @@ export class CompletionsStream extends TransformStream {
250
278
  break;
251
279
  }
252
280
  case "finish": {
253
- controller.enqueue(createChunk({}, toCompletionsFinishReason(part.finishReason), toCompletionsUsage(part.totalUsage)));
281
+ controller.enqueue(createChunk({}, toChatCompletionsFinishReason(part.finishReason), toChatCompletionsUsage(part.totalUsage), lastProviderMetadata));
254
282
  break;
255
283
  }
256
284
  case "error": {
@@ -265,25 +293,13 @@ export class CompletionsStream extends TransformStream {
265
293
  });
266
294
  }
267
295
  }
268
- export class SSETransformStream extends TransformStream {
269
- constructor() {
270
- super({
271
- transform(chunk, controller) {
272
- controller.enqueue(`data: ${JSON.stringify(chunk)}\n\n`);
273
- },
274
- flush(controller) {
275
- controller.enqueue("data: [DONE]\n\n");
276
- },
277
- });
278
- }
279
- }
280
- export const toCompletionsMessage = (result) => {
296
+ export const toChatCompletionsAssistantMessage = (result) => {
281
297
  const message = {
282
298
  role: "assistant",
283
299
  content: null,
284
300
  };
285
301
  if (result.toolCalls && result.toolCalls.length > 0) {
286
- message.tool_calls = result.toolCalls.map((toolCall) => toCompletionsToolCall(toolCall.toolCallId, toolCall.toolName, toolCall.input));
302
+ message.tool_calls = result.toolCalls.map((toolCall) => toChatCompletionsToolCall(toolCall.toolCallId, toolCall.toolName, toolCall.input));
287
303
  }
288
304
  for (const part of result.content) {
289
305
  if (part.type === "text") {
@@ -296,22 +312,32 @@ export const toCompletionsMessage = (result) => {
296
312
  }
297
313
  return message;
298
314
  };
299
- export function toCompletionsUsage(usage) {
300
- if (!usage)
301
- return undefined;
315
+ export function toChatCompletionsUsage(usage) {
302
316
  return {
303
- prompt_tokens: usage.inputTokens ?? 0,
304
- completion_tokens: usage.outputTokens ?? 0,
305
- total_tokens: usage.totalTokens ?? (usage.inputTokens ?? 0) + (usage.outputTokens ?? 0),
306
- completion_tokens_details: {
307
- reasoning_tokens: usage.outputTokenDetails.reasoningTokens ?? 0,
308
- },
309
- prompt_tokens_details: {
310
- cached_tokens: usage.inputTokenDetails.cacheReadTokens ?? 0,
311
- },
317
+ ...(usage.inputTokens !== undefined && {
318
+ prompt_tokens: usage.inputTokens,
319
+ }),
320
+ ...(usage.outputTokens !== undefined && {
321
+ completion_tokens: usage.outputTokens,
322
+ }),
323
+ ...((usage.totalTokens !== undefined ||
324
+ usage.inputTokens !== undefined ||
325
+ usage.outputTokens !== undefined) && {
326
+ total_tokens: usage.totalTokens ?? (usage.inputTokens ?? 0) + (usage.outputTokens ?? 0),
327
+ }),
328
+ ...(usage.outputTokenDetails?.reasoningTokens !== undefined && {
329
+ completion_tokens_details: {
330
+ reasoning_tokens: usage.outputTokenDetails.reasoningTokens,
331
+ },
332
+ }),
333
+ ...(usage.inputTokenDetails?.cacheReadTokens !== undefined && {
334
+ prompt_tokens_details: {
335
+ cached_tokens: usage.inputTokenDetails.cacheReadTokens,
336
+ },
337
+ }),
312
338
  };
313
339
  }
314
- export function toCompletionsToolCall(id, name, args) {
340
+ export function toChatCompletionsToolCall(id, name, args) {
315
341
  return {
316
342
  id,
317
343
  type: "function",
@@ -321,7 +347,7 @@ export function toCompletionsToolCall(id, name, args) {
321
347
  },
322
348
  };
323
349
  }
324
- export const toCompletionsFinishReason = (finishReason) => {
350
+ export const toChatCompletionsFinishReason = (finishReason) => {
325
351
  if (finishReason === "error" || finishReason === "other") {
326
352
  return "stop";
327
353
  }
@@ -1,82 +1,88 @@
1
- import { generateText, streamText } from "ai";
1
+ import { generateText, streamText, wrapLanguageModel } from "ai";
2
2
  import * as z from "zod/mini";
3
- import { parseConfig } from "../../config";
3
+ import { withLifecycle } from "../../lifecycle";
4
+ import { modelMiddlewareMatcher } from "../../middleware/matcher";
4
5
  import { resolveProvider } from "../../providers/registry";
5
6
  import { createErrorResponse } from "../../utils/errors";
6
- import { withHooks } from "../../utils/hooks";
7
- import { transformCompletionsInputs, createCompletionsResponse, createCompletionsStreamResponse, } from "./converters";
8
- import { CompletionsBodySchema } from "./schema";
7
+ import { convertToTextCallOptions, toChatCompletionsResponse, toChatCompletionsStreamResponse, } from "./converters";
8
+ import { ChatCompletionsBodySchema } from "./schema";
9
9
  export const chatCompletions = (config) => {
10
- const { providers, models, hooks } = parseConfig(config);
11
- const handler = async (req) => {
12
- if (req.method !== "POST") {
10
+ const hooks = config.hooks;
11
+ const handler = async (ctx) => {
12
+ if (!ctx.request || ctx.request.method !== "POST") {
13
13
  return createErrorResponse("METHOD_NOT_ALLOWED", "Method Not Allowed", 405);
14
14
  }
15
15
  let body;
16
16
  try {
17
- body = await req.json();
17
+ body = await ctx.request.json();
18
18
  }
19
19
  catch {
20
20
  return createErrorResponse("BAD_REQUEST", "Invalid JSON", 400);
21
21
  }
22
- const parsed = CompletionsBodySchema.safeParse(body);
22
+ const parsed = ChatCompletionsBodySchema.safeParse(body);
23
23
  if (!parsed.success) {
24
24
  return createErrorResponse("UNPROCESSABLE_ENTITY", "Validation error", 422, z.prettifyError(parsed.error));
25
25
  }
26
- const { model: modelId, stream, ...inputs } = parsed.data;
27
- let resolvedModelId;
26
+ ctx.body = parsed.data;
27
+ let stream, inputs;
28
+ ({ model: ctx.modelId, stream, ...inputs } = parsed.data);
28
29
  try {
29
- resolvedModelId = (await hooks?.resolveModelId?.({ modelId })) ?? modelId;
30
+ ctx.resolvedModelId = (await hooks?.resolveModelId?.(ctx)) ?? ctx.modelId;
30
31
  }
31
32
  catch (error) {
32
33
  return createErrorResponse("BAD_REQUEST", error, 400);
33
34
  }
34
- let textOptions;
35
+ ctx.operation = "text";
35
36
  try {
36
- textOptions = transformCompletionsInputs(inputs);
37
+ const override = await hooks?.resolveProvider?.(ctx);
38
+ ctx.provider =
39
+ override ??
40
+ resolveProvider({
41
+ providers: ctx.providers,
42
+ models: ctx.models,
43
+ modelId: ctx.resolvedModelId,
44
+ operation: ctx.operation,
45
+ });
37
46
  }
38
47
  catch (error) {
39
48
  return createErrorResponse("BAD_REQUEST", error, 400);
40
49
  }
41
- let provider;
50
+ const languageModel = ctx.provider.languageModel(ctx.resolvedModelId);
51
+ let textOptions;
42
52
  try {
43
- const args = {
44
- providers,
45
- models,
46
- modelId: resolvedModelId,
47
- operation: "text",
48
- };
49
- const override = await hooks?.resolveProvider?.(args);
50
- provider = override ?? resolveProvider(args);
53
+ textOptions = convertToTextCallOptions(inputs);
51
54
  }
52
55
  catch (error) {
53
56
  return createErrorResponse("BAD_REQUEST", error, 400);
54
57
  }
55
- const languageModel = provider.languageModel(resolvedModelId);
58
+ const languageModelWithMiddleware = wrapLanguageModel({
59
+ model: languageModel,
60
+ middleware: modelMiddlewareMatcher.for(ctx.resolvedModelId, languageModel.provider),
61
+ });
56
62
  if (stream) {
57
63
  let result;
58
64
  try {
59
65
  result = streamText({
60
- model: languageModel,
66
+ model: languageModelWithMiddleware,
61
67
  ...textOptions,
62
68
  });
63
69
  }
64
70
  catch (error) {
65
71
  return createErrorResponse("INTERNAL_SERVER_ERROR", error, 500);
66
72
  }
67
- return createCompletionsStreamResponse(result, modelId);
73
+ return toChatCompletionsStreamResponse(result, ctx.modelId);
68
74
  }
69
75
  let result;
70
76
  try {
71
77
  result = await generateText({
72
- model: languageModel,
78
+ model: languageModelWithMiddleware,
73
79
  ...textOptions,
74
80
  });
75
81
  }
76
82
  catch (error) {
77
83
  return createErrorResponse("INTERNAL_SERVER_ERROR", error, 500);
78
84
  }
79
- return createCompletionsResponse(result, modelId);
85
+ return toChatCompletionsResponse(result, ctx.modelId);
80
86
  };
81
- return { handler: withHooks(hooks, handler) };
87
+ return { handler: withLifecycle(handler, config) };
82
88
  };