@hebo-ai/gateway 0.6.2-rc0 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/README.md +3 -3
  2. package/dist/endpoints/chat-completions/converters.js +26 -21
  3. package/dist/endpoints/chat-completions/handler.js +2 -0
  4. package/dist/endpoints/chat-completions/otel.js +1 -1
  5. package/dist/endpoints/chat-completions/schema.d.ts +4 -18
  6. package/dist/endpoints/chat-completions/schema.js +14 -17
  7. package/dist/endpoints/embeddings/handler.js +2 -0
  8. package/dist/endpoints/embeddings/otel.js +5 -0
  9. package/dist/endpoints/embeddings/schema.d.ts +6 -0
  10. package/dist/endpoints/embeddings/schema.js +4 -1
  11. package/dist/endpoints/models/converters.js +3 -3
  12. package/dist/lifecycle.js +2 -2
  13. package/dist/logger/default.js +3 -3
  14. package/dist/logger/index.d.ts +2 -5
  15. package/dist/middleware/common.js +1 -0
  16. package/dist/middleware/utils.js +0 -3
  17. package/dist/models/amazon/middleware.js +8 -5
  18. package/dist/models/anthropic/middleware.js +13 -13
  19. package/dist/models/catalog.js +5 -1
  20. package/dist/models/cohere/middleware.js +7 -5
  21. package/dist/models/google/middleware.d.ts +1 -1
  22. package/dist/models/google/middleware.js +29 -25
  23. package/dist/models/openai/middleware.js +13 -9
  24. package/dist/models/voyage/middleware.js +2 -1
  25. package/dist/providers/bedrock/middleware.js +21 -23
  26. package/dist/providers/registry.js +3 -0
  27. package/dist/telemetry/fetch.js +7 -2
  28. package/dist/telemetry/gen-ai.js +15 -12
  29. package/dist/telemetry/memory.d.ts +1 -1
  30. package/dist/telemetry/memory.js +30 -14
  31. package/dist/telemetry/span.js +1 -1
  32. package/dist/telemetry/stream.js +30 -23
  33. package/dist/utils/env.js +4 -2
  34. package/dist/utils/preset.js +1 -0
  35. package/dist/utils/response.js +3 -1
  36. package/package.json +36 -50
  37. package/src/config.ts +0 -98
  38. package/src/endpoints/chat-completions/converters.test.ts +0 -631
  39. package/src/endpoints/chat-completions/converters.ts +0 -899
  40. package/src/endpoints/chat-completions/handler.test.ts +0 -391
  41. package/src/endpoints/chat-completions/handler.ts +0 -201
  42. package/src/endpoints/chat-completions/index.ts +0 -4
  43. package/src/endpoints/chat-completions/otel.test.ts +0 -315
  44. package/src/endpoints/chat-completions/otel.ts +0 -214
  45. package/src/endpoints/chat-completions/schema.ts +0 -364
  46. package/src/endpoints/embeddings/converters.ts +0 -51
  47. package/src/endpoints/embeddings/handler.test.ts +0 -133
  48. package/src/endpoints/embeddings/handler.ts +0 -137
  49. package/src/endpoints/embeddings/index.ts +0 -4
  50. package/src/endpoints/embeddings/otel.ts +0 -40
  51. package/src/endpoints/embeddings/schema.ts +0 -36
  52. package/src/endpoints/models/converters.ts +0 -56
  53. package/src/endpoints/models/handler.test.ts +0 -122
  54. package/src/endpoints/models/handler.ts +0 -37
  55. package/src/endpoints/models/index.ts +0 -3
  56. package/src/endpoints/models/schema.ts +0 -37
  57. package/src/errors/ai-sdk.ts +0 -99
  58. package/src/errors/gateway.ts +0 -17
  59. package/src/errors/openai.ts +0 -57
  60. package/src/errors/utils.ts +0 -47
  61. package/src/gateway.ts +0 -50
  62. package/src/index.ts +0 -19
  63. package/src/lifecycle.ts +0 -135
  64. package/src/logger/default.ts +0 -105
  65. package/src/logger/index.ts +0 -42
  66. package/src/middleware/common.test.ts +0 -215
  67. package/src/middleware/common.ts +0 -163
  68. package/src/middleware/debug.ts +0 -37
  69. package/src/middleware/matcher.ts +0 -161
  70. package/src/middleware/utils.ts +0 -34
  71. package/src/models/amazon/index.ts +0 -2
  72. package/src/models/amazon/middleware.test.ts +0 -133
  73. package/src/models/amazon/middleware.ts +0 -79
  74. package/src/models/amazon/presets.ts +0 -104
  75. package/src/models/anthropic/index.ts +0 -2
  76. package/src/models/anthropic/middleware.test.ts +0 -643
  77. package/src/models/anthropic/middleware.ts +0 -148
  78. package/src/models/anthropic/presets.ts +0 -191
  79. package/src/models/catalog.ts +0 -13
  80. package/src/models/cohere/index.ts +0 -2
  81. package/src/models/cohere/middleware.test.ts +0 -138
  82. package/src/models/cohere/middleware.ts +0 -76
  83. package/src/models/cohere/presets.ts +0 -186
  84. package/src/models/google/index.ts +0 -2
  85. package/src/models/google/middleware.test.ts +0 -298
  86. package/src/models/google/middleware.ts +0 -137
  87. package/src/models/google/presets.ts +0 -118
  88. package/src/models/meta/index.ts +0 -1
  89. package/src/models/meta/presets.ts +0 -143
  90. package/src/models/openai/index.ts +0 -2
  91. package/src/models/openai/middleware.test.ts +0 -189
  92. package/src/models/openai/middleware.ts +0 -103
  93. package/src/models/openai/presets.ts +0 -280
  94. package/src/models/types.ts +0 -114
  95. package/src/models/voyage/index.ts +0 -2
  96. package/src/models/voyage/middleware.test.ts +0 -28
  97. package/src/models/voyage/middleware.ts +0 -23
  98. package/src/models/voyage/presets.ts +0 -126
  99. package/src/providers/anthropic/canonical.ts +0 -17
  100. package/src/providers/anthropic/index.ts +0 -1
  101. package/src/providers/bedrock/canonical.ts +0 -87
  102. package/src/providers/bedrock/index.ts +0 -2
  103. package/src/providers/bedrock/middleware.test.ts +0 -303
  104. package/src/providers/bedrock/middleware.ts +0 -128
  105. package/src/providers/cohere/canonical.ts +0 -26
  106. package/src/providers/cohere/index.ts +0 -1
  107. package/src/providers/groq/canonical.ts +0 -21
  108. package/src/providers/groq/index.ts +0 -1
  109. package/src/providers/openai/canonical.ts +0 -16
  110. package/src/providers/openai/index.ts +0 -1
  111. package/src/providers/registry.test.ts +0 -44
  112. package/src/providers/registry.ts +0 -165
  113. package/src/providers/types.ts +0 -20
  114. package/src/providers/vertex/canonical.ts +0 -17
  115. package/src/providers/vertex/index.ts +0 -1
  116. package/src/providers/voyage/canonical.ts +0 -16
  117. package/src/providers/voyage/index.ts +0 -1
  118. package/src/telemetry/ai-sdk.ts +0 -46
  119. package/src/telemetry/baggage.ts +0 -27
  120. package/src/telemetry/fetch.ts +0 -62
  121. package/src/telemetry/gen-ai.ts +0 -113
  122. package/src/telemetry/http.ts +0 -62
  123. package/src/telemetry/index.ts +0 -1
  124. package/src/telemetry/memory.ts +0 -36
  125. package/src/telemetry/span.ts +0 -85
  126. package/src/telemetry/stream.ts +0 -64
  127. package/src/types.ts +0 -223
  128. package/src/utils/env.ts +0 -7
  129. package/src/utils/headers.ts +0 -27
  130. package/src/utils/preset.ts +0 -65
  131. package/src/utils/request.test.ts +0 -75
  132. package/src/utils/request.ts +0 -52
  133. package/src/utils/response.ts +0 -84
  134. package/src/utils/url.ts +0 -26
