@hebo-ai/gateway 0.2.1 → 0.3.0-rc.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 (167) hide show
  1. package/README.md +133 -16
  2. package/package.json +16 -7
  3. package/src/config.ts +43 -7
  4. package/src/endpoints/chat-completions/converters.test.ts +52 -1
  5. package/src/endpoints/chat-completions/converters.ts +130 -111
  6. package/src/endpoints/chat-completions/handler.test.ts +12 -12
  7. package/src/endpoints/chat-completions/handler.ts +76 -63
  8. package/src/endpoints/chat-completions/schema.ts +2 -0
  9. package/src/endpoints/embeddings/converters.ts +2 -5
  10. package/src/endpoints/embeddings/handler.test.ts +9 -9
  11. package/src/endpoints/embeddings/handler.ts +47 -48
  12. package/src/endpoints/models/converters.ts +3 -9
  13. package/src/endpoints/models/handler.test.ts +2 -2
  14. package/src/endpoints/models/handler.ts +10 -10
  15. package/src/errors/ai-sdk.ts +99 -0
  16. package/src/errors/gateway.ts +15 -0
  17. package/src/errors/openai.ts +35 -0
  18. package/src/errors/utils.ts +51 -0
  19. package/src/gateway.ts +19 -4
  20. package/src/index.ts +4 -0
  21. package/src/lifecycle.ts +40 -49
  22. package/src/logger/default.ts +81 -0
  23. package/src/logger/index.ts +43 -0
  24. package/src/middleware/common.test.ts +215 -0
  25. package/src/middleware/common.ts +125 -36
  26. package/src/middleware/matcher.ts +15 -2
  27. package/src/middleware/utils.ts +2 -1
  28. package/src/models/anthropic/middleware.ts +4 -1
  29. package/src/models/google/middleware.test.ts +41 -0
  30. package/src/models/google/middleware.ts +17 -14
  31. package/src/models/openai/middleware.test.ts +36 -4
  32. package/src/models/openai/middleware.ts +25 -3
  33. package/src/providers/registry.test.ts +3 -5
  34. package/src/providers/registry.ts +30 -7
  35. package/src/telemetry/access-log.ts +68 -0
  36. package/src/telemetry/stream.ts +77 -0
  37. package/src/telemetry/utils.ts +46 -0
  38. package/src/types.ts +33 -14
  39. package/src/utils/env.ts +7 -0
  40. package/src/utils/request.ts +62 -0
  41. package/src/utils/response.ts +45 -5
  42. package/dist/config.d.ts +0 -2
  43. package/dist/config.js +0 -29
  44. package/dist/endpoints/chat-completions/converters.d.ts +0 -36
  45. package/dist/endpoints/chat-completions/converters.js +0 -368
  46. package/dist/endpoints/chat-completions/handler.d.ts +0 -2
  47. package/dist/endpoints/chat-completions/handler.js +0 -96
  48. package/dist/endpoints/chat-completions/index.d.ts +0 -3
  49. package/dist/endpoints/chat-completions/index.js +0 -3
  50. package/dist/endpoints/chat-completions/schema.d.ts +0 -456
  51. package/dist/endpoints/chat-completions/schema.js +0 -190
  52. package/dist/endpoints/embeddings/converters.d.ts +0 -10
  53. package/dist/endpoints/embeddings/converters.js +0 -31
  54. package/dist/endpoints/embeddings/handler.d.ts +0 -2
  55. package/dist/endpoints/embeddings/handler.js +0 -83
  56. package/dist/endpoints/embeddings/index.d.ts +0 -3
  57. package/dist/endpoints/embeddings/index.js +0 -3
  58. package/dist/endpoints/embeddings/schema.d.ts +0 -38
  59. package/dist/endpoints/embeddings/schema.js +0 -26
  60. package/dist/endpoints/index.d.ts +0 -1
  61. package/dist/endpoints/index.js +0 -1
  62. package/dist/endpoints/models/converters.d.ts +0 -6
  63. package/dist/endpoints/models/converters.js +0 -42
  64. package/dist/endpoints/models/handler.d.ts +0 -2
  65. package/dist/endpoints/models/handler.js +0 -29
  66. package/dist/endpoints/models/index.d.ts +0 -3
  67. package/dist/endpoints/models/index.js +0 -3
  68. package/dist/endpoints/models/schema.d.ts +0 -42
  69. package/dist/endpoints/models/schema.js +0 -31
  70. package/dist/gateway.d.ts +0 -9
  71. package/dist/gateway.js +0 -25
  72. package/dist/index.d.ts +0 -11
  73. package/dist/index.js +0 -10
  74. package/dist/lifecycle.d.ts +0 -2
  75. package/dist/lifecycle.js +0 -49
  76. package/dist/middleware/common.d.ts +0 -4
  77. package/dist/middleware/common.js +0 -50
  78. package/dist/middleware/matcher.d.ts +0 -19
  79. package/dist/middleware/matcher.js +0 -82
  80. package/dist/middleware/utils.d.ts +0 -2
  81. package/dist/middleware/utils.js +0 -25
  82. package/dist/models/amazon/index.d.ts +0 -2
  83. package/dist/models/amazon/index.js +0 -2
  84. package/dist/models/amazon/middleware.d.ts +0 -3
  85. package/dist/models/amazon/middleware.js +0 -64
  86. package/dist/models/amazon/presets.d.ts +0 -2390
  87. package/dist/models/amazon/presets.js +0 -80
  88. package/dist/models/anthropic/index.d.ts +0 -2
  89. package/dist/models/anthropic/index.js +0 -2
  90. package/dist/models/anthropic/middleware.d.ts +0 -2
  91. package/dist/models/anthropic/middleware.js +0 -46
  92. package/dist/models/anthropic/presets.d.ts +0 -4106
  93. package/dist/models/anthropic/presets.js +0 -113
  94. package/dist/models/catalog.d.ts +0 -4
  95. package/dist/models/catalog.js +0 -4
  96. package/dist/models/cohere/index.d.ts +0 -2
  97. package/dist/models/cohere/index.js +0 -2
  98. package/dist/models/cohere/middleware.d.ts +0 -3
  99. package/dist/models/cohere/middleware.js +0 -60
  100. package/dist/models/cohere/presets.d.ts +0 -2918
  101. package/dist/models/cohere/presets.js +0 -134
  102. package/dist/models/google/index.d.ts +0 -2
  103. package/dist/models/google/index.js +0 -2
  104. package/dist/models/google/middleware.d.ts +0 -6
  105. package/dist/models/google/middleware.js +0 -87
  106. package/dist/models/google/presets.d.ts +0 -2166
  107. package/dist/models/google/presets.js +0 -76
  108. package/dist/models/meta/index.d.ts +0 -1
  109. package/dist/models/meta/index.js +0 -1
  110. package/dist/models/meta/presets.d.ts +0 -3254
  111. package/dist/models/meta/presets.js +0 -95
  112. package/dist/models/openai/index.d.ts +0 -2
  113. package/dist/models/openai/index.js +0 -2
  114. package/dist/models/openai/middleware.d.ts +0 -3
  115. package/dist/models/openai/middleware.js +0 -45
  116. package/dist/models/openai/presets.d.ts +0 -6252
  117. package/dist/models/openai/presets.js +0 -206
  118. package/dist/models/types.d.ts +0 -20
  119. package/dist/models/types.js +0 -80
  120. package/dist/models/voyage/index.d.ts +0 -2
  121. package/dist/models/voyage/index.js +0 -2
  122. package/dist/models/voyage/middleware.d.ts +0 -2
  123. package/dist/models/voyage/middleware.js +0 -18
  124. package/dist/models/voyage/presets.d.ts +0 -3471
  125. package/dist/models/voyage/presets.js +0 -85
  126. package/dist/providers/anthropic/canonical.d.ts +0 -3
  127. package/dist/providers/anthropic/canonical.js +0 -9
  128. package/dist/providers/anthropic/index.d.ts +0 -1
  129. package/dist/providers/anthropic/index.js +0 -1
  130. package/dist/providers/bedrock/canonical.d.ts +0 -17
  131. package/dist/providers/bedrock/canonical.js +0 -59
  132. package/dist/providers/bedrock/index.d.ts +0 -1
  133. package/dist/providers/bedrock/index.js +0 -1
  134. package/dist/providers/cohere/canonical.d.ts +0 -3
  135. package/dist/providers/cohere/canonical.js +0 -17
  136. package/dist/providers/cohere/index.d.ts +0 -1
  137. package/dist/providers/cohere/index.js +0 -1
  138. package/dist/providers/groq/canonical.d.ts +0 -3
  139. package/dist/providers/groq/canonical.js +0 -12
  140. package/dist/providers/groq/index.d.ts +0 -1
  141. package/dist/providers/groq/index.js +0 -1
  142. package/dist/providers/openai/canonical.d.ts +0 -3
  143. package/dist/providers/openai/canonical.js +0 -8
  144. package/dist/providers/openai/index.d.ts +0 -1
  145. package/dist/providers/openai/index.js +0 -1
  146. package/dist/providers/registry.d.ts +0 -24
  147. package/dist/providers/registry.js +0 -85
  148. package/dist/providers/types.d.ts +0 -7
  149. package/dist/providers/types.js +0 -11
  150. package/dist/providers/vertex/canonical.d.ts +0 -3
  151. package/dist/providers/vertex/canonical.js +0 -8
  152. package/dist/providers/vertex/index.d.ts +0 -1
  153. package/dist/providers/vertex/index.js +0 -1
  154. package/dist/providers/voyage/canonical.d.ts +0 -3
  155. package/dist/providers/voyage/canonical.js +0 -7
  156. package/dist/providers/voyage/index.d.ts +0 -1
  157. package/dist/providers/voyage/index.js +0 -1
  158. package/dist/types.d.ts +0 -132
  159. package/dist/types.js +0 -1
  160. package/dist/utils/errors.d.ts +0 -19
  161. package/dist/utils/errors.js +0 -25
  162. package/dist/utils/preset.d.ts +0 -9
  163. package/dist/utils/preset.js +0 -41
  164. package/dist/utils/response.d.ts +0 -1
  165. package/dist/utils/response.js +0 -10
  166. package/src/endpoints/index.ts +0 -1
  167. package/src/utils/errors.ts +0 -35
