@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.
- package/README.md +273 -162
- package/dist/config.js +2 -12
- package/dist/endpoints/chat-completions/converters.d.ts +28 -24
- package/dist/endpoints/chat-completions/converters.js +112 -73
- package/dist/endpoints/chat-completions/handler.js +44 -30
- package/dist/endpoints/chat-completions/schema.d.ts +394 -272
- package/dist/endpoints/chat-completions/schema.js +124 -57
- package/dist/endpoints/embeddings/converters.d.ts +4 -4
- package/dist/endpoints/embeddings/converters.js +8 -9
- package/dist/endpoints/embeddings/handler.js +40 -26
- package/dist/endpoints/embeddings/schema.d.ts +28 -38
- package/dist/endpoints/embeddings/schema.js +10 -10
- package/dist/endpoints/models/converters.d.ts +2 -2
- package/dist/endpoints/models/converters.js +10 -13
- package/dist/endpoints/models/handler.js +8 -9
- package/dist/endpoints/models/schema.d.ts +37 -31
- package/dist/endpoints/models/schema.js +23 -12
- package/dist/gateway.d.ts +8 -9
- package/dist/gateway.js +7 -10
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/lifecycle.d.ts +2 -0
- package/dist/{utils/hooks.js → lifecycle.js} +16 -8
- package/dist/middleware/common.d.ts +4 -0
- package/dist/middleware/common.js +50 -0
- package/dist/middleware/matcher.d.ts +19 -0
- package/dist/middleware/matcher.js +82 -0
- package/dist/middleware/utils.d.ts +2 -0
- package/dist/middleware/utils.js +25 -0
- package/dist/models/amazon/index.d.ts +2 -0
- package/dist/models/amazon/index.js +2 -0
- package/dist/models/amazon/middleware.d.ts +3 -0
- package/dist/models/amazon/middleware.js +64 -0
- package/dist/models/amazon/presets.d.ts +2390 -0
- package/dist/models/amazon/presets.js +80 -0
- package/dist/models/anthropic/index.d.ts +2 -0
- package/dist/models/anthropic/index.js +2 -0
- package/dist/models/anthropic/middleware.d.ts +2 -0
- package/dist/models/anthropic/middleware.js +46 -0
- package/dist/models/anthropic/presets.d.ts +4106 -0
- package/dist/models/anthropic/presets.js +113 -0
- package/dist/models/catalog.d.ts +3 -1
- package/dist/models/catalog.js +3 -2
- package/dist/models/cohere/index.d.ts +2 -0
- package/dist/models/cohere/index.js +2 -0
- package/dist/models/cohere/middleware.d.ts +3 -0
- package/dist/models/cohere/middleware.js +60 -0
- package/dist/models/cohere/presets.d.ts +2918 -0
- package/dist/models/cohere/presets.js +134 -0
- package/dist/models/google/index.d.ts +2 -0
- package/dist/models/google/index.js +2 -0
- package/dist/models/google/middleware.d.ts +6 -0
- package/dist/models/google/middleware.js +87 -0
- package/dist/models/{presets/gemini.d.ts → google/presets.d.ts} +400 -174
- package/dist/models/{presets/gemini.js → google/presets.js} +20 -5
- package/dist/models/meta/index.d.ts +1 -0
- package/dist/models/meta/index.js +1 -0
- package/dist/models/meta/presets.d.ts +3254 -0
- package/dist/models/{presets/llama.js → meta/presets.js} +44 -7
- package/dist/models/openai/index.d.ts +2 -0
- package/dist/models/openai/index.js +2 -0
- package/dist/models/openai/middleware.d.ts +3 -0
- package/dist/models/openai/middleware.js +45 -0
- package/dist/models/openai/presets.d.ts +6252 -0
- package/dist/models/openai/presets.js +206 -0
- package/dist/models/types.d.ts +3 -3
- package/dist/models/types.js +27 -0
- package/dist/models/voyage/index.d.ts +2 -0
- package/dist/models/voyage/index.js +2 -0
- package/dist/models/voyage/middleware.d.ts +2 -0
- package/dist/models/voyage/middleware.js +18 -0
- package/dist/models/{presets/voyage.d.ts → voyage/presets.d.ts} +322 -323
- package/dist/providers/anthropic/canonical.d.ts +3 -0
- package/dist/providers/anthropic/canonical.js +9 -0
- package/dist/providers/anthropic/index.d.ts +1 -0
- package/dist/providers/anthropic/index.js +1 -0
- package/dist/providers/bedrock/canonical.d.ts +17 -0
- package/dist/providers/bedrock/canonical.js +59 -0
- package/dist/providers/bedrock/index.d.ts +1 -0
- package/dist/providers/bedrock/index.js +1 -0
- package/dist/providers/cohere/canonical.d.ts +3 -0
- package/dist/providers/{canonical/cohere.js → cohere/canonical.js} +6 -6
- package/dist/providers/cohere/index.d.ts +1 -0
- package/dist/providers/cohere/index.js +1 -0
- package/dist/providers/groq/canonical.d.ts +3 -0
- package/dist/providers/groq/canonical.js +12 -0
- package/dist/providers/groq/index.d.ts +1 -0
- package/dist/providers/groq/index.js +1 -0
- package/dist/providers/openai/canonical.d.ts +3 -0
- package/dist/providers/openai/canonical.js +8 -0
- package/dist/providers/openai/index.d.ts +1 -0
- package/dist/providers/openai/index.js +1 -0
- package/dist/providers/registry.d.ts +17 -26
- package/dist/providers/registry.js +26 -29
- package/dist/providers/types.d.ts +1 -1
- package/dist/providers/types.js +1 -0
- package/dist/providers/vertex/canonical.d.ts +3 -0
- package/dist/providers/vertex/canonical.js +8 -0
- package/dist/providers/vertex/index.d.ts +1 -0
- package/dist/providers/vertex/index.js +1 -0
- package/dist/providers/voyage/canonical.d.ts +3 -0
- package/dist/providers/voyage/canonical.js +7 -0
- package/dist/providers/voyage/index.d.ts +1 -0
- package/dist/providers/voyage/index.js +1 -0
- package/dist/types.d.ts +66 -30
- package/dist/utils/errors.d.ts +9 -0
- package/dist/utils/errors.js +11 -0
- package/dist/utils/preset.d.ts +1 -7
- package/dist/utils/preset.js +1 -1
- package/dist/utils/response.d.ts +1 -0
- package/dist/utils/response.js +10 -0
- package/package.json +81 -70
- package/src/config.ts +2 -18
- package/src/endpoints/chat-completions/converters.test.ts +39 -0
- package/src/endpoints/chat-completions/converters.ts +208 -112
- package/src/endpoints/chat-completions/handler.test.ts +47 -18
- package/src/endpoints/chat-completions/handler.ts +54 -35
- package/src/endpoints/chat-completions/schema.ts +161 -88
- package/src/endpoints/embeddings/converters.ts +15 -11
- package/src/endpoints/embeddings/handler.test.ts +27 -30
- package/src/endpoints/embeddings/handler.ts +49 -28
- package/src/endpoints/embeddings/schema.ts +10 -10
- package/src/endpoints/models/converters.ts +23 -15
- package/src/endpoints/models/handler.test.ts +26 -29
- package/src/endpoints/models/handler.ts +10 -12
- package/src/endpoints/models/schema.ts +26 -20
- package/src/gateway.ts +10 -24
- package/src/index.ts +3 -0
- package/src/lifecycle.ts +67 -0
- package/src/middleware/common.ts +74 -0
- package/src/middleware/matcher.ts +115 -0
- package/src/middleware/utils.ts +32 -0
- package/src/models/amazon/index.ts +2 -0
- package/src/models/amazon/middleware.test.ts +133 -0
- package/src/models/amazon/middleware.ts +78 -0
- package/src/models/amazon/presets.ts +104 -0
- package/src/models/anthropic/index.ts +2 -0
- package/src/models/anthropic/middleware.test.ts +263 -0
- package/src/models/anthropic/middleware.ts +57 -0
- package/src/models/anthropic/presets.ts +161 -0
- package/src/models/catalog.ts +10 -2
- package/src/models/cohere/index.ts +2 -0
- package/src/models/cohere/middleware.test.ts +138 -0
- package/src/models/cohere/middleware.ts +76 -0
- package/src/models/cohere/presets.ts +186 -0
- package/src/models/google/index.ts +2 -0
- package/src/models/google/middleware.test.ts +197 -0
- package/src/models/google/middleware.ts +111 -0
- package/src/models/{presets/gemini.ts → google/presets.ts} +25 -5
- package/src/models/meta/index.ts +1 -0
- package/src/models/{presets/llama.ts → meta/presets.ts} +68 -7
- package/src/models/openai/index.ts +2 -0
- package/src/models/openai/middleware.test.ts +125 -0
- package/src/models/openai/middleware.ts +56 -0
- package/src/models/openai/presets.ts +269 -0
- package/src/models/types.ts +29 -2
- package/src/models/voyage/index.ts +2 -0
- package/src/models/voyage/middleware.test.ts +28 -0
- package/src/models/voyage/middleware.ts +23 -0
- package/src/providers/anthropic/canonical.ts +17 -0
- package/src/providers/anthropic/index.ts +1 -0
- package/src/providers/bedrock/canonical.ts +85 -0
- package/src/providers/bedrock/index.ts +1 -0
- package/src/providers/cohere/canonical.ts +26 -0
- package/src/providers/cohere/index.ts +1 -0
- package/src/providers/groq/canonical.ts +21 -0
- package/src/providers/groq/index.ts +1 -0
- package/src/providers/openai/canonical.ts +16 -0
- package/src/providers/openai/index.ts +1 -0
- package/src/providers/registry.test.ts +12 -10
- package/src/providers/registry.ts +51 -46
- package/src/providers/types.ts +1 -0
- package/src/providers/vertex/canonical.ts +17 -0
- package/src/providers/vertex/index.ts +1 -0
- package/src/providers/voyage/canonical.ts +16 -0
- package/src/providers/voyage/index.ts +1 -0
- package/src/types.ts +77 -28
- package/src/utils/errors.ts +13 -0
- package/src/utils/preset.ts +2 -6
- package/src/utils/response.ts +15 -0
- package/dist/models/presets/claude.d.ts +0 -1165
- package/dist/models/presets/claude.js +0 -40
- package/dist/models/presets/cohere.d.ts +0 -383
- package/dist/models/presets/cohere.js +0 -26
- package/dist/models/presets/gpt-oss.d.ts +0 -779
- package/dist/models/presets/gpt-oss.js +0 -40
- package/dist/models/presets/llama.d.ts +0 -1400
- package/dist/providers/canonical/anthropic.d.ts +0 -25
- package/dist/providers/canonical/anthropic.js +0 -14
- package/dist/providers/canonical/bedrock.d.ts +0 -26
- package/dist/providers/canonical/bedrock.js +0 -41
- package/dist/providers/canonical/cohere.d.ts +0 -17
- package/dist/providers/canonical/groq.d.ts +0 -17
- package/dist/providers/canonical/groq.js +0 -10
- package/dist/providers/canonical/openai.d.ts +0 -17
- package/dist/providers/canonical/openai.js +0 -8
- package/dist/providers/canonical/vertex.d.ts +0 -17
- package/dist/providers/canonical/vertex.js +0 -10
- package/dist/providers/canonical/voyage.d.ts +0 -17
- package/dist/providers/canonical/voyage.js +0 -8
- package/dist/utils/hooks.d.ts +0 -2
- package/src/models/presets/claude.ts +0 -59
- package/src/models/presets/cohere.ts +0 -37
- package/src/models/presets/gpt-oss.ts +0 -55
- package/src/providers/canonical/anthropic.ts +0 -32
- package/src/providers/canonical/bedrock.ts +0 -72
- package/src/providers/canonical/cohere.ts +0 -36
- package/src/providers/canonical/groq.ts +0 -25
- package/src/providers/canonical/openai.ts +0 -16
- package/src/providers/canonical/vertex.ts +0 -18
- package/src/providers/canonical/voyage.ts +0 -16
- package/src/utils/hooks.ts +0 -51
- package/dist/models/{presets/voyage.js → voyage/presets.js} +10 -10
- 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
|
|
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 {
|
|
35
|
-
import {
|
|
36
|
-
import {
|
|
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 +
|
|
42
|
-
groq:
|
|
43
|
-
|
|
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:
|
|
49
|
-
// Choose a preset for common SOTA models
|
|
50
|
-
gptOss20b
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
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
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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 {
|
|
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
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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
|
-
|
|
151
|
+
azure,
|
|
226
152
|
},
|
|
227
|
-
models:
|
|
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
|
-
|
|
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
|
-
-
|
|
254
|
-
|
|
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 {
|
|
267
|
-
import { gptOss20b } from "@hebo-ai/gateway/models/
|
|
268
|
-
import { claudeSonnet45, claude } from "@hebo-ai/gateway/models/
|
|
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 =
|
|
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 =
|
|
278
|
-
|
|
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:
|
|
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
|
|
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
|
|
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:
|
|
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
|
-
|
|
418
|
-
|
|
419
|
-
|
|
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 =
|
|
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 =
|
|
542
|
+
const textOptions = convertToTextCallOptions(inputs);
|
|
436
543
|
|
|
437
544
|
const result = await streamText({
|
|
438
|
-
model:
|
|
545
|
+
model: wrapLanguageModel({
|
|
546
|
+
model: groq(model),
|
|
547
|
+
middleware: forwardParamsMiddleware("groq"),
|
|
548
|
+
}),
|
|
439
549
|
...textOptions
|
|
440
550
|
});
|
|
441
551
|
|
|
442
|
-
return
|
|
552
|
+
return toChatCompletionsStreamResponse(result, model);
|
|
443
553
|
}
|
|
444
554
|
```
|
|
445
555
|
|
|
446
|
-
Non-streaming versions are available via `
|
|
556
|
+
Non-streaming versions are available via `createChatCompletionsResponse`. Equivalent schemas and helpers are available in the `embeddings` and `models` endpoints.
|
|
447
557
|
|
|
448
|
-
|
|
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 (
|
|
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
|
|
28
|
+
return { ...config, providers, models: parsedModels, [kParsed]: true };
|
|
39
29
|
};
|