@@ -1,40 +0,0 @@
1
- import type { Attributes } from "@opentelemetry/api";
2
-
3
- import type { Embeddings, EmbeddingsInputs } from "./schema";
4
-
5
- import { type TelemetrySignalLevel } from "../../types";
6
-
7
- export const getEmbeddingsRequestAttributes = (
8
- inputs: EmbeddingsInputs,
9
- signalLevel?: TelemetrySignalLevel,
10
- ): Attributes => {
11
- if (!signalLevel || signalLevel === "off") return {};
12
-
13
- const attrs: Attributes = {};
14
-
15
- if (signalLevel !== "required") {
16
- Object.assign(attrs, {
17
- "gen_ai.embeddings.dimension.count": inputs.dimensions,
18
- });
19
- }
20
-
21
- return attrs;
22
- };
23
-
24
- export const getEmbeddingsResponseAttributes = (
25
- embeddings: Embeddings,
26
- signalLevel?: TelemetrySignalLevel,
27
- ): Attributes => {
28
- if (!signalLevel || signalLevel === "off") return {};
29
-
30
- const attrs: Attributes = {};
31
-
32
- if (signalLevel !== "required") {
33
- Object.assign(attrs, {
34
- "gen_ai.usage.input_tokens": embeddings.usage?.prompt_tokens,
35
- "gen_ai.usage.total_tokens": embeddings.usage?.total_tokens,
36
- });
37
- }
38
-
39
- return attrs;
40
- };
@@ -1,36 +0,0 @@
1
- import * as z from "zod";
2
-
3
- export const EmbeddingsInputsSchema = z.object({
4
- input: z.union([z.string(), z.array(z.string())]),
5
- dimensions: z.int().nonnegative().max(65536).optional(),
6
- });
7
- export type EmbeddingsInputs = z.infer<typeof EmbeddingsInputsSchema>;
8
-
9
- export const EmbeddingsBodySchema = z.looseObject({
10
- model: z.string(),
11
- ...EmbeddingsInputsSchema.shape,
12
- });
13
- export type EmbeddingsBody = z.infer<typeof EmbeddingsBodySchema>;
14
-
15
- export const EmbeddingsDataSchema = z.object({
16
- object: z.literal("embedding"),
17
- embedding: z.array(z.number()),
18
- index: z.int().nonnegative(),
19
- });
20
- export type EmbeddingsData = z.infer<typeof EmbeddingsDataSchema>;
21
-
22
- export const EmbeddingsUsageSchema = z.object({
23
- prompt_tokens: z.int().nonnegative().optional(),
24
- total_tokens: z.int().nonnegative().optional(),
25
- });
26
- export type EmbeddingsUsage = z.infer<typeof EmbeddingsUsageSchema>;
27
-
28
- export const EmbeddingsSchema = z.object({
29
- object: z.literal("list"),
30
- data: z.array(EmbeddingsDataSchema),
31
- model: z.string(),
32
- usage: EmbeddingsUsageSchema.nullable(),
33
- // Extensions
34
- provider_metadata: z.unknown().optional().meta({ extension: true }),
35
- });
36
- export type Embeddings = z.infer<typeof EmbeddingsSchema>;
@@ -1,56 +0,0 @@
1
- import type { ModelCatalog, CatalogModel } from "../../models/types";
2
- import type { ModelList, Model } from "./schema";
3
-
4
- import { toResponse } from "../../utils/response";
5
-
6
- export function toModel(id: string, catalogModel: CatalogModel): Model {
7
- const { created, providers, modalities, additionalProperties, ...rest } = catalogModel;
8
- let createdTimestamp = Math.floor(Date.now() / 1000);
9
- if (created) {
10
- const parsed = Date.parse(created);
11
- if (!isNaN(parsed)) {
12
- createdTimestamp = Math.floor(parsed / 1000);
13
- }
14
- }
15
-
16
- const model = {
17
- id,
18
- object: "model" as const,
19
- created: createdTimestamp,
20
- owned_by: id.split("/")[0] || "system",
21
- architecture: {
22
- input_modalities: modalities?.input || [],
23
- modality:
24
- modalities?.input &&
25
- modalities?.output &&
26
- `${modalities.input?.[0]}->${modalities.output?.[0]}`,
27
- output_modalities: modalities?.output || [],
28
- },
29
- endpoints:
30
- providers?.map((provider) => ({
31
- tag: provider,
32
- })) || [],
33
- ...rest,
34
- ...additionalProperties,
35
- };
36
-
37
- return model;
38
- }
39
-
40
- export function toModels(models: ModelCatalog): ModelList {
41
- return {
42
- object: "list",
43
- data: Object.entries(models).map(([id, catalogModel]) => toModel(id, catalogModel!)),
44
- };
45
- }
46
- export function createModelsResponse(models: ModelCatalog, responseInit?: ResponseInit): Response {
47
- return toResponse(toModels(models), responseInit);
48
- }
49
-
50
- export function createModelResponse(
51
- id: string,
52
- catalogModel: CatalogModel,
53
- responseInit?: ResponseInit,
54
- ): Response {
55
- return toResponse(toModel(id, catalogModel), responseInit);
56
- }
@@ -1,122 +0,0 @@
1
- import { MockProviderV3 } from "ai/test";
2
- import { describe, expect, test } from "bun:test";
3
-
4
- import { parseResponse } from "../../../test/helpers/http";
5
- import { models } from "./handler";
6
-
7
- const baseUrl = "http://localhost/models";
8
-
9
- describe("Models Handler", () => {
10
- const endpoint = models({
11
- providers: {
12
- anthropic: new MockProviderV3(),
13
- google: new MockProviderV3(),
14
- },
15
- models: {
16
- "anthropic/claude-opus-4.5": {
17
- name: "Claude Opus 4.5",
18
- created: "2025-09-29T10:00:00.000Z",
19
- knowledge: "2025-07",
20
- modalities: {
21
- input: ["text", "image"],
22
- output: ["text"],
23
- },
24
- context: 200000,
25
- capabilities: ["reasoning", "tool_call"],
26
- providers: ["anthropic"],
27
- },
28
- "google/gemini-3-flash": {
29
- name: "Gemini 3 Flash",
30
- created: "2025-10-01T08:30:00.000Z",
31
- modalities: {
32
- input: ["text", "video"],
33
- output: ["text"],
34
- },
35
- context: 128000,
36
- providers: ["google"],
37
- },
38
- },
39
- });
40
-
41
- test("should list models via GET request with realistic data (exact match)", async () => {
42
- const request = new Request(baseUrl, { method: "GET" });
43
-
44
- const res = await endpoint.handler(request);
45
- const data = await parseResponse(res);
46
-
47
- expect(data).toEqual({
48
- object: "list",
49
- data: [
50
- {
51
- id: "anthropic/claude-opus-4.5",
52
- object: "model",
53
- created: Math.floor(Date.parse("2025-09-29T10:00:00.000Z") / 1000),
54
- owned_by: "anthropic",
55
- name: "Claude Opus 4.5",
56
- knowledge: "2025-07",
57
- context: 200000,
58
- capabilities: ["reasoning", "tool_call"],
59
- architecture: {
60
- modality: "text->text",
61
- input_modalities: ["text", "image"],
62
- output_modalities: ["text"],
63
- },
64
- endpoints: [{ tag: "anthropic" }],
65
- },
66
- {
67
- id: "google/gemini-3-flash",
68
- object: "model",
69
- created: Math.floor(Date.parse("2025-10-01T08:30:00.000Z") / 1000),
70
- owned_by: "google",
71
- name: "Gemini 3 Flash",
72
- context: 128000,
73
- architecture: {
74
- modality: "text->text",
75
- input_modalities: ["text", "video"],
76
- output_modalities: ["text"],
77
- },
78
- endpoints: [{ tag: "google" }],
79
- },
80
- ],
81
- });
82
- });
83
-
84
- test("should return a single model by ID", async () => {
85
- const request = new Request(`${baseUrl}/anthropic/claude-opus-4.5`, { method: "GET" });
86
-
87
- const res = await endpoint.handler(request);
88
- const data = await parseResponse(res);
89
-
90
- expect(data).toEqual({
91
- id: "anthropic/claude-opus-4.5",
92
- object: "model",
93
- created: Math.floor(Date.parse("2025-09-29T10:00:00.000Z") / 1000),
94
- owned_by: "anthropic",
95
- name: "Claude Opus 4.5",
96
- knowledge: "2025-07",
97
- context: 200000,
98
- capabilities: ["reasoning", "tool_call"],
99
- architecture: {
100
- modality: "text->text",
101
- input_modalities: ["text", "image"],
102
- output_modalities: ["text"],
103
- },
104
- endpoints: [{ tag: "anthropic" }],
105
- });
106
- });
107
-
108
- test("should return 'Method Not Allowed' for POST request", async () => {
109
- const request = new Request(baseUrl, { method: "POST" });
110
-
111
- const res = await endpoint.handler(request);
112
- const data = await parseResponse(res);
113
-
114
- expect(data).toMatchObject({
115
- error: {
116
- code: "method_not_allowed",
117
- message: "Method Not Allowed",
118
- type: "invalid_request_error",
119
- },
120
- });
121
- });
122
- });
@@ -1,37 +0,0 @@
1
- import type { GatewayConfig, Endpoint, GatewayContext } from "../../types";
2
-
3
- import { GatewayError } from "../../errors/gateway";
4
- import { winterCgHandler } from "../../lifecycle";
5
- import { toModels, toModel } from "./converters";
6
-
7
- export const models = (config: GatewayConfig): Endpoint => {
8
- // oxlint-disable-next-line require-await
9
- const handler = async (ctx: GatewayContext) => {
10
- ctx.operation = "models";
11
-
12
- if (!ctx.request || ctx.request.method !== "GET") {
13
- throw new GatewayError("Method Not Allowed", 405);
14
- }
15
-
16
- const rawId = ctx.request.url.split("/models/", 2)[1]?.split("?", 1)[0];
17
- if (!rawId) {
18
- return toModels(ctx.models);
19
- }
20
-
21
- let modelId = rawId;
22
- try {
23
- modelId = decodeURIComponent(rawId);
24
- } catch {
25
- throw new GatewayError(`Invalid model ID: '${modelId}'`, 400);
26
- }
27
-
28
- const model = ctx.models[modelId];
29
- if (!model) {
30
- throw new GatewayError(`Model not found: '${modelId}'`, 404);
31
- }
32
-
33
- return toModel(modelId, model);
34
- };
35
-
36
- return { handler: winterCgHandler(handler, config) };
37
- };
@@ -1,3 +0,0 @@
1
- export * from "./converters";
2
- export * from "./handler";
3
- export * from "./schema";
@@ -1,37 +0,0 @@
1
- import * as z from "zod";
2
-
3
- export const ModelSchema = z.looseObject({
4
- // Core
5
- id: z.string(),
6
- object: z.literal("model"),
7
- created: z.int().nonnegative(),
8
- owned_by: z.string(),
9
- // Extensions
10
- name: z.string().optional().meta({ extension: true }),
11
- knowledge: z.string().optional().meta({ extension: true }),
12
- context: z.int().nonnegative().optional().meta({ extension: true }),
13
- architecture: z
14
- .object({
15
- modality: z.string().optional(),
16
- input_modalities: z.array(z.string()).readonly().optional(),
17
- output_modalities: z.array(z.string()).readonly().optional(),
18
- })
19
- .optional()
20
- .meta({ extension: true }),
21
- endpoints: z
22
- .array(
23
- z.object({
24
- tag: z.string(),
25
- }),
26
- )
27
- .optional()
28
- .meta({ extension: true }),
29
- capabilities: z.array(z.string()).readonly().optional().meta({ extension: true }),
30
- });
31
- export type Model = z.infer<typeof ModelSchema>;
32
-
33
- export const ModelListSchema = z.object({
34
- object: z.literal("list"),
35
- data: z.array(ModelSchema),
36
- });
37
- export type ModelList = z.infer<typeof ModelListSchema>;
@@ -1,99 +0,0 @@
1
- import {
2
- AISDKError,
3
- APICallError,
4
- DownloadError,
5
- EmptyResponseBodyError,
6
- InvalidArgumentError,
7
- InvalidDataContentError,
8
- InvalidMessageRoleError,
9
- InvalidPromptError,
10
- InvalidResponseDataError,
11
- InvalidStreamPartError,
12
- InvalidToolApprovalError,
13
- InvalidToolInputError,
14
- JSONParseError,
15
- LoadAPIKeyError,
16
- LoadSettingError,
17
- MessageConversionError,
18
- MissingToolResultsError,
19
- NoContentGeneratedError,
20
- NoImageGeneratedError,
21
- NoObjectGeneratedError,
22
- NoOutputGeneratedError,
23
- NoSpeechGeneratedError,
24
- NoSuchModelError,
25
- NoSuchProviderError,
26
- NoSuchToolError,
27
- NoTranscriptGeneratedError,
28
- NoVideoGeneratedError,
29
- RetryError,
30
- ToolCallNotFoundForApprovalError,
31
- ToolCallRepairError,
32
- TooManyEmbeddingValuesForCallError,
33
- TypeValidationError,
34
- UIMessageStreamError,
35
- UnsupportedModelVersionError,
36
- UnsupportedFunctionalityError,
37
- } from "ai";
38
-
39
- import { GatewayError } from "./gateway";
40
- import { STATUS_CODE } from "./utils";
41
-
42
- export const normalizeAiSdkError = (error: unknown): GatewayError | undefined => {
43
- if (APICallError.isInstance(error)) {
44
- const status = error.statusCode ?? (error.isRetryable ? 502 : 422);
45
- const code = `UPSTREAM_${STATUS_CODE(status)}`;
46
- return new GatewayError(error, status, code);
47
- }
48
-
49
- if (
50
- JSONParseError.isInstance(error) ||
51
- InvalidResponseDataError.isInstance(error) ||
52
- TypeValidationError.isInstance(error) ||
53
- EmptyResponseBodyError.isInstance(error) ||
54
- NoContentGeneratedError.isInstance(error) ||
55
- NoOutputGeneratedError.isInstance(error) ||
56
- InvalidStreamPartError.isInstance(error) ||
57
- UIMessageStreamError.isInstance(error) ||
58
- RetryError.isInstance(error) ||
59
- DownloadError.isInstance(error) ||
60
- ToolCallRepairError.isInstance(error) ||
61
- NoImageGeneratedError.isInstance(error) ||
62
- NoObjectGeneratedError.isInstance(error) ||
63
- NoSpeechGeneratedError.isInstance(error) ||
64
- NoTranscriptGeneratedError.isInstance(error) ||
65
- NoVideoGeneratedError.isInstance(error)
66
- ) {
67
- return new GatewayError(error, 502, `UPSTREAM_${STATUS_CODE(502)}`);
68
- }
69
-
70
- if (
71
- InvalidArgumentError.isInstance(error) ||
72
- InvalidPromptError.isInstance(error) ||
73
- InvalidMessageRoleError.isInstance(error) ||
74
- InvalidDataContentError.isInstance(error) ||
75
- MessageConversionError.isInstance(error) ||
76
- InvalidToolInputError.isInstance(error) ||
77
- InvalidToolApprovalError.isInstance(error) ||
78
- ToolCallNotFoundForApprovalError.isInstance(error) ||
79
- MissingToolResultsError.isInstance(error) ||
80
- NoSuchToolError.isInstance(error) ||
81
- UnsupportedModelVersionError.isInstance(error) ||
82
- UnsupportedFunctionalityError.isInstance(error) ||
83
- TooManyEmbeddingValuesForCallError.isInstance(error) ||
84
- NoSuchModelError.isInstance(error) ||
85
- NoSuchProviderError.isInstance(error)
86
- ) {
87
- return new GatewayError(error, 422, `UPSTREAM_${STATUS_CODE(422)}`);
88
- }
89
-
90
- if (LoadSettingError.isInstance(error) || LoadAPIKeyError.isInstance(error)) {
91
- return new GatewayError(error, 500);
92
- }
93
-
94
- if (AISDKError.isInstance(error)) {
95
- return new GatewayError(error, 500);
96
- }
97
-
98
- return undefined;
99
- };
@@ -1,17 +0,0 @@
1
- import { STATUS_CODE } from "./utils";
2
-
3
- export class GatewayError extends Error {
4
- readonly status: number;
5
- readonly code: string;
6
-
7
- constructor(error: unknown, status: number, code?: string, cause?: unknown) {
8
- const isError = error instanceof Error;
9
- super(isError ? error.message : String(error));
10
-
11
- this.name = "GatewayError";
12
- this.cause = cause ?? (isError ? error : undefined);
13
-
14
- this.status = status;
15
- this.code = code ?? STATUS_CODE(status);
16
- }
17
- }
@@ -1,57 +0,0 @@
1
- import * as z from "zod";
2
-
3
- import { isProduction } from "../utils/env";
4
- import { resolveRequestId } from "../utils/headers";
5
- import { toResponse } from "../utils/response";
6
- import { getErrorMeta, STATUS_CODE } from "./utils";
7
-
8
- export const OpenAIErrorSchema = z.object({
9
- error: z.object({
10
- message: z.string(),
11
- type: z.string(),
12
- code: z.string().optional().nullable(),
13
- param: z.string().optional().nullable(),
14
- }),
15
- });
16
-
17
- export class OpenAIError {
18
- readonly error;
19
-
20
- constructor(message: string, type: string = "server_error", code?: string, param: string = "") {
21
- this.error = { message, type, code: code?.toLowerCase(), param };
22
- }
23
- }
24
-
25
- const mapType = (status: number) => (status < 500 ? "invalid_request_error" : "server_error");
26
-
27
- const maybeMaskMessage = (meta: ReturnType<typeof getErrorMeta>, requestId?: string) => {
28
- // FUTURE: consider masking all upstream errors, also 4xx
29
- if (!(isProduction() && meta.status >= 500)) {
30
- return meta.message;
31
- }
32
- // FUTURE: always attach requestId to errors (masked and unmasked)
33
- return `${STATUS_CODE(meta.status)} (${requestId ?? "see requestId in response headers"})`;
34
- };
35
-
36
- export function toOpenAIError(error: unknown): OpenAIError {
37
- const meta = getErrorMeta(error);
38
-
39
- return new OpenAIError(maybeMaskMessage(meta), mapType(meta.status), meta.code);
40
- }
41
-
42
- export function toOpenAIErrorResponse(error: unknown, responseInit?: ResponseInit) {
43
- const meta = getErrorMeta(error);
44
-
45
- return toResponse(
46
- new OpenAIError(
47
- maybeMaskMessage(meta, resolveRequestId(responseInit)),
48
- mapType(meta.status),
49
- meta.code,
50
- ),
51
- {
52
- ...responseInit,
53
- status: meta.status,
54
- statusText: meta.code,
55
- },
56
- );
57
- }
@@ -1,47 +0,0 @@
1
- import { normalizeAiSdkError } from "./ai-sdk";
2
- import { GatewayError } from "./gateway";
3
-
4
- export const STATUS_CODES = {
5
- 400: "BAD_REQUEST",
6
- 401: "UNAUTHORIZED",
7
- 402: "PAYMENT_REQUIRED",
8
- 403: "FORBIDDEN",
9
- 404: "NOT_FOUND",
10
- 405: "METHOD_NOT_ALLOWED",
11
- 409: "CONFLICT",
12
- 422: "UNPROCESSABLE_ENTITY",
13
- 429: "TOO_MANY_REQUESTS",
14
- 499: "CLIENT_CLOSED_REQUEST",
15
- 500: "INTERNAL_SERVER_ERROR",
16
- 502: "BAD_GATEWAY",
17
- 503: "SERVICE_UNAVAILABLE",
18
- 504: "GATEWAY_TIMEOUT",
19
- } as const;
20
-
21
- export const STATUS_CODE = (status: number) => {
22
- const label = STATUS_CODES[status as keyof typeof STATUS_CODES];
23
- if (label) return label;
24
- return status >= 400 && status < 500 ? STATUS_CODES[400] : STATUS_CODES[500];
25
- };
26
-
27
- // FUTURE: always return a wrapped GatewayError?
28
- export function getErrorMeta(error: unknown) {
29
- const message = error instanceof Error ? error.message : String(error);
30
-
31
- let status: number;
32
- let code: string;
33
-
34
- if (error instanceof GatewayError) {
35
- ({ status, code } = error);
36
- } else {
37
- const normalized = normalizeAiSdkError(error);
38
- if (normalized) {
39
- ({ status, code } = normalized);
40
- } else {
41
- status = 500;
42
- code = STATUS_CODE(status);
43
- }
44
- }
45
-
46
- return { status, code, message };
47
- }
package/src/gateway.ts DELETED
@@ -1,50 +0,0 @@
1
- import type { Endpoint, GatewayConfig, HeboGateway } from "./types";
2
-
3
- import { parseConfig } from "./config";
4
- import { chatCompletions } from "./endpoints/chat-completions/handler";
5
- import { embeddings } from "./endpoints/embeddings/handler";
6
- import { models } from "./endpoints/models/handler";
7
- import { GatewayError } from "./errors/gateway";
8
- import { winterCgHandler } from "./lifecycle";
9
- import { logger } from "./logger";
10
-
11
- let inflight = 0;
12
-
13
- export function gateway(config: GatewayConfig) {
14
- const basePath = (config.basePath ?? "").replace(/\/+$/, "");
15
- const parsedConfig = parseConfig(config);
16
-
17
- const notFoundHandler = winterCgHandler(() => {
18
- throw new GatewayError("Not Found", 404);
19
- }, parsedConfig);
20
-
21
- const routes = {
22
- ["/chat/completions"]: chatCompletions(parsedConfig),
23
- ["/embeddings"]: embeddings(parsedConfig),
24
- ["/models"]: models(parsedConfig),
25
- } as const satisfies Record<string, Endpoint>;
26
-
27
- const routeEntries = Object.entries(routes);
28
-
29
- const handler = (req: Request, state?: Record<string, unknown>): Promise<Response> => {
30
- let pathname = new URL(req.url).pathname;
31
- if (basePath && pathname.startsWith(basePath)) {
32
- pathname = pathname.slice(basePath.length);
33
- }
34
-
35
- logger.info(`[gateway] ${req.method} ${pathname} (${++inflight})`);
36
- for (const [route, endpoint] of routeEntries) {
37
- if (pathname === route || pathname.startsWith(route + "/")) {
38
- try {
39
- return endpoint.handler(req, state);
40
- } finally {
41
- inflight--;
42
- }
43
- }
44
- }
45
-
46
- return notFoundHandler(req, state);
47
- };
48
-
49
- return { handler, routes } satisfies HeboGateway<typeof routes>;
50
- }
package/src/index.ts DELETED
@@ -1,19 +0,0 @@
1
- export * from "./gateway";
2
- export type * from "./types";
3
-
4
- export * from "./errors/gateway";
5
- export * from "./errors/openai";
6
- export * from "./logger";
7
-
8
- export * from "./middleware/common";
9
- export * from "./middleware/matcher";
10
-
11
- export * from "./endpoints/chat-completions";
12
- export * from "./endpoints/embeddings";
13
- export * from "./endpoints/models";
14
-
15
- export * from "./models/catalog";
16
- export * from "./models/types";
17
-
18
- export * from "./providers/registry";
19
- export * from "./providers/types";