@hebo-ai/gateway 0.1.2 → 0.2.1

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 (214) hide show
  1. package/README.md +273 -162
  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 +112 -73
  5. package/dist/endpoints/chat-completions/handler.js +44 -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 +40 -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 +10 -13
  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 +50 -0
  26. package/dist/middleware/matcher.d.ts +19 -0
  27. package/dist/middleware/matcher.js +82 -0
  28. package/dist/middleware/utils.d.ts +2 -0
  29. package/dist/middleware/utils.js +25 -0
  30. package/dist/models/amazon/index.d.ts +2 -0
  31. package/dist/models/amazon/index.js +2 -0
  32. package/dist/models/amazon/middleware.d.ts +3 -0
  33. package/dist/models/amazon/middleware.js +64 -0
  34. package/dist/models/amazon/presets.d.ts +2390 -0
  35. package/dist/models/amazon/presets.js +80 -0
  36. package/dist/models/anthropic/index.d.ts +2 -0
  37. package/dist/models/anthropic/index.js +2 -0
  38. package/dist/models/anthropic/middleware.d.ts +2 -0
  39. package/dist/models/anthropic/middleware.js +46 -0
  40. package/dist/models/anthropic/presets.d.ts +4106 -0
  41. package/dist/models/anthropic/presets.js +113 -0
  42. package/dist/models/catalog.d.ts +3 -1
  43. package/dist/models/catalog.js +3 -2
  44. package/dist/models/cohere/index.d.ts +2 -0
  45. package/dist/models/cohere/index.js +2 -0
  46. package/dist/models/cohere/middleware.d.ts +3 -0
  47. package/dist/models/cohere/middleware.js +60 -0
  48. package/dist/models/cohere/presets.d.ts +2918 -0
  49. package/dist/models/cohere/presets.js +134 -0
  50. package/dist/models/google/index.d.ts +2 -0
  51. package/dist/models/google/index.js +2 -0
  52. package/dist/models/google/middleware.d.ts +6 -0
  53. package/dist/models/google/middleware.js +87 -0
  54. package/dist/models/{presets/gemini.d.ts → google/presets.d.ts} +400 -174
  55. package/dist/models/{presets/gemini.js → google/presets.js} +20 -5
  56. package/dist/models/meta/index.d.ts +1 -0
  57. package/dist/models/meta/index.js +1 -0
  58. package/dist/models/meta/presets.d.ts +3254 -0
  59. package/dist/models/{presets/llama.js → meta/presets.js} +44 -7
  60. package/dist/models/openai/index.d.ts +2 -0
  61. package/dist/models/openai/index.js +2 -0
  62. package/dist/models/openai/middleware.d.ts +3 -0
  63. package/dist/models/openai/middleware.js +45 -0
  64. package/dist/models/openai/presets.d.ts +6252 -0
  65. package/dist/models/openai/presets.js +206 -0
  66. package/dist/models/types.d.ts +3 -3
  67. package/dist/models/types.js +27 -0
  68. package/dist/models/voyage/index.d.ts +2 -0
  69. package/dist/models/voyage/index.js +2 -0
  70. package/dist/models/voyage/middleware.d.ts +2 -0
  71. package/dist/models/voyage/middleware.js +18 -0
  72. package/dist/models/{presets/voyage.d.ts → voyage/presets.d.ts} +322 -323
  73. package/dist/providers/anthropic/canonical.d.ts +3 -0
  74. package/dist/providers/anthropic/canonical.js +9 -0
  75. package/dist/providers/anthropic/index.d.ts +1 -0
  76. package/dist/providers/anthropic/index.js +1 -0
  77. package/dist/providers/bedrock/canonical.d.ts +17 -0
  78. package/dist/providers/bedrock/canonical.js +59 -0
  79. package/dist/providers/bedrock/index.d.ts +1 -0
  80. package/dist/providers/bedrock/index.js +1 -0
  81. package/dist/providers/cohere/canonical.d.ts +3 -0
  82. package/dist/providers/{canonical/cohere.js → cohere/canonical.js} +6 -6
  83. package/dist/providers/cohere/index.d.ts +1 -0
  84. package/dist/providers/cohere/index.js +1 -0
  85. package/dist/providers/groq/canonical.d.ts +3 -0
  86. package/dist/providers/groq/canonical.js +12 -0
  87. package/dist/providers/groq/index.d.ts +1 -0
  88. package/dist/providers/groq/index.js +1 -0
  89. package/dist/providers/openai/canonical.d.ts +3 -0
  90. package/dist/providers/openai/canonical.js +8 -0
  91. package/dist/providers/openai/index.d.ts +1 -0
  92. package/dist/providers/openai/index.js +1 -0
  93. package/dist/providers/registry.d.ts +17 -26
  94. package/dist/providers/registry.js +26 -29
  95. package/dist/providers/types.d.ts +1 -1
  96. package/dist/providers/types.js +1 -0
  97. package/dist/providers/vertex/canonical.d.ts +3 -0
  98. package/dist/providers/vertex/canonical.js +8 -0
  99. package/dist/providers/vertex/index.d.ts +1 -0
  100. package/dist/providers/vertex/index.js +1 -0
  101. package/dist/providers/voyage/canonical.d.ts +3 -0
  102. package/dist/providers/voyage/canonical.js +7 -0
  103. package/dist/providers/voyage/index.d.ts +1 -0
  104. package/dist/providers/voyage/index.js +1 -0
  105. package/dist/types.d.ts +66 -30
  106. package/dist/utils/errors.d.ts +9 -0
  107. package/dist/utils/errors.js +11 -0
  108. package/dist/utils/preset.d.ts +1 -7
  109. package/dist/utils/preset.js +1 -1
  110. package/dist/utils/response.d.ts +1 -0
  111. package/dist/utils/response.js +10 -0
  112. package/package.json +81 -70
  113. package/src/config.ts +2 -18
  114. package/src/endpoints/chat-completions/converters.test.ts +39 -0
  115. package/src/endpoints/chat-completions/converters.ts +208 -112
  116. package/src/endpoints/chat-completions/handler.test.ts +47 -18
  117. package/src/endpoints/chat-completions/handler.ts +54 -35
  118. package/src/endpoints/chat-completions/schema.ts +161 -88
  119. package/src/endpoints/embeddings/converters.ts +15 -11
  120. package/src/endpoints/embeddings/handler.test.ts +27 -30
  121. package/src/endpoints/embeddings/handler.ts +49 -28
  122. package/src/endpoints/embeddings/schema.ts +10 -10
  123. package/src/endpoints/models/converters.ts +23 -15
  124. package/src/endpoints/models/handler.test.ts +26 -29
  125. package/src/endpoints/models/handler.ts +10 -12
  126. package/src/endpoints/models/schema.ts +26 -20
  127. package/src/gateway.ts +10 -24
  128. package/src/index.ts +3 -0
  129. package/src/lifecycle.ts +67 -0
  130. package/src/middleware/common.ts +74 -0
  131. package/src/middleware/matcher.ts +115 -0
  132. package/src/middleware/utils.ts +32 -0
  133. package/src/models/amazon/index.ts +2 -0
  134. package/src/models/amazon/middleware.test.ts +133 -0
  135. package/src/models/amazon/middleware.ts +78 -0
  136. package/src/models/amazon/presets.ts +104 -0
  137. package/src/models/anthropic/index.ts +2 -0
  138. package/src/models/anthropic/middleware.test.ts +263 -0
  139. package/src/models/anthropic/middleware.ts +57 -0
  140. package/src/models/anthropic/presets.ts +161 -0
  141. package/src/models/catalog.ts +10 -2
  142. package/src/models/cohere/index.ts +2 -0
  143. package/src/models/cohere/middleware.test.ts +138 -0
  144. package/src/models/cohere/middleware.ts +76 -0
  145. package/src/models/cohere/presets.ts +186 -0
  146. package/src/models/google/index.ts +2 -0
  147. package/src/models/google/middleware.test.ts +197 -0
  148. package/src/models/google/middleware.ts +111 -0
  149. package/src/models/{presets/gemini.ts → google/presets.ts} +25 -5
  150. package/src/models/meta/index.ts +1 -0
  151. package/src/models/{presets/llama.ts → meta/presets.ts} +68 -7
  152. package/src/models/openai/index.ts +2 -0
  153. package/src/models/openai/middleware.test.ts +125 -0
  154. package/src/models/openai/middleware.ts +56 -0
  155. package/src/models/openai/presets.ts +269 -0
  156. package/src/models/types.ts +29 -2
  157. package/src/models/voyage/index.ts +2 -0
  158. package/src/models/voyage/middleware.test.ts +28 -0
  159. package/src/models/voyage/middleware.ts +23 -0
  160. package/src/providers/anthropic/canonical.ts +17 -0
  161. package/src/providers/anthropic/index.ts +1 -0
  162. package/src/providers/bedrock/canonical.ts +85 -0
  163. package/src/providers/bedrock/index.ts +1 -0
  164. package/src/providers/cohere/canonical.ts +26 -0
  165. package/src/providers/cohere/index.ts +1 -0
  166. package/src/providers/groq/canonical.ts +21 -0
  167. package/src/providers/groq/index.ts +1 -0
  168. package/src/providers/openai/canonical.ts +16 -0
  169. package/src/providers/openai/index.ts +1 -0
  170. package/src/providers/registry.test.ts +12 -10
  171. package/src/providers/registry.ts +51 -46
  172. package/src/providers/types.ts +1 -0
  173. package/src/providers/vertex/canonical.ts +17 -0
  174. package/src/providers/vertex/index.ts +1 -0
  175. package/src/providers/voyage/canonical.ts +16 -0
  176. package/src/providers/voyage/index.ts +1 -0
  177. package/src/types.ts +77 -28
  178. package/src/utils/errors.ts +13 -0
  179. package/src/utils/preset.ts +2 -6
  180. package/src/utils/response.ts +15 -0
  181. package/dist/models/presets/claude.d.ts +0 -1165
  182. package/dist/models/presets/claude.js +0 -40
  183. package/dist/models/presets/cohere.d.ts +0 -383
  184. package/dist/models/presets/cohere.js +0 -26
  185. package/dist/models/presets/gpt-oss.d.ts +0 -779
  186. package/dist/models/presets/gpt-oss.js +0 -40
  187. package/dist/models/presets/llama.d.ts +0 -1400
  188. package/dist/providers/canonical/anthropic.d.ts +0 -25
  189. package/dist/providers/canonical/anthropic.js +0 -14
  190. package/dist/providers/canonical/bedrock.d.ts +0 -26
  191. package/dist/providers/canonical/bedrock.js +0 -41
  192. package/dist/providers/canonical/cohere.d.ts +0 -17
  193. package/dist/providers/canonical/groq.d.ts +0 -17
  194. package/dist/providers/canonical/groq.js +0 -10
  195. package/dist/providers/canonical/openai.d.ts +0 -17
  196. package/dist/providers/canonical/openai.js +0 -8
  197. package/dist/providers/canonical/vertex.d.ts +0 -17
  198. package/dist/providers/canonical/vertex.js +0 -10
  199. package/dist/providers/canonical/voyage.d.ts +0 -17
  200. package/dist/providers/canonical/voyage.js +0 -8
  201. package/dist/utils/hooks.d.ts +0 -2
  202. package/src/models/presets/claude.ts +0 -59
  203. package/src/models/presets/cohere.ts +0 -37
  204. package/src/models/presets/gpt-oss.ts +0 -55
  205. package/src/providers/canonical/anthropic.ts +0 -32
  206. package/src/providers/canonical/bedrock.ts +0 -72
  207. package/src/providers/canonical/cohere.ts +0 -36
  208. package/src/providers/canonical/groq.ts +0 -25
  209. package/src/providers/canonical/openai.ts +0 -16
  210. package/src/providers/canonical/vertex.ts +0 -18
  211. package/src/providers/canonical/voyage.ts +0 -16
  212. package/src/utils/hooks.ts +0 -51
  213. package/dist/models/{presets/voyage.js → voyage/presets.js} +10 -10
  214. package/src/models/{presets/voyage.ts → voyage/presets.ts} +10 -10