package/README.md CHANGED
@@ -2,13 +2,15 @@
2
2
 
3
3
  Roll your own AI gateway for full control over models, providers, routing logic, guardrails, observability and more ...
4
4
 
5
- ## Overview
5
+ ## 🐒 Overview
6
6
 
7
7
  Existing AI gateways like OpenRouter, Vercel AI Gateway, LiteLLM, and Portkey work out of the box, but they’re hard to extend once your needs go beyond configuration.
8
8
 
9
9
  Hebo Gateway is an open-source, embeddable AI gateway framework built to live inside your app. It gives you full control over providers, models, routing, and the request lifecycle.
10
10
 
11
- ## Features
11
+ Learn more in our blog post: [Yet Another AI Gateway?](https://hebo.ai/blog/260127-hebo-gateway/) (`https://hebo.ai/blog/260127-hebo-gateway/`)
12
+
13
+ ## 🍌 Features
12
14
 
13
15
  - 🌐 OpenAI-compatible /chat/completions, /embeddings & /models endpoints.
14
16
  - 🔌 Integrate into your existing Hono, Elysia, Next.js & TanStack apps.
@@ -18,13 +20,28 @@ Hebo Gateway is an open-source, embeddable AI gateway framework built to live in
18
20
  - 🪝 Hook system to customize routing, auth, rate limits, and shape responses.
19
21
  - 🧰 Low-level OpenAI-compatible schema, converters, and middleware helpers.
20
22
 
21
- ## Installation
23
+ ## 📦 Installation
22
24
 
23
25
  ```bash
24
26
  bun install @hebo-ai/gateway
25
27
  ```
26
28
 
27
- ## Quickstart
29
+ ## ☰ Table of Contents
30
+
31
+ - Quickstart
32
+ - [Setup A Gateway Instance](#setup-a-gateway-instance) | [Mount Route Handlers](#mount-route-handlers) | [Call the Gateway](#call-the-gateway)
33
+ - Configuration Reference
34
+ - [Providers](#providers) | [Models](#models) | [Hooks](#hooks) | [Logger](#logger-settings)
35
+ - Framework Support
36
+ - [ElysiaJS](#elysiajs) | [Hono](#hono) | [Next.js](#nextjs) | [TanStack Start](#tanstack-start)
37
+ - Runtime Support
38
+ - [Vercel Edge](#vercel-edge) | [Cloudflare Workers](#cloudflare-workers) | [Deno Deploy](#deno-deploy) | [AWS Lambda](#aws-lambda)
39
+ - OpenAI Extensions
40
+ - [Reasoning](#reasoning)
41
+ - Advanced Usage
42
+ - [Passing Framework State to Hooks](#passing-framework-state-to-hooks) | [Selective Route Mounting](#selective-route-mounting) | [Low-level Schemas & Converters](#low-level-schemas--converters)
43
+
44
+ ## 🚀 Quickstart
28
45
 
29
46
  ### Setup A Gateway Instance
30
47
 
@@ -113,7 +130,7 @@ const { text } = await generateText({
113
130
  console.log(text);
114
131
  ```
115
132
 
116
- ## Configuration Reference
133
+ ## ⚙️ Configuration Reference
117
134
 
118
135
  ### Providers
119
136
 
@@ -316,25 +333,27 @@ const gw = gateway({
316
333
  },
317
334
  /**
318
335
  * Runs after the endpoint handler.
319
- * @param ctx.response Response returned by the handler.
320
- * @returns Response to replace, or undefined to keep original.
336
+ * @param ctx.result Result object returned by the handler.
337
+ * @returns Modified result, or undefined to keep original.
321
338
  */
322
- after: async (ctx: { response: Response }): Promise<Response | void> => {
339
+ after: async (ctx: {
340
+ result: object | ReadableStream<Uint8Array>
341
+ }): Promise<object | ReadableStream<Uint8Array> | void> => {
323
342
  // Example Use Cases:
324
- // - Transform response
325
- // - Response logging
343
+ // - Transform result
344
+ // - Result logging
326
345
  return undefined;
327
346
  },
328
347
  },
329
348
  });
330
349
  ```
331
350
 
332
- The `ctx` object is **readonly for core fields**. Use return values to override request / response and provide modelId / provider instances.
351
+ The `ctx` object is **readonly for core fields**. Use return values to override request / result and to provide modelId / provider instances.
333
352
 
334
353
  > [!TIP]
335
354
  > 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
355
 
337
- ## Framework Support
356
+ ## 🧩 Framework Support
338
357
 
339
358
  Hebo Gateway exposes **WinterCG-compatible** handlers that integrate with almost any existing framework.
340
359
 
@@ -362,7 +381,9 @@ export default new Hono().mount("/v1/gateway/", gw.handler);
362
381
  console.log(`🐒 Hebo Gateway is running with Hono framework`);
363
382
  ```
364
383
 
365
- ### Next.js (App Router)
384
+ ### Next.js
385
+
386
+ #### App Router
366
387
 
367
388
  `app/api/gateway/[...all]/route.ts`
368
389
 
@@ -376,7 +397,7 @@ const gw = gateway({
376
397
  export const POST = gw.handler, GET = gw.handler;
377
398
  ```
378
399
 
379
- ### Next.js (Pages Router)
400
+ #### Pages Router
380
401
 
381
402
  `pages/api/gateway/[...all].ts`
382
403
 
@@ -416,7 +437,69 @@ export const Route = createFileRoute("/api/$")({
416
437
  });
417
438
  ```
418
439
 
419
- ## OpenAI Extensions
440
+ ## 🌍 Runtime Support
441
+
442
+ Hebo Gateway also works directly with runtime-level `Request -> Response` handlers.
443
+
444
+ ### Vercel Edge
445
+
446
+ `api/gateway.ts`
447
+
448
+ ```ts
449
+ export const runtime = "edge";
450
+
451
+ const gw = gateway({
452
+ // ...
453
+ });
454
+
455
+ export default gw.handler;
456
+ ```
457
+
458
+ ### Cloudflare Workers
459
+
460
+ `src/index.ts`
461
+
462
+ ```ts
463
+ const gw = gateway({
464
+ // ...
465
+ });
466
+
467
+ export default {
468
+ fetch: gw.handler,
469
+ };
470
+ ```
471
+
472
+ ### Deno Deploy
473
+
474
+ `main.ts`
475
+
476
+ ```ts
477
+ import { serve } from "https://deno.land/std/http/server.ts";
478
+
479
+ const gw = gateway({
480
+ // ...
481
+ });
482
+
483
+ serve((request: Request) => gw.handler(request));
484
+ ```
485
+
486
+ ### AWS Lambda
487
+
488
+ `src/lambda.ts`
489
+
490
+ ```ts
491
+ import { awsLambdaEventHandler } from "@hattip/adapter-aws-lambda";
492
+
493
+ const gw = gateway({
494
+ // ...
495
+ });
496
+
497
+ export const handler = awsLambdaEventHandler({
498
+ handler: gw.handler,
499
+ });
500
+ ```
501
+
502
+ ## 🧠 OpenAI Extensions
420
503
 
421
504
  ### Reasoning
422
505
 
@@ -449,7 +532,41 @@ Reasoning output is surfaced as extension to the `completion` object.
449
532
 
450
533
  Most SDKs handle these fields out-of-the-box.
451
534
 
452
- ## Advanced Usage
535
+ ## 🧪 Advanced Usage
536
+
537
+ ### Logger Settings
538
+
539
+ You can configure logging via the `logger` field in the gateway config. By default, the logger uses `console` and sets the level to `debug` in non-production and `info` in production (based on the `NODE_ENV` environment variable).
540
+
541
+ ```ts
542
+ import { gateway } from "@hebo-ai/gateway";
543
+
544
+ const gw = gateway({
545
+ // ...
546
+ logger: {
547
+ level: "debug", // "trace" | "debug" | "info" | "warn" | "error" | "silent"
548
+ },
549
+ });
550
+ ```
551
+
552
+ If you provide a custom logger, it must implement `trace`, `debug`, `info`, `warn`, and `error` methods.
553
+ For production workloads, we recommend `pino` for better logging performance and lower overhead.
554
+
555
+ Example with **pino**:
556
+
557
+ ```ts
558
+ import pino from "pino";
559
+ import { gateway } from "@hebo-ai/gateway";
560
+
561
+ const gw = gateway({
562
+ // ...
563
+ logger: pino(
564
+ {
565
+ level: "info"
566
+ }
567
+ ),
568
+ });
569
+ ```
453
570
 
454
571
  ### Passing Framework State to Hooks
455
572
 
package/package.json CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "@hebo-ai/gateway",
3
- "version": "0.2.1",
4
- "description": "Hebo Gateway - Roll your own AI gateway for full control over models, providers, routing logic, guardrails, observability and more.",
3
+ "version": "0.3.0-rc.1",
4
+ "description": "AI gateway as a framework. For full control over models, routing & lifecycle. OpenAI-compatible /chat/completions, /embeddings & /models.",
5
5
  "keywords": [
6
6
  "ai",
7
+ "ai-gateway",
7
8
  "anthropic",
8
9
  "bedrock",
9
10
  "elysiajs",
@@ -11,10 +12,13 @@
11
12
  "groq",
12
13
  "hono",
13
14
  "llm",
15
+ "llm-gateway",
16
+ "llmops",
14
17
  "nextjs",
15
18
  "openai",
16
19
  "tanstack-start",
17
20
  "vercel-ai-sdk",
21
+ "vertex",
18
22
  "voyage"
19
23
  ],
20
24
  "homepage": "https://hebo.ai/gateway",
@@ -34,8 +38,10 @@
34
38
  ],
35
39
  "type": "module",
36
40
  "sideEffects": [
41
+ "./dist/logger/index.js",
37
42
  "./dist/providers/**/*.js",
38
43
  "./dist/models/**/*.js",
44
+ "./src/logger/index.ts",
39
45
  "./src/providers/**/*.ts",
40
46
  "./src/models/**/*.ts"
41
47
  ],
@@ -45,11 +51,6 @@
45
51
  "import": "./dist/index.js",
46
52
  "dev-source": "./src/index.ts"
47
53
  },
48
- "./endpoints": {
49
- "types": "./dist/endpoints/index.d.ts",
50
- "import": "./dist/endpoints/index.js",
51
- "dev-source": "./src/endpoints/index.ts"
52
- },
53
54
  "./endpoints/chat-completions": {
54
55
  "types": "./dist/endpoints/chat-completions/index.d.ts",
55
56
  "import": "./dist/endpoints/chat-completions/index.js",
@@ -65,6 +66,11 @@
65
66
  "import": "./dist/endpoints/models/index.js",
66
67
  "dev-source": "./src/endpoints/models/index.ts"
67
68
  },
69
+ "./errors/openai": {
70
+ "types": "./dist/errors/openai.d.ts",
71
+ "import": "./dist/errors/openai.js",
72
+ "dev-source": "./src/errors/openai.ts"
73
+ },
68
74
  "./middleware/common": {
69
75
  "types": "./dist/middleware/common.d.ts",
70
76
  "import": "./dist/middleware/common.js",
@@ -149,7 +155,9 @@
149
155
  },
150
156
  "dependencies": {
151
157
  "@ai-sdk/provider": "^3.0.7",
158
+ "@ai-sdk/provider-utils": "^4.0.13",
152
159
  "ai": "^6.0.67",
160
+ "serialize-error": "^13.0.1",
153
161
  "zod": "^4.3.6"
154
162
  },
155
163
  "devDependencies": {
@@ -172,6 +180,7 @@
172
180
  "next": "^16.1.6",
173
181
  "oxfmt": "^0.24.0",
174
182
  "oxlint": "^1.42.0",
183
+ "pino": "^10.3.0",
175
184
  "typescript": "^5.9.3",
176
185
  "vite": "^7.3.1",
177
186
  "vite-tsconfig-paths": "^6.0.5",
package/src/config.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { isLogger, logger, setLoggerInstance } from "./logger";
2
+ import { createDefaultLogger } from "./logger/default";
1
3
  import { kParsed, type GatewayConfig, type GatewayConfigParsed } from "./types";
2
4
 
3
5
  export const parseConfig = (config: GatewayConfig): GatewayConfigParsed => {
@@ -5,30 +7,64 @@ export const parseConfig = (config: GatewayConfig): GatewayConfigParsed => {
5
7
  if (kParsed in config) return config as GatewayConfigParsed;
6
8
 
7
9
  const providers = config.providers ?? {};
10
+ const parsedProviders = {} as typeof providers;
8
11
  const models = config.models ?? {};
9
12
 
10
- if (Object.keys(providers).length === 0) {
11
- throw new Error("Gateway config error: no providers configured (config.providers is empty).");
13
+ // Set the global logger instance
14
+ if (config.logger === undefined) {
15
+ setLoggerInstance(createDefaultLogger({}));
16
+ } else if (config.logger !== null) {
17
+ setLoggerInstance(isLogger(config.logger) ? config.logger : createDefaultLogger(config.logger));
18
+
19
+ logger.info(
20
+ isLogger(config.logger)
21
+ ? `[logger] custom logger configured`
22
+ : `[logger] logger configured: level=${config.logger.level}`,
23
+ );
24
+ }
25
+
26
+ // Strip providers that are not configured
27
+ for (const id in providers) {
28
+ const provider = providers[id];
29
+ if (provider === undefined) {
30
+ logger.warn(`[config] ${id} provider removed (undefined)`);
31
+ continue;
32
+ }
33
+ parsedProviders[id] = provider;
12
34
  }
13
35
 
14
- // Strip out providers from models that are not configured
36
+ if (Object.keys(parsedProviders).length === 0) {
37
+ throw new Error("No providers configured (config.providers is empty)");
38
+ }
39
+
40
+ // Strip providers that are not configured from models
15
41
  const parsedModels = {} as typeof models;
42
+ const warnings = new Set<string>();
16
43
  for (const id in models) {
17
44
  const model = models[id!];
18
45
 
19
46
  const kept: string[] = [];
20
47
 
21
48
  for (const p of model!.providers) {
22
- if (p in providers) kept.push(p);
23
- else console.warn(`[models] ${id}: provider "${p}" removed (not configured)`);
49
+ if (p in parsedProviders) kept.push(p);
50
+ else warnings.add(p);
24
51
  }
25
52
 
26
53
  if (kept.length > 0) parsedModels[id] = { ...model!, providers: kept };
27
54
  }
55
+ for (const warning of warnings) {
56
+ logger.warn(`[config] ${warning} provider removed (not configured)`);
57
+ }
28
58
 
29
59
  if (Object.keys(parsedModels).length === 0) {
30
- throw new Error("Gateway config error: no models configured (config.models is empty).");
60
+ throw new Error("No models configured (config.models is empty)");
31
61
  }
32
62
 
33
- return { ...config, providers, models: parsedModels, [kParsed]: true };
63
+ return {
64
+ ...config,
65
+ logger: config.logger,
66
+ providers: parsedProviders,
67
+ models: parsedModels,
68
+ [kParsed]: true,
69
+ };
34
70
  };
@@ -1,8 +1,59 @@
1
+ import type { GenerateTextResult, ToolSet, Output } from "ai";
2
+
1
3
  import { describe, expect, test } from "bun:test";
2
4
 
3
- import { convertToTextCallOptions } from "./converters";
5
+ import { convertToTextCallOptions, toChatCompletionsAssistantMessage } from "./converters";
4
6
 
5
7
  describe("Chat Completions Converters", () => {
8
+ describe("toChatCompletionsAssistantMessage", () => {
9
+ test("should pass through providerMetadata to extra_content", () => {
10
+ const mockResult: GenerateTextResult<ToolSet, Output.Output> = {
11
+ content: [
12
+ {
13
+ type: "text",
14
+ text: "hello",
15
+ providerMetadata: {
16
+ vertex: {
17
+ thought_signature: "signature-abc",
18
+ },
19
+ },
20
+ } as any,
21
+ ],
22
+ toolCalls: [],
23
+ };
24
+
25
+ const message = toChatCompletionsAssistantMessage(mockResult);
26
+
27
+ expect(message.extra_content).toEqual({
28
+ vertex: {
29
+ thought_signature: "signature-abc",
30
+ },
31
+ });
32
+ });
33
+
34
+ test("should pass through providerMetadata to tool calls", () => {
35
+ const mockResult: GenerateTextResult<ToolSet, Output.Output> = {
36
+ content: [],
37
+ toolCalls: [
38
+ {
39
+ toolCallId: "call_123",
40
+ toolName: "get_weather",
41
+ input: { location: "London" },
42
+ providerMetadata: {
43
+ vertex: { thought_signature: "tool-signature" },
44
+ },
45
+ },
46
+ ],
47
+ };
48
+
49
+ const message = toChatCompletionsAssistantMessage(mockResult);
50
+
51
+ expect(message.tool_calls![0].extra_content).toEqual({
52
+ vertex: { thought_signature: "tool-signature" },
53
+ });
54
+ });
55
+ });
56
+
6
57
  describe("convertToTextCallOptions", () => {
7
58
  test("should use max_completion_tokens when present", () => {
8
59
  const result = convertToTextCallOptions({