@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.
- package/README.md +3 -3
- package/dist/endpoints/chat-completions/converters.js +26 -21
- package/dist/endpoints/chat-completions/handler.js +2 -0
- package/dist/endpoints/chat-completions/otel.js +1 -1
- package/dist/endpoints/chat-completions/schema.d.ts +4 -18
- package/dist/endpoints/chat-completions/schema.js +14 -17
- package/dist/endpoints/embeddings/handler.js +2 -0
- package/dist/endpoints/embeddings/otel.js +5 -0
- package/dist/endpoints/embeddings/schema.d.ts +6 -0
- package/dist/endpoints/embeddings/schema.js +4 -1
- package/dist/endpoints/models/converters.js +3 -3
- package/dist/lifecycle.js +2 -2
- package/dist/logger/default.js +3 -3
- package/dist/logger/index.d.ts +2 -5
- package/dist/middleware/common.js +1 -0
- package/dist/middleware/utils.js +0 -3
- package/dist/models/amazon/middleware.js +8 -5
- package/dist/models/anthropic/middleware.js +13 -13
- package/dist/models/catalog.js +5 -1
- package/dist/models/cohere/middleware.js +7 -5
- package/dist/models/google/middleware.d.ts +1 -1
- package/dist/models/google/middleware.js +29 -25
- package/dist/models/openai/middleware.js +13 -9
- package/dist/models/voyage/middleware.js +2 -1
- package/dist/providers/bedrock/middleware.js +21 -23
- package/dist/providers/registry.js +3 -0
- package/dist/telemetry/fetch.js +7 -2
- package/dist/telemetry/gen-ai.js +15 -12
- package/dist/telemetry/memory.d.ts +1 -1
- package/dist/telemetry/memory.js +30 -14
- package/dist/telemetry/span.js +1 -1
- package/dist/telemetry/stream.js +30 -23
- package/dist/utils/env.js +4 -2
- package/dist/utils/preset.js +1 -0
- package/dist/utils/response.js +3 -1
- package/package.json +36 -50
- package/src/config.ts +0 -98
- package/src/endpoints/chat-completions/converters.test.ts +0 -631
- package/src/endpoints/chat-completions/converters.ts +0 -899
- package/src/endpoints/chat-completions/handler.test.ts +0 -391
- package/src/endpoints/chat-completions/handler.ts +0 -201
- package/src/endpoints/chat-completions/index.ts +0 -4
- package/src/endpoints/chat-completions/otel.test.ts +0 -315
- package/src/endpoints/chat-completions/otel.ts +0 -214
- package/src/endpoints/chat-completions/schema.ts +0 -364
- package/src/endpoints/embeddings/converters.ts +0 -51
- package/src/endpoints/embeddings/handler.test.ts +0 -133
- package/src/endpoints/embeddings/handler.ts +0 -137
- package/src/endpoints/embeddings/index.ts +0 -4
- package/src/endpoints/embeddings/otel.ts +0 -40
- package/src/endpoints/embeddings/schema.ts +0 -36
- package/src/endpoints/models/converters.ts +0 -56
- package/src/endpoints/models/handler.test.ts +0 -122
- package/src/endpoints/models/handler.ts +0 -37
- package/src/endpoints/models/index.ts +0 -3
- package/src/endpoints/models/schema.ts +0 -37
- package/src/errors/ai-sdk.ts +0 -99
- package/src/errors/gateway.ts +0 -17
- package/src/errors/openai.ts +0 -57
- package/src/errors/utils.ts +0 -47
- package/src/gateway.ts +0 -50
- package/src/index.ts +0 -19
- package/src/lifecycle.ts +0 -135
- package/src/logger/default.ts +0 -105
- package/src/logger/index.ts +0 -42
- package/src/middleware/common.test.ts +0 -215
- package/src/middleware/common.ts +0 -163
- package/src/middleware/debug.ts +0 -37
- package/src/middleware/matcher.ts +0 -161
- package/src/middleware/utils.ts +0 -34
- package/src/models/amazon/index.ts +0 -2
- package/src/models/amazon/middleware.test.ts +0 -133
- package/src/models/amazon/middleware.ts +0 -79
- package/src/models/amazon/presets.ts +0 -104
- package/src/models/anthropic/index.ts +0 -2
- package/src/models/anthropic/middleware.test.ts +0 -643
- package/src/models/anthropic/middleware.ts +0 -148
- package/src/models/anthropic/presets.ts +0 -191
- package/src/models/catalog.ts +0 -13
- package/src/models/cohere/index.ts +0 -2
- package/src/models/cohere/middleware.test.ts +0 -138
- package/src/models/cohere/middleware.ts +0 -76
- package/src/models/cohere/presets.ts +0 -186
- package/src/models/google/index.ts +0 -2
- package/src/models/google/middleware.test.ts +0 -298
- package/src/models/google/middleware.ts +0 -137
- package/src/models/google/presets.ts +0 -118
- package/src/models/meta/index.ts +0 -1
- package/src/models/meta/presets.ts +0 -143
- package/src/models/openai/index.ts +0 -2
- package/src/models/openai/middleware.test.ts +0 -189
- package/src/models/openai/middleware.ts +0 -103
- package/src/models/openai/presets.ts +0 -280
- package/src/models/types.ts +0 -114
- package/src/models/voyage/index.ts +0 -2
- package/src/models/voyage/middleware.test.ts +0 -28
- package/src/models/voyage/middleware.ts +0 -23
- package/src/models/voyage/presets.ts +0 -126
- package/src/providers/anthropic/canonical.ts +0 -17
- package/src/providers/anthropic/index.ts +0 -1
- package/src/providers/bedrock/canonical.ts +0 -87
- package/src/providers/bedrock/index.ts +0 -2
- package/src/providers/bedrock/middleware.test.ts +0 -303
- package/src/providers/bedrock/middleware.ts +0 -128
- package/src/providers/cohere/canonical.ts +0 -26
- package/src/providers/cohere/index.ts +0 -1
- package/src/providers/groq/canonical.ts +0 -21
- package/src/providers/groq/index.ts +0 -1
- package/src/providers/openai/canonical.ts +0 -16
- package/src/providers/openai/index.ts +0 -1
- package/src/providers/registry.test.ts +0 -44
- package/src/providers/registry.ts +0 -165
- package/src/providers/types.ts +0 -20
- package/src/providers/vertex/canonical.ts +0 -17
- package/src/providers/vertex/index.ts +0 -1
- package/src/providers/voyage/canonical.ts +0 -16
- package/src/providers/voyage/index.ts +0 -1
- package/src/telemetry/ai-sdk.ts +0 -46
- package/src/telemetry/baggage.ts +0 -27
- package/src/telemetry/fetch.ts +0 -62
- package/src/telemetry/gen-ai.ts +0 -113
- package/src/telemetry/http.ts +0 -62
- package/src/telemetry/index.ts +0 -1
- package/src/telemetry/memory.ts +0 -36
- package/src/telemetry/span.ts +0 -85
- package/src/telemetry/stream.ts +0 -64
- package/src/types.ts +0 -223
- package/src/utils/env.ts +0 -7
- package/src/utils/headers.ts +0 -27
- package/src/utils/preset.ts +0 -65
- package/src/utils/request.test.ts +0 -75
- package/src/utils/request.ts +0 -52
- package/src/utils/response.ts +0 -84
- 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,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>;
|
package/src/errors/ai-sdk.ts
DELETED
|
@@ -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
|
-
};
|
package/src/errors/gateway.ts
DELETED
|
@@ -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
|
-
}
|
package/src/errors/openai.ts
DELETED
|
@@ -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
|
-
}
|
package/src/errors/utils.ts
DELETED
|
@@ -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";
|