package/README.md CHANGED
@@ -21,7 +21,7 @@ Hebo Gateway is an open-source, embeddable AI gateway framework built to live in
21
21
  ## Installation
22
22
 
23
23
  ```bash
24
- bun add @hebo-ai/gateway ai @ai-sdk/groq
24
+ bun install @hebo-ai/gateway
25
25
  ```
26
26
 
27
27
  ## Quickstart
@@ -31,33 +31,42 @@ bun add @hebo-ai/gateway ai @ai-sdk/groq
31
31
  Start by creating a gateway instance with at least one provider and a few models.
32
32
 
33
33
  ```ts
34
- import { gateway, createModelCatalog } from "@hebo-ai/gateway";
35
- import { createGroqWithCanonicalIds } from "@hebo-ai/gateway/providers/groq";
36
- import { gptOss20b, gptOss } from "@hebo-ai/gateway/models/gpt-oss";
34
+ import { createGroq } from "@ai-sdk/groq";
35
+ import { gateway, defineModelCatalog } from "@hebo-ai/gateway";
36
+ import { withCanonicalIdsForGroq } from "@hebo-ai/gateway/providers/groq";
37
+ import { gptOss20b, gptOss } from "@hebo-ai/gateway/models/openai";
37
38
 
38
39
  export const gw = gateway({
39
40
  // PROVIDER REGISTRY
40
41
  providers: {
41
- // Any Vercel AI SDK provider +WithCanonicalIds
42
- groq: createGroqWithCanonicalIds({
43
- apiKey: process.env.GROQ_API_KEY,
44
- },
42
+ // Any Vercel AI SDK provider + withCanonicalIdsForX helper
43
+ groq: withCanonicalIdsForGroq(
44
+ createGroq({
45
+ apiKey: process.env.GROQ_API_KEY,
46
+ }),
47
+ ),
45
48
  },
46
49
 
47
50
  // MODEL CATALOG
48
- models: createModelCatalog(
49
- // Choose a preset for common SOTA models
50
- gptOss20b({
51
- providers: ["groq"],
52
- }),
53
- // Or add a whole model family
54
- ...gptOss["all"].map((preset) =>
55
- preset({})
51
+ models: defineModelCatalog(
52
+ // Choose a pre-configured preset for common SOTA models
53
+ gptOss20b,
54
+ // Or add a whole model family with your own provider list
55
+ gptOss["all"].map(
56
+ preset => preset({
57
+ providers: ["groq"],
58
+ })
56
59
  ),
57
60
  ),
58
61
  });
59
62
  ```
