@hebo-ai/gateway 0.1.2 → 0.2.0
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 +172 -67
- package/dist/config.js +2 -12
- package/dist/endpoints/chat-completions/converters.d.ts +28 -24
- package/dist/endpoints/chat-completions/converters.js +99 -73
- package/dist/endpoints/chat-completions/handler.js +36 -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 +32 -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 +9 -12
- 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 +44 -0
- package/dist/middleware/matcher.d.ts +18 -0
- package/dist/middleware/matcher.js +83 -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 +2 -0
- package/dist/models/amazon/middleware.js +20 -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 +5 -0
- package/dist/models/anthropic/middleware.js +67 -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 +2 -0
- package/dist/models/cohere/middleware.js +18 -0
- package/dist/models/cohere/presets.d.ts +2918 -0
- package/dist/models/cohere/presets.js +129 -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 +2 -0
- package/dist/models/google/middleware.js +20 -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 +2 -0
- package/dist/models/openai/middleware.js +20 -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 +15 -0
- package/dist/providers/{canonical/bedrock.js → bedrock/canonical.js} +13 -15
- 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 +16 -26
- package/dist/providers/registry.js +19 -26
- 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 +60 -30
- package/dist/utils/errors.js +2 -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 +79 -70
- package/src/config.ts +2 -18
- package/src/endpoints/chat-completions/converters.test.ts +39 -0
- package/src/endpoints/chat-completions/converters.ts +191 -112
- package/src/endpoints/chat-completions/handler.test.ts +47 -18
- package/src/endpoints/chat-completions/handler.ts +40 -34
- 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 +34 -28
- package/src/endpoints/embeddings/schema.ts +10 -10
- package/src/endpoints/models/converters.ts +22 -14
- 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/{utils/hooks.ts → lifecycle.ts} +21 -11
- package/src/middleware/common.ts +68 -0
- package/src/middleware/matcher.ts +117 -0
- package/src/models/amazon/index.ts +2 -0
- package/src/models/amazon/middleware.ts +25 -0
- package/src/models/amazon/presets.ts +104 -0
- package/src/models/anthropic/index.ts +2 -0
- package/src/models/anthropic/middleware.test.ts +184 -0
- package/src/models/anthropic/middleware.ts +75 -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.ts +23 -0
- package/src/models/cohere/presets.ts +181 -0
- package/src/models/google/index.ts +2 -0
- package/src/models/google/middleware.ts +25 -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.ts +25 -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.ts +23 -0
- package/src/providers/anthropic/canonical.ts +17 -0
- package/src/providers/anthropic/index.ts +1 -0
- package/src/providers/{canonical/bedrock.ts → bedrock/canonical.ts} +22 -32
- 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 +43 -43
- 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 +64 -28
- package/src/utils/errors.ts +2 -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/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/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/dist/models/{presets/voyage.js → voyage/presets.js} +10 -10
- package/src/models/{presets/voyage.ts → voyage/presets.ts} +10 -10
|
@@ -1,32 +1,31 @@
|
|
|
1
|
-
import { embedMany } from "ai";
|
|
1
|
+
import { embedMany, wrapEmbeddingModel } from "ai";
|
|
2
2
|
import * as z from "zod/mini";
|
|
3
3
|
|
|
4
|
-
import type { GatewayConfig, Endpoint } from "../../types";
|
|
4
|
+
import type { GatewayConfig, Endpoint, GatewayContext } from "../../types";
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { withLifecycle } from "../../lifecycle";
|
|
7
|
+
import { modelMiddlewareMatcher } from "../../middleware/matcher";
|
|
7
8
|
import { resolveProvider } from "../../providers/registry";
|
|
8
9
|
import { createErrorResponse } from "../../utils/errors";
|
|
9
|
-
import {
|
|
10
|
-
import { transformEmbeddingsInputs, createEmbeddingsResponse } from "./converters";
|
|
10
|
+
import { convertToEmbedCallOptions, createEmbeddingsResponse } from "./converters";
|
|
11
11
|
import { EmbeddingsBodySchema } from "./schema";
|
|
12
12
|
|
|
13
13
|
export const embeddings = (config: GatewayConfig): Endpoint => {
|
|
14
|
-
const
|
|
14
|
+
const hooks = config.hooks;
|
|
15
15
|
|
|
16
|
-
const handler = async (
|
|
17
|
-
if (
|
|
16
|
+
const handler = async (ctx: GatewayContext): Promise<Response> => {
|
|
17
|
+
if (!ctx.request || ctx.request.method !== "POST") {
|
|
18
18
|
return createErrorResponse("METHOD_NOT_ALLOWED", "Method Not Allowed", 405);
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
let body;
|
|
22
22
|
try {
|
|
23
|
-
body = await
|
|
23
|
+
body = await ctx.request.json();
|
|
24
24
|
} catch {
|
|
25
25
|
return createErrorResponse("BAD_REQUEST", "Invalid JSON", 400);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
const parsed = EmbeddingsBodySchema.safeParse(body);
|
|
29
|
-
|
|
30
29
|
if (!parsed.success) {
|
|
31
30
|
return createErrorResponse(
|
|
32
31
|
"UNPROCESSABLE_ENTITY",
|
|
@@ -35,51 +34,58 @@ export const embeddings = (config: GatewayConfig): Endpoint => {
|
|
|
35
34
|
z.prettifyError(parsed.error),
|
|
36
35
|
);
|
|
37
36
|
}
|
|
37
|
+
ctx.body = parsed.data;
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
let inputs;
|
|
40
|
+
({ model: ctx.modelId, ...inputs } = parsed.data);
|
|
40
41
|
|
|
41
|
-
let resolvedModelId;
|
|
42
42
|
try {
|
|
43
|
-
resolvedModelId = (await hooks?.resolveModelId?.(
|
|
43
|
+
ctx.resolvedModelId = (await hooks?.resolveModelId?.(ctx)) ?? ctx.modelId;
|
|
44
44
|
} catch (error) {
|
|
45
45
|
return createErrorResponse("BAD_REQUEST", error, 400);
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
ctx.operation = "embeddings";
|
|
49
49
|
try {
|
|
50
|
-
|
|
50
|
+
const override = await hooks?.resolveProvider?.(ctx);
|
|
51
|
+
ctx.provider =
|
|
52
|
+
override ??
|
|
53
|
+
resolveProvider({
|
|
54
|
+
providers: ctx.providers,
|
|
55
|
+
models: ctx.models,
|
|
56
|
+
modelId: ctx.resolvedModelId,
|
|
57
|
+
operation: ctx.operation,
|
|
58
|
+
});
|
|
51
59
|
} catch (error) {
|
|
52
60
|
return createErrorResponse("BAD_REQUEST", error, 400);
|
|
53
61
|
}
|
|
54
62
|
|
|
55
|
-
|
|
63
|
+
const embeddingModel = ctx.provider.embeddingModel(ctx.resolvedModelId);
|
|
64
|
+
|
|
65
|
+
let embedOptions;
|
|
56
66
|
try {
|
|
57
|
-
|
|
58
|
-
providers,
|
|
59
|
-
models,
|
|
60
|
-
modelId: resolvedModelId,
|
|
61
|
-
operation: "embeddings" as const,
|
|
62
|
-
};
|
|
63
|
-
const override = await hooks?.resolveProvider?.(args);
|
|
64
|
-
provider = override ?? resolveProvider(args);
|
|
67
|
+
embedOptions = convertToEmbedCallOptions(inputs);
|
|
65
68
|
} catch (error) {
|
|
66
69
|
return createErrorResponse("BAD_REQUEST", error, 400);
|
|
67
70
|
}
|
|
68
71
|
|
|
69
|
-
const
|
|
72
|
+
const embeddingModelWithMiddleware = wrapEmbeddingModel({
|
|
73
|
+
model: embeddingModel,
|
|
74
|
+
middleware: modelMiddlewareMatcher.forEmbedding(ctx.resolvedModelId, embeddingModel.provider),
|
|
75
|
+
});
|
|
70
76
|
|
|
71
77
|
let result;
|
|
72
78
|
try {
|
|
73
79
|
result = await embedMany({
|
|
74
|
-
model:
|
|
80
|
+
model: embeddingModelWithMiddleware,
|
|
75
81
|
...embedOptions,
|
|
76
82
|
});
|
|
77
83
|
} catch (error) {
|
|
78
84
|
return createErrorResponse("INTERNAL_SERVER_ERROR", error, 500);
|
|
79
85
|
}
|
|
80
86
|
|
|
81
|
-
return createEmbeddingsResponse(result, modelId);
|
|
87
|
+
return createEmbeddingsResponse(result, ctx.modelId);
|
|
82
88
|
};
|
|
83
89
|
|
|
84
|
-
return { handler:
|
|
90
|
+
return { handler: withLifecycle(handler, config) };
|
|
85
91
|
};
|
|
@@ -1,28 +1,27 @@
|
|
|
1
|
-
import * as z from "zod
|
|
1
|
+
import * as z from "zod";
|
|
2
2
|
|
|
3
3
|
export const EmbeddingsInputsSchema = z.object({
|
|
4
4
|
input: z.union([z.string(), z.array(z.string())]),
|
|
5
|
-
|
|
6
|
-
dimensions: z.optional(z.number()),
|
|
7
|
-
user: z.optional(z.string()),
|
|
5
|
+
dimensions: z.int().nonnegative().max(65536).optional(),
|
|
8
6
|
});
|
|
9
7
|
export type EmbeddingsInputs = z.infer<typeof EmbeddingsInputsSchema>;
|
|
10
8
|
|
|
11
|
-
export const EmbeddingsBodySchema = z.
|
|
9
|
+
export const EmbeddingsBodySchema = z.looseObject({
|
|
12
10
|
model: z.string(),
|
|
11
|
+
...EmbeddingsInputsSchema.shape,
|
|
13
12
|
});
|
|
14
13
|
export type EmbeddingsBody = z.infer<typeof EmbeddingsBodySchema>;
|
|
15
14
|
|
|
16
15
|
export const EmbeddingsDataSchema = z.object({
|
|
17
16
|
object: z.literal("embedding"),
|
|
18
17
|
embedding: z.array(z.number()),
|
|
19
|
-
index: z.
|
|
18
|
+
index: z.int().nonnegative(),
|
|
20
19
|
});
|
|
21
20
|
export type EmbeddingsData = z.infer<typeof EmbeddingsDataSchema>;
|
|
22
21
|
|
|
23
22
|
export const EmbeddingsUsageSchema = z.object({
|
|
24
|
-
prompt_tokens: z.
|
|
25
|
-
total_tokens: z.
|
|
23
|
+
prompt_tokens: z.int().nonnegative().optional(),
|
|
24
|
+
total_tokens: z.int().nonnegative().optional(),
|
|
26
25
|
});
|
|
27
26
|
export type EmbeddingsUsage = z.infer<typeof EmbeddingsUsageSchema>;
|
|
28
27
|
|
|
@@ -30,7 +29,8 @@ export const EmbeddingsSchema = z.object({
|
|
|
30
29
|
object: z.literal("list"),
|
|
31
30
|
data: z.array(EmbeddingsDataSchema),
|
|
32
31
|
model: z.string(),
|
|
33
|
-
usage: EmbeddingsUsageSchema,
|
|
34
|
-
|
|
32
|
+
usage: EmbeddingsUsageSchema.nullable(),
|
|
33
|
+
// Extensions
|
|
34
|
+
provider_metadata: z.optional(z.any()).meta({ extension: true }),
|
|
35
35
|
});
|
|
36
36
|
export type Embeddings = z.infer<typeof EmbeddingsSchema>;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { ModelCatalog, CatalogModel } from "../../models/types";
|
|
2
2
|
import type { ModelList, Model } from "./schema";
|
|
3
3
|
|
|
4
|
+
import { mergeResponseInit } from "../../utils/response";
|
|
5
|
+
|
|
4
6
|
export function toModel(id: string, catalogModel: CatalogModel): Model {
|
|
5
7
|
const { created, providers, modalities, additionalProperties, ...rest } = catalogModel;
|
|
6
8
|
let createdTimestamp = Math.floor(Date.now() / 1000);
|
|
@@ -11,18 +13,18 @@ export function toModel(id: string, catalogModel: CatalogModel): Model {
|
|
|
11
13
|
}
|
|
12
14
|
}
|
|
13
15
|
|
|
14
|
-
const model
|
|
16
|
+
const model = {
|
|
15
17
|
id,
|
|
16
|
-
object: "model",
|
|
18
|
+
object: "model" as const,
|
|
17
19
|
created: createdTimestamp,
|
|
18
20
|
owned_by: providers?.[0] || "system",
|
|
19
21
|
architecture: {
|
|
20
|
-
input_modalities: modalities
|
|
22
|
+
input_modalities: modalities?.input || [],
|
|
21
23
|
modality:
|
|
22
|
-
modalities
|
|
23
|
-
modalities
|
|
24
|
+
modalities?.input &&
|
|
25
|
+
modalities?.output &&
|
|
24
26
|
`${modalities.input?.[0]}->${modalities.output?.[0]}`,
|
|
25
|
-
output_modalities: modalities
|
|
27
|
+
output_modalities: modalities?.output || [],
|
|
26
28
|
},
|
|
27
29
|
endpoints:
|
|
28
30
|
providers?.map((provider) => ({
|
|
@@ -41,14 +43,20 @@ export function toModels(models: ModelCatalog): ModelList {
|
|
|
41
43
|
data: Object.entries(models).map(([id, catalogModel]) => toModel(id, catalogModel!)),
|
|
42
44
|
};
|
|
43
45
|
}
|
|
44
|
-
export function createModelsResponse(models: ModelCatalog): Response {
|
|
45
|
-
return new Response(
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
export function createModelsResponse(models: ModelCatalog, responseInit?: ResponseInit): Response {
|
|
47
|
+
return new Response(
|
|
48
|
+
JSON.stringify(toModels(models)),
|
|
49
|
+
mergeResponseInit({ "Content-Type": "application/json" }, responseInit),
|
|
50
|
+
);
|
|
48
51
|
}
|
|
49
52
|
|
|
50
|
-
export function createModelResponse(
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
53
|
+
export function createModelResponse(
|
|
54
|
+
id: string,
|
|
55
|
+
catalogModel: CatalogModel,
|
|
56
|
+
responseInit?: ResponseInit,
|
|
57
|
+
): Response {
|
|
58
|
+
return new Response(
|
|
59
|
+
JSON.stringify(toModel(id, catalogModel)),
|
|
60
|
+
mergeResponseInit({ "Content-Type": "application/json" }, responseInit),
|
|
61
|
+
);
|
|
54
62
|
}
|
|
@@ -1,46 +1,43 @@
|
|
|
1
|
-
import { createProviderRegistry } from "ai";
|
|
2
1
|
import { MockProviderV3 } from "ai/test";
|
|
3
2
|
import { describe, expect, test } from "bun:test";
|
|
4
3
|
|
|
5
4
|
import { parseResponse } from "../../../test/helpers/http";
|
|
6
|
-
import { createModelCatalog } from "../../models/catalog";
|
|
7
5
|
import { models } from "./handler";
|
|
8
6
|
|
|
9
7
|
const baseUrl = "http://localhost/models";
|
|
10
8
|
|
|
11
9
|
describe("Models Handler", () => {
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const catalog = createModelCatalog({
|
|
18
|
-
"anthropic/claude-opus-4.5": {
|
|
19
|
-
name: "Claude Opus 4.5",
|
|
20
|
-
created: "2025-09-29T10:00:00.000Z",
|
|
21
|
-
knowledge: "2025-07",
|
|
22
|
-
modalities: {
|
|
23
|
-
input: ["text", "image"],
|
|
24
|
-
output: ["text"],
|
|
25
|
-
},
|
|
26
|
-
context: 200000,
|
|
27
|
-
capabilities: ["reasoning", "tool_call"],
|
|
28
|
-
providers: ["anthropic"],
|
|
10
|
+
const endpoint = models({
|
|
11
|
+
providers: {
|
|
12
|
+
anthropic: new MockProviderV3(),
|
|
13
|
+
google: new MockProviderV3(),
|
|
29
14
|
},
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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"],
|
|
36
37
|
},
|
|
37
|
-
context: 128000,
|
|
38
|
-
providers: ["google"],
|
|
39
38
|
},
|
|
40
39
|
});
|
|
41
40
|
|
|
42
|
-
const endpoint = models({ providers: registry, models: catalog });
|
|
43
|
-
|
|
44
41
|
test("should list models via GET request with realistic data (exact match)", async () => {
|
|
45
42
|
const request = new Request(baseUrl, { method: "GET" });
|
|
46
43
|
|
|
@@ -1,23 +1,21 @@
|
|
|
1
|
-
import type { GatewayConfig, Endpoint } from "../../types";
|
|
1
|
+
import type { GatewayConfig, Endpoint, GatewayContext } from "../../types";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { withLifecycle } from "../../lifecycle";
|
|
4
4
|
import { createErrorResponse } from "../../utils/errors";
|
|
5
|
-
import { withHooks } from "../../utils/hooks";
|
|
6
5
|
import { createModelsResponse, createModelResponse } from "./converters";
|
|
7
6
|
|
|
8
7
|
export const models = (config: GatewayConfig): Endpoint => {
|
|
9
|
-
const { models, hooks } = parseConfig(config);
|
|
10
|
-
|
|
11
8
|
// eslint-disable-next-line require-await
|
|
12
|
-
const handler = async (
|
|
13
|
-
|
|
9
|
+
const handler = async (ctx: GatewayContext): Promise<Response> => {
|
|
10
|
+
const request = ctx.request;
|
|
11
|
+
|
|
12
|
+
if (!request || request.method !== "GET") {
|
|
14
13
|
return createErrorResponse("METHOD_NOT_ALLOWED", "Method Not Allowed", 405);
|
|
15
14
|
}
|
|
16
15
|
|
|
17
|
-
const rawId =
|
|
18
|
-
|
|
16
|
+
const rawId = request.url.split("/models/", 2)[1]?.split("?", 1)[0];
|
|
19
17
|
if (!rawId) {
|
|
20
|
-
return createModelsResponse(models);
|
|
18
|
+
return createModelsResponse(ctx.models);
|
|
21
19
|
}
|
|
22
20
|
|
|
23
21
|
let modelId = rawId;
|
|
@@ -27,7 +25,7 @@ export const models = (config: GatewayConfig): Endpoint => {
|
|
|
27
25
|
return createErrorResponse("BAD_REQUEST", "Invalid model ID", 400);
|
|
28
26
|
}
|
|
29
27
|
|
|
30
|
-
const model = models[modelId];
|
|
28
|
+
const model = ctx.models[modelId];
|
|
31
29
|
if (!model) {
|
|
32
30
|
return createErrorResponse("NOT_FOUND", `Model '${modelId}' not found`, 404);
|
|
33
31
|
}
|
|
@@ -35,5 +33,5 @@ export const models = (config: GatewayConfig): Endpoint => {
|
|
|
35
33
|
return createModelResponse(modelId, model);
|
|
36
34
|
};
|
|
37
35
|
|
|
38
|
-
return { handler:
|
|
36
|
+
return { handler: withLifecycle(handler, config) };
|
|
39
37
|
};
|
|
@@ -1,27 +1,33 @@
|
|
|
1
|
-
import * as z from "zod
|
|
1
|
+
import * as z from "zod";
|
|
2
2
|
|
|
3
|
-
export const ModelSchema = z.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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(
|
|
18
23
|
z.object({
|
|
19
24
|
tag: z.string(),
|
|
20
25
|
}),
|
|
21
|
-
)
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
)
|
|
26
|
+
)
|
|
27
|
+
.optional()
|
|
28
|
+
.meta({ extension: true }),
|
|
29
|
+
capabilities: z.array(z.string()).readonly().optional().meta({ extension: true }),
|
|
30
|
+
});
|
|
25
31
|
export type Model = z.infer<typeof ModelSchema>;
|
|
26
32
|
|
|
27
33
|
export const ModelListSchema = z.object({
|
package/src/gateway.ts
CHANGED
|
@@ -1,35 +1,21 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
GatewayConfig,
|
|
4
|
-
GatewayConfigBase,
|
|
5
|
-
GatewayConfigRegistry,
|
|
6
|
-
HeboGateway,
|
|
7
|
-
} from "./types";
|
|
8
|
-
|
|
9
|
-
import { parseConfig } from "./config";
|
|
1
|
+
import type { Endpoint, GatewayConfig, HeboGateway } from "./types";
|
|
2
|
+
|
|
10
3
|
import { chatCompletions } from "./endpoints/chat-completions/handler";
|
|
11
4
|
import { embeddings } from "./endpoints/embeddings/handler";
|
|
12
5
|
import { models } from "./endpoints/models/handler";
|
|
13
6
|
|
|
14
|
-
|
|
15
|
-
(
|
|
7
|
+
export function gateway(config: GatewayConfig) {
|
|
8
|
+
const basePath = (config.basePath ?? "").replace(/\/+$/, "");
|
|
9
|
+
|
|
10
|
+
const routes = {
|
|
16
11
|
["/chat/completions"]: chatCompletions(config),
|
|
17
12
|
["/embeddings"]: embeddings(config),
|
|
18
13
|
["/models"]: models(config),
|
|
19
|
-
}
|
|
14
|
+
} as const satisfies Record<string, Endpoint>;
|
|
20
15
|
|
|
21
|
-
type GatewayRoutes = ReturnType<typeof buildRoutes>;
|
|
22
|
-
|
|
23
|
-
export function gateway(config: GatewayConfigBase): HeboGateway<GatewayRoutes>;
|
|
24
|
-
export function gateway(config: GatewayConfigRegistry): HeboGateway<GatewayRoutes>;
|
|
25
|
-
export function gateway(config: GatewayConfig): HeboGateway<GatewayRoutes> {
|
|
26
|
-
const parsedConfig = parseConfig(config);
|
|
27
|
-
|
|
28
|
-
const basePath = (config.basePath ?? "").replace(/\/+$/, "");
|
|
29
|
-
const routes = buildRoutes(parsedConfig);
|
|
30
16
|
const routeEntries = Object.entries(routes);
|
|
31
17
|
|
|
32
|
-
const handler = (req: Request): Promise<Response> => {
|
|
18
|
+
const handler = (req: Request, state?: Record<string, unknown>): Promise<Response> => {
|
|
33
19
|
let pathname = new URL(req.url).pathname;
|
|
34
20
|
if (basePath && pathname.startsWith(basePath)) {
|
|
35
21
|
pathname = pathname.slice(basePath.length);
|
|
@@ -37,12 +23,12 @@ export function gateway(config: GatewayConfig): HeboGateway<GatewayRoutes> {
|
|
|
37
23
|
|
|
38
24
|
for (const [route, endpoint] of routeEntries) {
|
|
39
25
|
if (pathname === route || pathname.startsWith(route + "/")) {
|
|
40
|
-
return endpoint.handler(req);
|
|
26
|
+
return endpoint.handler(req, state);
|
|
41
27
|
}
|
|
42
28
|
}
|
|
43
29
|
|
|
44
30
|
return Promise.resolve(new Response("Not Found", { status: 404 }));
|
|
45
31
|
};
|
|
46
32
|
|
|
47
|
-
return { handler, routes }
|
|
33
|
+
return { handler, routes } satisfies HeboGateway<typeof routes>;
|
|
48
34
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
export * from "./gateway";
|
|
2
2
|
export type * from "./types";
|
|
3
3
|
|
|
4
|
+
export * from "./middleware/common";
|
|
5
|
+
export * from "./middleware/matcher";
|
|
6
|
+
|
|
4
7
|
export * from "./endpoints/chat-completions";
|
|
5
8
|
export * from "./endpoints/embeddings";
|
|
6
9
|
export * from "./endpoints/models";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { GatewayConfig, GatewayContext, RequestPatch } from "./types";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { parseConfig } from "./config";
|
|
4
|
+
import { createErrorResponse } from "./utils/errors";
|
|
4
5
|
|
|
5
6
|
const maybeApplyRequestPatch = (request: Request, patch: RequestPatch) => {
|
|
6
7
|
if (!patch.headers && patch.body === undefined) return request;
|
|
@@ -21,30 +22,39 @@ const maybeApplyRequestPatch = (request: Request, patch: RequestPatch) => {
|
|
|
21
22
|
return new Request(request, init);
|
|
22
23
|
};
|
|
23
24
|
|
|
24
|
-
export const
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
export const withLifecycle = (
|
|
26
|
+
run: (ctx: GatewayContext) => Promise<Response>,
|
|
27
|
+
config: GatewayConfig,
|
|
27
28
|
) => {
|
|
28
|
-
const
|
|
29
|
+
const parsedConfig = parseConfig(config);
|
|
30
|
+
|
|
31
|
+
const handler = async (request: Request, state?: Record<string, unknown>): Promise<Response> => {
|
|
32
|
+
const context: GatewayContext = {
|
|
33
|
+
request,
|
|
34
|
+
state: state ?? {},
|
|
35
|
+
providers: parsedConfig.providers,
|
|
36
|
+
models: parsedConfig.models,
|
|
37
|
+
};
|
|
38
|
+
|
|
29
39
|
let beforeResult;
|
|
30
40
|
try {
|
|
31
|
-
beforeResult = await hooks?.before?.(
|
|
41
|
+
beforeResult = await parsedConfig.hooks?.before?.(context);
|
|
32
42
|
} catch (error) {
|
|
33
43
|
return createErrorResponse("INTERNAL_SERVER_ERROR", error, 500);
|
|
34
44
|
}
|
|
35
45
|
if (beforeResult instanceof Response) return beforeResult;
|
|
36
46
|
|
|
37
|
-
|
|
47
|
+
context.request = beforeResult ? maybeApplyRequestPatch(request, beforeResult) : request;
|
|
38
48
|
|
|
39
|
-
|
|
49
|
+
context.response = await run(context);
|
|
40
50
|
|
|
41
51
|
let after;
|
|
42
52
|
try {
|
|
43
|
-
after = await hooks?.after?.(
|
|
53
|
+
after = await parsedConfig.hooks?.after?.(context);
|
|
44
54
|
} catch (error) {
|
|
45
55
|
return createErrorResponse("INTERNAL_SERVER_ERROR", error, 500);
|
|
46
56
|
}
|
|
47
|
-
return after ?? response;
|
|
57
|
+
return after ?? context.response;
|
|
48
58
|
};
|
|
49
59
|
|
|
50
60
|
return handler;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { JSONObject } from "@ai-sdk/provider";
|
|
2
|
+
import type { EmbeddingModelMiddleware, LanguageModelMiddleware } from "ai";
|
|
3
|
+
|
|
4
|
+
import type { ProviderId } from "../providers/types";
|
|
5
|
+
|
|
6
|
+
function snakeToCamel(key: string): string {
|
|
7
|
+
return key.replaceAll(/_([a-z])/g, (_, c: string) => c.toUpperCase());
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function camelizeKeysDeep(value: unknown): unknown {
|
|
11
|
+
if (value === null || typeof value !== "object") return value;
|
|
12
|
+
|
|
13
|
+
if (Array.isArray(value)) {
|
|
14
|
+
return value.map((v) => camelizeKeysDeep(v));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const out: Record<string, unknown> = {};
|
|
18
|
+
for (const [k, v] of Object.entries(value)) {
|
|
19
|
+
if (v === undefined || v === null) continue;
|
|
20
|
+
out[snakeToCamel(k)] = camelizeKeysDeep(v);
|
|
21
|
+
}
|
|
22
|
+
return out;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Converts snake_case params in providerOptions to camelCase
|
|
27
|
+
* and moves all of them into providerOptions[providerName].
|
|
28
|
+
*/
|
|
29
|
+
type Kind = "embedding" | "language";
|
|
30
|
+
type MiddlewareFor<K extends Kind> = K extends "embedding"
|
|
31
|
+
? EmbeddingModelMiddleware
|
|
32
|
+
: LanguageModelMiddleware;
|
|
33
|
+
type TransformOptsFor<K extends Kind> = Parameters<
|
|
34
|
+
NonNullable<MiddlewareFor<K>["transformParams"]>
|
|
35
|
+
>[0];
|
|
36
|
+
function forwardParamsForMiddleware<K extends Kind>(
|
|
37
|
+
_kind: K,
|
|
38
|
+
providerName: ProviderId,
|
|
39
|
+
): MiddlewareFor<K> {
|
|
40
|
+
return {
|
|
41
|
+
specificationVersion: "v3" as const,
|
|
42
|
+
// eslint-disable-next-line require-await
|
|
43
|
+
transformParams: async (options: TransformOptsFor<K>) => {
|
|
44
|
+
const { params } = options;
|
|
45
|
+
const providerOptions = params.providerOptions;
|
|
46
|
+
if (!providerOptions) return params;
|
|
47
|
+
|
|
48
|
+
const target = (providerOptions[providerName] ??= {});
|
|
49
|
+
for (const key in providerOptions) {
|
|
50
|
+
if (key === providerName) continue;
|
|
51
|
+
Object.assign(target, camelizeKeysDeep(providerOptions[key]) as Record<string, JSONObject>);
|
|
52
|
+
if (key === "unknown") delete providerOptions[key];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return params;
|
|
56
|
+
},
|
|
57
|
+
} as MiddlewareFor<K>;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function forwardParamsMiddleware(providerName: ProviderId): LanguageModelMiddleware {
|
|
61
|
+
return forwardParamsForMiddleware("language", providerName);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function forwardParamsEmbeddingMiddleware(
|
|
65
|
+
providerName: ProviderId,
|
|
66
|
+
): EmbeddingModelMiddleware {
|
|
67
|
+
return forwardParamsForMiddleware("embedding", providerName);
|
|
68
|
+
}
|