60
63
 
64
+ > [!NOTE]
65
+ > Don't forget to install the Groq provider package too: `@ai-sdk/groq`.
66
+
67
+ > [!TIP]
68
+ > Why `withCanonicalIdsForX`? In most cases you want your gateway to route using model IDs that are consistent across providers (e.g. `openai/gpt-oss-20b` rather than `openai.gpt-oss-20b-v1:0`). We call that `Canonical IDs` - they are what enable routing, fallbacks, and policy rules. Without this wrapper, providers only understands their native IDs, which would make cross-provider routing impossible.
69
+
61
70
  ### Mount Route Handlers
62
71
 
63
72
  Hebo Gateway plugs into your favorite web framework. Simply mount the gateway’s `handler` under a prefix, and keep using your existing lifecycle hooks for authentication, logging, observability, and more.
@@ -104,181 +113,105 @@ const { text } = await generateText({
104
113
  console.log(text);
105
114
  ```
106
115
 
107
- ## Framework Support
108
-
109
- Hebo Gateway exposes **WinterCG-compatible** handlers that integrate with almost any existing framework.
110
-
111
- ### ElysiaJS
112
-
113
- `src/index.ts`
114
-
115
- ```ts
116
- import { Elysia } from "elysia";
117
-
118
- const app = new Elysia().mount("/v1/gateway/", gw.handler).listen(3000);
119
-
120
- console.log(`🐒 Hebo Gateway is running with Elysia at ${app.server?.url}`);
121
- ```
122
-
123
- ### Hono
124
-
125
- `src/index.ts`
126
-
127
- ```ts
128
- import { Hono } from "hono";
129
-
130
- export default new Hono().mount("/v1/gateway/", gw.handler);
131
-
132
- console.log(`🐒 Hebo Gateway is running with Hono framework`);
133
- ```
134
-
135
- ### Next.js (App Router)
136
-
137
- `app/api/gateway/[...all]/route.ts`
138
-
139
- ```ts
140
- const gw = gateway({
141
- // Required: add `basePath` to your gateway config
142
- basePath: "/api/gateway",
143
- // ...
144
- });
145
-
146
- export const POST = gw.handler, GET = gw.handler;
147
- ```
148
-
149
- ### Next.js (Pages Router)
150
-
151
- `pages/api/gateway/[...all].ts`
152
-
153
- ```ts
154
- // Requires `@mjackson/node-fetch-server` npm package
155
- import { createRequest, sendResponse } from "@mjackson/node-fetch-server";
156
-
157
- const gw = gateway({
158
- // Required: add `basePath` to your gateway config
159
- basePath: "/api/gateway",
160
- // ...
161
- });
162
-
163
- export default async function handler(req, res) {
164
- await sendResponse(res, await gw.handler(createRequest(req, res)));
165
- }
166
- ```
167
-
168
- ### TanStack Start
169
-
170
- `routes/api/$.ts`
171
-
172
- ```ts
173
- const gw = gateway({
174
- // Required: add `basePath` to your gateway config
175
- basePath: "/api/gateway",
176
- // ...
177
- });
178
-
179
- export const Route = createFileRoute("/api/$")({
180
- server: {
181
- handlers: {
182
- GET: ({ request }) => gw.handler(request),
183
- POST: ({ request }) => gw.handler(request),
184
- },
185
- },
186
- });
187
- ```
188
-
189
116
  ## Configuration Reference
190
117
 
191
118
  ### Providers
192
119
 
193
120
  Hebo Gateway’s provider registry accepts any **Vercel AI SDK Provider**. For Hebo to be able to route a model across different providers, the names need to be canonicalized to a common form, for example 'openai/gpt-4.1-mini' instead of 'gpt-4.1-mini'.
194
121
 
195
- Out-of-the-box canonical providers:
122
+ We currently provide out-of-the-box canonical providers for: `Bedrock`, `Anthropic`, `Cohere`, `Vertex`, `Groq`, `OpenAI`, and `Voyage`. Import the helper from the matching package path:
196
123
 
197
- - Amazon Bedrock (`createAmazonBedrockWithCanonicalIds`): `@hebo-ai/gateway/providers/bedrock`
198
- - Anthropic (`createAnthropicWithCanonicalIds`): `@hebo-ai/gateway/providers/anthropic`
199
- - Cohere (`createCohereWithCanonicalIds`): `@hebo-ai/gateway/providers/cohere`
200
- - Google Vertex AI (`createVertexWithCanonicalIds`): `@hebo-ai/gateway/providers/vertex`
201
- - Groq (`createGroqWithCanonicalIds`): `@hebo-ai/gateway/providers/groq`
202
- - OpenAI (`createOpenAIWithCanonicalIds`): `@hebo-ai/gateway/providers/openai`
203
- - Voyage (`createVoyageWithCanonicalIds`): `@hebo-ai/gateway/providers/voyage`
124
+ ```ts
125
+ // pattern: @hebo-ai/gateway/providers/<provider>
126
+ import { withCanonicalIdsForGroq } from "@hebo-ai/gateway/providers/groq";
127
+ ```
204
128
 
205
129
  If an adapter is not yet provided, you can create your own by wrapping the provider instance with the `withCanonicalIds` helper and define your custom canonicalization mapping & rules.
206
130
 
207
131
  ```ts
208
- import { createOpenAI } from "@ai-sdk/openai";
132
+ import { createAzure } from "@ai-sdk/openai";
209
133
  import {
210
134
  gateway,
211
- createModelCatalog,
212
135
  withCanonicalIds,
213
136
  } from "@hebo-ai/gateway";
214
137
 
215
- const openai = withCanonicalIds(
216
- createOpenAI({ apiKey: process.env.OPENAI_API_KEY }),
217
- {
218
- "openai/gpt-4.1-mini": "gpt-4.1-mini",
219
- "openai/text-embedding-3-small": "text-embedding-3-small",
220
- },
138
+ const azure = withCanonicalIds(
139
+ createAzure({
140
+ resourceName: process.env["AZURE_RESOURCE_NAME"],
141
+ apiKey: process.env["AZURE_API_KEY"]
142
+ }), {
143
+ mapping: {
144
+ "openai/gpt-4.1-mini": "your-gpt-4.1-mini-deployment-name",
145
+ "openai/text-embedding-3-small": "your-embeddings-3-small-deployment-name",
146
+ }},
221
147
  );
222
148
 
223
149
  const gw = gateway({
224
150
  providers: {
225
- openai,
151
+ azure,
226
152
  },
227
- models: createModelCatalog({
153
+ models: {
228
154
  // ...your models pointing at canonical IDs above
229
- }),
155
+ },
230
156
  });
231
157
  ```
232
158
 
233
159
  ### Models
234
160
 
235
- Registering models tells Hebo Gateway which models are available, under which canonical ID and what capabilities they have.
161
+ Register models to tell the gateway what's available, under which canonical ID and what capabilities each one has.
236
162
 
237
163
  #### Model Presets
238
164
 
239
- To simplify the registration, Hebo Gateway ships a set of model presets under `@hebo-ai/gateway/models`. Use these when you want ready-to-use catalog entries with sane defaults for common SOTA models.
165
+ To simplify the registration, Hebo Gateway ships a set of model presets under `@hebo-ai/gateway/models`. Use these when you want ready-to-use catalog entries with sane defaults for common SOTA models.
240
166
 
241
167
  Presets come in two forms:
242
- - Individual presets (e.g. `gptOss20b`, `claudeSonnet45`) for a single model.
243
- - Family presets (e.g. `claude`, `gemini`, `llama`) which group multiple models and expose helpers like `latest`, `all`, and versioned arrays (for example `claude["v4.5"]`).
244
-
245
- Out-of-the-box model presets:
246
-
247
- - **Claude** — `@hebo-ai/gateway/models/claude`
248
- Family: `claude` (`v4.5`, `v4.x`, `latest`, `all`)
249
-
250
- - **Gemini** — `@hebo-ai/gateway/models/gemini`
251
- Family: `gemini` (`v2.5`, `v3-preview`, `v2.x`, `v3.x`, `latest`, `preview`, `all`)
252
168
 
253
- - **GPT-OSS** `@hebo-ai/gateway/models/gpt-oss`
254
- Family: `gptOss` (`v1`, `v1.x`, `latest`, `all`)
255
-
256
- - **Llama** — `@hebo-ai/gateway/models/llama`
257
- Family: `llama` (`v3.1`, `v3.3`, `v4`, `v3.x`, `v4.x`, `latest`, `all`)
258
-
259
- - **Cohere** — `@hebo-ai/gateway/models/cohere`
260
- Family: `cohere` (`v4`, `v4.x`, `latest`, `all`)
261
-
262
- - **Voyage** — `@hebo-ai/gateway/models/voyage`
263
- Family: `voyage` (`v2`, `v3`, `v3.5`, `v4`, `v2.x`, `v3.x`, `v4.x`, `latest`, `all`)
169
+ - Individual presets (e.g. `gptOss20b`, `claudeSonnet45`) for a single model.
170
+ - Family presets (e.g. `claude`, `gemini`, `llama`) which group multiple models and expose helpers like `latest`, `all`, `vX` (e.g. `claude["v4.5"]`).
264
171
 
265
172
  ```ts
266
- import { createModelCatalog } from "@hebo-ai/gateway";
267
- import { gptOss20b } from "@hebo-ai/gateway/models/gpt-oss";
268
- import { claudeSonnet45, claude } from "@hebo-ai/gateway/models/claude";
173
+ import { defineModelCatalog } from "@hebo-ai/gateway";
174
+ import { gptOss20b } from "@hebo-ai/gateway/models/openai";
175
+ import { claudeSonnet45, claude } from "@hebo-ai/gateway/models/anthropic";
269
176
 
270
177
  // Individual preset
271
- const models = createModelCatalog(
178
+ const models = defineModelCatalog(
272
179
  gptOss20b({ providers: ["groq"] }),
273
180
  claudeSonnet45({ providers: ["bedrock"] }),
274
181
  );
275
182
 
276
183
  // Family preset (pick a group and apply the same override to each)
277
- const modelsFromFamily = createModelCatalog(
278
- ...claude["latest"].map((preset) => preset({ providers: ["anthropic"] })),
184
+ const modelsFromFamily = defineModelCatalog(
185
+ claude["latest"].map((preset) => preset({ providers: ["anthropic"] })),
279
186
  );
280
187
  ```
281
188
 
189
+ Out-of-the-box model presets:
190
+
191
+ - **Amazon** — `@hebo-ai/gateway/models/amazon`
192
+ Nova: `nova` (`v1`, `v2`, `v1.x`, `v2.x`, `latest`, `embeddings`, `all`)
193
+
194
+ - **Anthropic** — `@hebo-ai/gateway/models/anthropic`
195
+ Claude: `claude` (`v4.5`, `v4.1`, `v4`, `v3.7`, `v3.5`, `v3`, `v4.x`, `v3.x`, `haiku`, `sonnet`, `opus`, `latest`, `all`)
196
+
197
+ - **Cohere** — `@hebo-ai/gateway/models/cohere`
198
+ Command: `command` (`A`, `R`, `latest`, `all`)
199
+ Embed: `embed` (`v4`, `v3`, `latest`, `all`)
200
+
201
+ - **Google** — `@hebo-ai/gateway/models/google`
202
+ Gemini: `gemini` (`v2.5`, `v3-preview`, `v2.x`, `v3.x`, `embeddings`, `latest`, `preview`, `all`)
203
+
204
+ - **Meta** — `@hebo-ai/gateway/models/meta`
205
+ Llama: `llama` (`v3.1`, `v3.2`, `v3.3`, `v4`, `v3.x`, `v4.x`, `latest`, `all`)
206
+
207
+ - **OpenAI** — `@hebo-ai/gateway/models/openai`
208
+ GPT: `gpt` (`v5`, `v5.1`, `v5.2`, `v5.x`, `chat`, `codex`, `pro`, `latest`, `all`)
209
+ GPT-OSS: `gptOss` (`v1`, `v1.x`, `latest`, `all`)
210
+ Embeddings: `textEmbeddings` (`v3`, `v3.x`, `latest`, `all`)
211
+
212
+ - **Voyage** — `@hebo-ai/gateway/models/voyage`
213
+ Voyage: `voyage` (`v2`, `v3`, `v3.5`, `v4`, `v2.x`, `v3.x`, `v4.x`, `latest`, `all`)
214
+
282
215
  #### User-defined Models
283
216
 
284
217
  As the ecosystem is moving faster than anyone can keep-up with, you can always register your own model entries by following the `CatalogModel` type.
@@ -288,7 +221,7 @@ const gw = gateway({
288
221
  providers: {
289
222
  // ...
290
223
  },
291
- models: createModelCatalog({
224
+ models: {
292
225
  "openai/gpt-5.2": {
293
226
  name: "GPT 5.2",
294
227
  created: "2025-12-11",
@@ -312,13 +245,16 @@ const gw = gateway({
312
245
  }
313
246
  },
314
247
  // ...
315
- }),
248
+ },
316
249
  });
317
250
  ```
318
251
 
252
+ > [!NOTE]
253
+ > The only mandatory property is the `providers` array, everything else is optional metadata.
254
+
319
255
  ### Hooks
320
256
 
321
- Hooks allow you to plug-into the lifecycle of the gateway and enrich it with additional functionality. All hooks are available as async and non-async.
257
+ Hooks allow you to plug-into the lifecycle of the gateway and enrich it with additional functionality, like your actual routing logic. All hooks are available as async and non-async.
322
258
 
323
259
  ```ts
324
260
  const gw = gateway({
@@ -345,25 +281,31 @@ const gw = gateway({
345
281
  },
346
282
  /**
347
283
  * Maps a user-provided model ID or alias to a canonical ID.
284
+ * @param ctx.body The parsed body object with all call parameters.
348
285
  * @param ctx.modelId Incoming model ID.
349
286
  * @returns Canonical model ID or undefined to keep original.
350
287
  */
351
- resolveModelId: async (ctx: { modelId: ModelId }): Promise<ModelId | void> => {
288
+ resolveModelId?: (ctx: {
289
+ body: ChatCompletionsBody | EmbeddingsBody;
290
+ modelId: ModelId;
291
+ }) => ModelId | void | Promise<ModelId | void> {
352
292
  // Example Use Cases:
353
293
  // - Resolve modelAlias to modelId
354
294
  return undefined;
355
295
  },
356
296
  /**
357
297
  * Picks a provider instance for the request.
358
- * @param ctx.providers Provider registry.
298
+ * @param ctx.providers ProviderRegistry from config.
359
299
  * @param ctx.models ModelCatalog from config.
300
+ * @param ctx.body The parsed body object with all call parameters.
360
301
  * @param ctx.modelId Resolved model ID.
361
302
  * @param ctx.operation Operation type ("text" | "embeddings").
362
303
  * @returns ProviderV3 to override, or undefined to use default.
363
304
  */
364
305
  resolveProvider: async (ctx: {
365
- providers: ProviderRegistryProvider;
306
+ providers: ProviderRegistry;
366
307
  models: ModelCatalog;
308
+ body: ChatCompletionsBody | EmbeddingsBody;
367
309
  modelId: ModelId;
368
310
  operation: "text" | "embeddings";
369
311
  }): Promise<ProviderV3 | void> => {
@@ -387,8 +329,172 @@ const gw = gateway({
387
329
  });
388
330
  ```
389
331
 
332
+ The `ctx` object is **readonly for core fields**. Use return values to override request / response and provide modelId / provider instances.
333
+
334
+ > [!TIP]
335
+ > To pass data between hooks, use `ctx.state`. It’s a per-request mutable bag in which you can stash things like auth info, routing decisions, timers, or trace IDs and read them later again in any of the other hooks.
336
+
337
+ ## Framework Support
338
+
339
+ Hebo Gateway exposes **WinterCG-compatible** handlers that integrate with almost any existing framework.
340
+
341
+ ### ElysiaJS
342
+
343
+ `src/index.ts`
344
+
345
+ ```ts
346
+ import { Elysia } from "elysia";
347
+
348
+ const app = new Elysia().mount("/v1/gateway/", gw.handler).listen(3000);
349
+
350
+ console.log(`🐒 Hebo Gateway is running with Elysia at ${app.server?.url}`);
351
+ ```
352
+
353
+ ### Hono
354
+
355
+ `src/index.ts`
356
+
357
+ ```ts
358
+ import { Hono } from "hono";
359
+
360
+ export default new Hono().mount("/v1/gateway/", gw.handler);
361
+
362
+ console.log(`🐒 Hebo Gateway is running with Hono framework`);
363
+ ```
364
+
365
+ ### Next.js (App Router)
366
+
367
+ `app/api/gateway/[...all]/route.ts`
368
+
369
+ ```ts
370
+ const gw = gateway({
371
+ // Required: add `basePath` to your gateway config
372
+ basePath: "/api/gateway",
373
+ // ...
374
+ });
375
+
376
+ export const POST = gw.handler, GET = gw.handler;
377
+ ```
378
+
379
+ ### Next.js (Pages Router)
380
+
381
+ `pages/api/gateway/[...all].ts`
382
+
383
+ ```ts
384
+ // Requires `@mjackson/node-fetch-server` npm package
385
+ import { createRequest, sendResponse } from "@mjackson/node-fetch-server";
386
+
387
+ const gw = gateway({
388
+ // Required: add `basePath` to your gateway config
389
+ basePath: "/api/gateway",
390
+ // ...
391
+ });
392
+
393
+ export default async function handler(req, res) {
394
+ await sendResponse(res, await gw.handler(createRequest(req, res)));
395
+ }
396
+ ```
397
+
398
+ ### TanStack Start
399
+
400
+ `routes/api/$.ts`
401
+
402
+ ```ts
403
+ const gw = gateway({
404
+ // Required: add `basePath` to your gateway config
405
+ basePath: "/api/gateway",
406
+ // ...
407
+ });
408
+
409
+ export const Route = createFileRoute("/api/$")({
410
+ server: {
411
+ handlers: {
412
+ GET: ({ request }) => gw.handler(request),
413
+ POST: ({ request }) => gw.handler(request),
414
+ },
415
+ },
416
+ });
417
+ ```
418
+
419
+ ## OpenAI Extensions
420
+
421
+ ### Reasoning
422
+
423
+ In addition to the official `reasoning_effort` parameter, the chat completions endpoint accepts a `reasoning` object for more fine-grained control of the budget. It's treated as provider-agnostic input and normalized before hitting the upstream model.
424
+
425
+ ```json
426
+ {
427
+ "model": "anthropic/claude-4-sonnet",
428
+ "messages": [{ "role": "user", "content": "Explain the tradeoffs." }],
429
+ "reasoning": { "effort": "medium" }
430
+ }
431
+ ```
432
+
433
+ Normalization rules:
434
+
435
+ - `enabled` -> fall-back to model default if none provided
436
+ - `max_tokens`: fall-back to model default if model supports
437
+ - `effort` -> budget = percentage of `max_tokens`
438
+ - `none`: 0%
439
+ - `minimal`: 10%
440
+ - `low`: 20%
441
+ - `medium`: 50% (default)
442
+ - `high`: 80%
443
+ - `xhigh`: 95%
444
+
445
+ Reasoning output is surfaced as extension to the `completion` object.
446
+
447
+ - When present, it is returned on the assistant message as `reasoning_content`. Reasoning token counts (when available) are returned on `usage.completion_tokens_details.reasoning_tokens`.
448
+ - For stream responses, reasoning text is sent incrementally as `reasoning_content` part (separate from normal text `content` deltas). Token counts land in the final `usage` object on the terminating chunk.
449
+
450
+ Most SDKs handle these fields out-of-the-box.
451
+
390
452
  ## Advanced Usage
391
453
 
454
+ ### Passing Framework State to Hooks
455
+
456
+ You can pass per-request info from your framework into the gateway via the second `state` argument on the handler, then read it in hooks through `ctx.state`.
457
+
458
+ ```ts
459
+ import { Elysia } from "elysia";
460
+ import { gateway } from "@hebo-ai/gateway";
461
+
462
+ const basePath = "/v1/gateway";
463
+
464
+ const gw = gateway({
465
+ basePath,
466
+ providers: {
467
+ // ...
468
+ },
469
+ models: {
470
+ // ...
471
+ },
472
+ hooks: {
473
+ resolveProvider: async (ctx) => {
474
+ // Select provider based on userId
475
+ const user = ctx.state.auth.userId;
476
+ if (user.startsWith("vip:")) {
477
+ return ctx.providers["openai"];
478
+ } else {
479
+ return ctx.providers["groq"];
480
+ }
481
+ },
482
+ },
483
+ });
484
+
485
+ const app = new Elysia()
486
+ .derive(({ headers }) => ({
487
+ auth: {
488
+ userId: headers["x-user-id"],
489
+ },
490
+ }))
491
+ .all(`${basePath}/*`, ({ request, auth }) => gw.handler(request, { auth }), { parse: 'none' })
492
+ .listen(3000);
493
+ ```
494
+
495
+ > [!NOTE]
496
+ > The `parse: 'none'` hook is required to prevent Elysia from consuming the body.
497
+
392
498
  ### Selective Route Mounting
393
499
 
394
500
  If you want to have more flexibility, for example for custom rate limit checks per route, you can also choose to only mount individual routes from the gateway's `routes` property.
@@ -410,14 +516,15 @@ console.log(`🐒 /chat/completions mounted to ${app.server?.url}/chat`);
410
516
  We also provide full schemas, helper functions and types to convert between **OpenAI <> Vercel AI SDK** for advanced use cases like creating your own endpoint. They are available via deep-imports and completely tree-shakeable.
411
517
 
412
518
  ```ts
413
- import { streamText } from "ai";
519
+ import { streamText, wrapLanguageModel } from "ai";
414
520
  import { createGroq } from "@ai-sdk/groq";
415
521
  import * as z from "zod";
416
522
  import {
417
- CompletionsBodySchema,
418
- transformCompletionsInputs,
419
- createCompletionsStreamResponse,
523
+ ChatCompletionsBodySchema,
524
+ convertToTextCallOptions,
525
+ toChatCompletionsStreamResponse,
420
526
  } from "@hebo-ai/gateway/endpoints/chat-completions";
527
+ import { forwardParamsMiddleware } from "@hebo-ai/gateway/middleware/common";
421
528
 
422
529
  const groq = createGroq({ apiKey: process.env.GROQ_API_KEY });
423
530
 
@@ -425,24 +532,28 @@ export async function handler(req: Request): Promise<Response> {
425
532
 
426
533
  const body = await req.json();
427
534
 
428
- const parsed = CompletionsBodySchema.safeParse(body);
535
+ const parsed = ChatCompletionsBodySchema.safeParse(body);
429
536
  if (!parsed.success) {
430
537
  return new Response(z.prettifyError(parsed.error), { status: 422 });
431
538
  }
432
539
 
433
540
  const { model, ...inputs } = parsed.data;
434
541
 
435
- const textOptions = transformCompletionsInputs(inputs);
542
+ const textOptions = convertToTextCallOptions(inputs);
436
543
 
437
544
  const result = await streamText({
438
- model: groq(model),
545
+ model: wrapLanguageModel({
546
+ model: groq(model),
547
+ middleware: forwardParamsMiddleware("groq"),
548
+ }),
439
549
  ...textOptions
440
550
  });
441
551
 
442
- return createCompletionsStreamResponse(result, model);
552
+ return toChatCompletionsStreamResponse(result, model);
443
553
  }
444
554
  ```
445
555
 
446
- Non-streaming versions are available via `createCompletionsResponse`. Equivalent schemas and helper are available in the `embeddings` and `models` endpoints.
556
+ Non-streaming versions are available via `createChatCompletionsResponse`. Equivalent schemas and helpers are available in the `embeddings` and `models` endpoints.
447
557
 
448
- Since Zod v4.3 you can also generate a JSON Schema from any zod object by calling the `.toJSONSchema()` function. This can be useful, for example, to create OpenAPI documentation.
558
+ > [!TIP]
559
+ > Since Zod v4.3 you can generate a JSON Schema from any zod object by calling `z.toJSONSchema(...)`. This is useful for producing OpenAPI documentation from the same source of truth.
package/dist/config.js CHANGED
@@ -1,4 +1,3 @@
1
- import { createProviderRegistry } from "ai";
2
1
  import { kParsed } from "./types";
3
2
  export const parseConfig = (config) => {
4
3
  // If it has been parsed before, just return
@@ -9,22 +8,13 @@ export const parseConfig = (config) => {
9
8
  if (Object.keys(providers).length === 0) {
10
9
  throw new Error("Gateway config error: no providers configured (config.providers is empty).");
11
10
  }
12
- // Initialize ProviderRegistry (if nessecary)
13
- let registry;
14
- if ("languageModel" in providers) {
15
- registry = providers;
16
- }
17
- else {
18
- registry = createProviderRegistry(providers);
19
- }
20
11
  // Strip out providers from models that are not configured
21
- const providerKeys = Object.keys(registry.providers);
22
12
  const parsedModels = {};
23
13
  for (const id in models) {
24
14
  const model = models[id];
25
15
  const kept = [];
26
16
  for (const p of model.providers) {
27
- if (providerKeys.includes(p))
17
+ if (p in providers)
28
18
  kept.push(p);
29
19
  else
30
20
  console.warn(`[models] ${id}: provider "${p}" removed (not configured)`);
@@ -35,5 +25,5 @@ export const parseConfig = (config) => {
35
25
  if (Object.keys(parsedModels).length === 0) {
36
26
  throw new Error("Gateway config error: no models configured (config.models is empty).");
37
27
  }
38
- return { ...config, providers: registry, models: parsedModels, [kParsed]: true };
28
+ return { ...config, providers, models: parsedModels, [kParsed]: true };
39
29
  };