@hebo-ai/gateway 0.3.0 → 0.4.0-alpha.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 +26 -3
- package/dist/endpoints/chat-completions/converters.js +1 -0
- package/dist/endpoints/chat-completions/handler.js +8 -5
- package/dist/endpoints/embeddings/handler.js +5 -3
- package/dist/errors/openai.js +1 -0
- package/dist/lifecycle.js +8 -12
- package/dist/types.d.ts +16 -4
- package/package.json +1 -1
- package/src/endpoints/chat-completions/converters.ts +1 -0
- package/src/endpoints/chat-completions/handler.ts +13 -5
- package/src/endpoints/embeddings/handler.ts +9 -3
- package/src/errors/openai.ts +1 -0
- package/src/lifecycle.ts +14 -14
- package/src/types.ts +22 -4
package/README.md
CHANGED
|
@@ -288,11 +288,23 @@ const gw = gateway({
|
|
|
288
288
|
* @returns Optional RequestPatch to merge into headers / override body.
|
|
289
289
|
* Returning a Response stops execution of the endpoint.
|
|
290
290
|
*/
|
|
291
|
-
|
|
291
|
+
onRequest: async (ctx: { request: Request }): Promise<RequestPatch | Response | void> => {
|
|
292
292
|
// Example Use Cases:
|
|
293
|
-
// - Transform request body
|
|
294
293
|
// - Verify authentication
|
|
295
294
|
// - Enforce rate limits
|
|
295
|
+
return undefined;
|
|
296
|
+
},
|
|
297
|
+
/**
|
|
298
|
+
* Runs after body is parsed & validated.
|
|
299
|
+
* @param ctx.body Parsed request body.
|
|
300
|
+
* @returns Replacement parsed body, or undefined to keep original body unchanged.
|
|
301
|
+
*/
|
|
302
|
+
before: async (ctx: {
|
|
303
|
+
body: ChatCompletionsBody | EmbeddingsBody;
|
|
304
|
+
operation: "text" | "embeddings";
|
|
305
|
+
}): Promise<ChatCompletionsBody | EmbeddingsBody | void> => {
|
|
306
|
+
// Example Use Cases:
|
|
307
|
+
// - Transform request body
|
|
296
308
|
// - Observability integration
|
|
297
309
|
return undefined;
|
|
298
310
|
},
|
|
@@ -344,11 +356,22 @@ const gw = gateway({
|
|
|
344
356
|
// - Result logging
|
|
345
357
|
return undefined;
|
|
346
358
|
},
|
|
359
|
+
/**
|
|
360
|
+
* Runs after the gateway has produced the final Response.
|
|
361
|
+
* @param ctx.response Response object returned by the lifecycle.
|
|
362
|
+
* @returns Replacement response, or undefined to keep original.
|
|
363
|
+
*/
|
|
364
|
+
onResponse: async (ctx: { response: Response }): Promise<Response | void> => {
|
|
365
|
+
// Example Use Cases:
|
|
366
|
+
// - Add response headers
|
|
367
|
+
// - Replace or redact response payload
|
|
368
|
+
return undefined;
|
|
369
|
+
},
|
|
347
370
|
},
|
|
348
371
|
});
|
|
349
372
|
```
|
|
350
373
|
|
|
351
|
-
The `ctx` object is **readonly for core fields**. Use return values to override request / result and to provide modelId / provider instances.
|
|
374
|
+
The `ctx` object is **readonly for core fields**. Use return values to override request / parsed body / result / response and to provide modelId / provider instances.
|
|
352
375
|
|
|
353
376
|
> [!TIP]
|
|
354
377
|
> 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.
|
|
@@ -30,13 +30,14 @@ export const chatCompletions = (config) => {
|
|
|
30
30
|
throw new GatewayError(z.prettifyError(parsed.error), 400);
|
|
31
31
|
}
|
|
32
32
|
ctx.body = parsed.data;
|
|
33
|
+
ctx.operation = "text";
|
|
34
|
+
ctx.body = (await hooks?.before?.(ctx)) ?? ctx.body;
|
|
33
35
|
// Resolve model + provider (hooks may override defaults).
|
|
34
36
|
let inputs, stream;
|
|
35
|
-
({ model: ctx.modelId, stream, ...inputs } =
|
|
37
|
+
({ model: ctx.modelId, stream, ...inputs } = ctx.body);
|
|
36
38
|
ctx.resolvedModelId =
|
|
37
39
|
(await hooks?.resolveModelId?.(ctx)) ?? ctx.modelId;
|
|
38
40
|
logger.debug(`[chat] resolved ${ctx.modelId} to ${ctx.resolvedModelId}`);
|
|
39
|
-
ctx.operation = "text";
|
|
40
41
|
const override = await hooks?.resolveProvider?.(ctx);
|
|
41
42
|
ctx.provider =
|
|
42
43
|
override ??
|
|
@@ -79,7 +80,7 @@ export const chatCompletions = (config) => {
|
|
|
79
80
|
throw new DOMException("Upstream failed", "AbortError");
|
|
80
81
|
},
|
|
81
82
|
timeout: {
|
|
82
|
-
|
|
83
|
+
totalMs: 5 * 60 * 1000,
|
|
83
84
|
},
|
|
84
85
|
experimental_include: {
|
|
85
86
|
requestBody: false,
|
|
@@ -88,7 +89,8 @@ export const chatCompletions = (config) => {
|
|
|
88
89
|
...textOptions,
|
|
89
90
|
});
|
|
90
91
|
markPerf(ctx.request, "aiSdkEnd");
|
|
91
|
-
|
|
92
|
+
ctx.result = toChatCompletionsStream(result, ctx.modelId);
|
|
93
|
+
return (await hooks?.after?.(ctx)) ?? ctx.result;
|
|
92
94
|
}
|
|
93
95
|
const result = await generateText({
|
|
94
96
|
model: languageModelWithMiddleware,
|
|
@@ -104,7 +106,8 @@ export const chatCompletions = (config) => {
|
|
|
104
106
|
});
|
|
105
107
|
markPerf(ctx.request, "aiSdkEnd");
|
|
106
108
|
logger.trace({ requestId: resolveRequestId(ctx.request), result }, "[chat] AI SDK result");
|
|
107
|
-
|
|
109
|
+
ctx.result = toChatCompletions(result, ctx.modelId);
|
|
110
|
+
return (await hooks?.after?.(ctx)) ?? ctx.result;
|
|
108
111
|
};
|
|
109
112
|
return { handler: winterCgHandler(handler, config) };
|
|
110
113
|
};
|
|
@@ -30,13 +30,14 @@ export const embeddings = (config) => {
|
|
|
30
30
|
throw new GatewayError(z.prettifyError(parsed.error), 400);
|
|
31
31
|
}
|
|
32
32
|
ctx.body = parsed.data;
|
|
33
|
+
ctx.operation = "embeddings";
|
|
34
|
+
ctx.body = (await hooks?.before?.(ctx)) ?? ctx.body;
|
|
33
35
|
// Resolve model + provider (hooks may override defaults).
|
|
34
36
|
let inputs;
|
|
35
|
-
({ model: ctx.modelId, ...inputs } =
|
|
37
|
+
({ model: ctx.modelId, ...inputs } = ctx.body);
|
|
36
38
|
ctx.resolvedModelId =
|
|
37
39
|
(await hooks?.resolveModelId?.(ctx)) ?? ctx.modelId;
|
|
38
40
|
logger.debug(`[embeddings] resolved ${ctx.modelId} to ${ctx.resolvedModelId}`);
|
|
39
|
-
ctx.operation = "embeddings";
|
|
40
41
|
const override = await hooks?.resolveProvider?.(ctx);
|
|
41
42
|
ctx.provider =
|
|
42
43
|
override ??
|
|
@@ -67,7 +68,8 @@ export const embeddings = (config) => {
|
|
|
67
68
|
});
|
|
68
69
|
markPerf(ctx.request, "aiSdkEnd");
|
|
69
70
|
logger.trace({ requestId: resolveRequestId(ctx.request), result }, "[embeddings] AI SDK result");
|
|
70
|
-
|
|
71
|
+
ctx.result = toEmbeddings(result, ctx.modelId);
|
|
72
|
+
return (await hooks?.after?.(ctx)) ?? ctx.result;
|
|
71
73
|
};
|
|
72
74
|
return { handler: winterCgHandler(handler, config) };
|
|
73
75
|
};
|
package/dist/errors/openai.js
CHANGED
|
@@ -27,6 +27,7 @@ export function toOpenAIErrorResponse(error, responseInit) {
|
|
|
27
27
|
let message;
|
|
28
28
|
if (shouldMask) {
|
|
29
29
|
const requestId = resolveRequestId(responseInit);
|
|
30
|
+
// FUTURE: always attach requestId to errors (masked and unmasked)
|
|
30
31
|
message = `${STATUS_CODE(meta.status)} (${requestId})`;
|
|
31
32
|
}
|
|
32
33
|
else {
|
package/dist/lifecycle.js
CHANGED
|
@@ -9,23 +9,19 @@ export const winterCgHandler = (run, config) => {
|
|
|
9
9
|
const parsedConfig = parseConfig(config);
|
|
10
10
|
const core = async (ctx) => {
|
|
11
11
|
try {
|
|
12
|
-
const
|
|
13
|
-
if (
|
|
14
|
-
if (
|
|
15
|
-
ctx.response =
|
|
12
|
+
const onRequest = await parsedConfig.hooks?.onRequest?.(ctx);
|
|
13
|
+
if (onRequest) {
|
|
14
|
+
if (onRequest instanceof Response) {
|
|
15
|
+
ctx.response = onRequest;
|
|
16
16
|
return;
|
|
17
17
|
}
|
|
18
|
-
ctx.request = maybeApplyRequestPatch(ctx.request,
|
|
18
|
+
ctx.request = maybeApplyRequestPatch(ctx.request, onRequest);
|
|
19
19
|
}
|
|
20
20
|
ctx.result = await run(ctx);
|
|
21
|
-
const after = await parsedConfig.hooks?.after?.(ctx);
|
|
22
|
-
if (after)
|
|
23
|
-
ctx.result = after;
|
|
24
|
-
if (ctx.result instanceof Response) {
|
|
25
|
-
ctx.response = ctx.result;
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
21
|
ctx.response = toResponse(ctx.result, prepareResponseInit(ctx.request));
|
|
22
|
+
const onResponse = await parsedConfig.hooks?.onResponse?.(ctx);
|
|
23
|
+
if (onResponse)
|
|
24
|
+
ctx.response = onResponse;
|
|
29
25
|
}
|
|
30
26
|
catch (error) {
|
|
31
27
|
logger.error({
|
package/dist/types.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ import type { Logger, LoggerConfig } from "./logger";
|
|
|
5
5
|
import type { ModelCatalog, ModelId } from "./models/types";
|
|
6
6
|
import type { ProviderId, ProviderRegistry } from "./providers/types";
|
|
7
7
|
/**
|
|
8
|
-
* Request overrides returned from the `
|
|
8
|
+
* Request overrides returned from the `onRequest` hook.
|
|
9
9
|
*/
|
|
10
10
|
export type RequestPatch = {
|
|
11
11
|
/**
|
|
@@ -77,10 +77,12 @@ export type HookContext = Omit<Readonly<GatewayContext>, "state"> & {
|
|
|
77
77
|
state: GatewayContext["state"];
|
|
78
78
|
};
|
|
79
79
|
type RequiredHookContext<K extends keyof GatewayContext> = Omit<HookContext, K> & Required<Pick<HookContext, K>>;
|
|
80
|
-
export type
|
|
80
|
+
export type OnRequestHookContext = RequiredHookContext<"request">;
|
|
81
|
+
export type BeforeHookContext = RequiredHookContext<"request" | "body" | "operation">;
|
|
81
82
|
export type ResolveModelHookContext = RequiredHookContext<"request" | "body" | "modelId">;
|
|
82
83
|
export type ResolveProviderHookContext = RequiredHookContext<"request" | "body" | "modelId" | "resolvedModelId" | "operation">;
|
|
83
84
|
export type AfterHookContext = RequiredHookContext<"request" | "result" | "provider" | "resolvedModelId" | "operation">;
|
|
85
|
+
export type OnResponseHookContext = RequiredHookContext<"request" | "response">;
|
|
84
86
|
/**
|
|
85
87
|
* Hooks to plugin to the gateway lifecycle.
|
|
86
88
|
*/
|
|
@@ -90,7 +92,12 @@ export type GatewayHooks = {
|
|
|
90
92
|
* @returns Optional RequestPatch to merge into headers / override body,
|
|
91
93
|
* or Response to short-circuit the request.
|
|
92
94
|
*/
|
|
93
|
-
|
|
95
|
+
onRequest?: (ctx: OnRequestHookContext) => void | RequestPatch | Response | Promise<void | RequestPatch | Response>;
|
|
96
|
+
/**
|
|
97
|
+
* Runs after request JSON is parsed and validated for chat completions / embeddings.
|
|
98
|
+
* @returns Replacement parsed body, or undefined to keep original.
|
|
99
|
+
*/
|
|
100
|
+
before?: (ctx: BeforeHookContext) => void | ChatCompletionsBody | EmbeddingsBody | Promise<void | ChatCompletionsBody | EmbeddingsBody>;
|
|
94
101
|
/**
|
|
95
102
|
* Maps a user-provided model ID or alias to a canonical ID.
|
|
96
103
|
* @returns Canonical model ID or undefined to keep original.
|
|
@@ -103,9 +110,14 @@ export type GatewayHooks = {
|
|
|
103
110
|
resolveProvider?: (ctx: ResolveProviderHookContext) => ProviderV3 | void | Promise<ProviderV3 | void>;
|
|
104
111
|
/**
|
|
105
112
|
* Runs after the endpoint handler.
|
|
106
|
-
* @returns
|
|
113
|
+
* @returns Result to replace, or undefined to keep original.
|
|
107
114
|
*/
|
|
108
115
|
after?: (ctx: AfterHookContext) => void | object | ReadableStream<Uint8Array> | Promise<void | object | ReadableStream<Uint8Array>>;
|
|
116
|
+
/**
|
|
117
|
+
* Runs after the lifecycle has produced the final Response.
|
|
118
|
+
* @returns Replacement Response, or undefined to keep original.
|
|
119
|
+
*/
|
|
120
|
+
onResponse?: (ctx: OnResponseHookContext) => void | Response | Promise<void | Response>;
|
|
109
121
|
};
|
|
110
122
|
/**
|
|
111
123
|
* Main configuration object for the gateway.
|
package/package.json
CHANGED
|
@@ -2,6 +2,8 @@ import { generateText, streamText, wrapLanguageModel } from "ai";
|
|
|
2
2
|
import * as z from "zod/mini";
|
|
3
3
|
|
|
4
4
|
import type {
|
|
5
|
+
AfterHookContext,
|
|
6
|
+
BeforeHookContext,
|
|
5
7
|
GatewayConfig,
|
|
6
8
|
Endpoint,
|
|
7
9
|
GatewayContext,
|
|
@@ -43,15 +45,17 @@ export const chatCompletions = (config: GatewayConfig): Endpoint => {
|
|
|
43
45
|
}
|
|
44
46
|
ctx.body = parsed.data;
|
|
45
47
|
|
|
48
|
+
ctx.operation = "text";
|
|
49
|
+
ctx.body = (await hooks?.before?.(ctx as BeforeHookContext)) ?? ctx.body;
|
|
50
|
+
|
|
46
51
|
// Resolve model + provider (hooks may override defaults).
|
|
47
52
|
let inputs, stream;
|
|
48
|
-
({ model: ctx.modelId, stream, ...inputs } =
|
|
53
|
+
({ model: ctx.modelId, stream, ...inputs } = ctx.body);
|
|
49
54
|
|
|
50
55
|
ctx.resolvedModelId =
|
|
51
56
|
(await hooks?.resolveModelId?.(ctx as ResolveModelHookContext)) ?? ctx.modelId;
|
|
52
57
|
logger.debug(`[chat] resolved ${ctx.modelId} to ${ctx.resolvedModelId}`);
|
|
53
58
|
|
|
54
|
-
ctx.operation = "text";
|
|
55
59
|
const override = await hooks?.resolveProvider?.(ctx as ResolveProviderHookContext);
|
|
56
60
|
ctx.provider =
|
|
57
61
|
override ??
|
|
@@ -101,7 +105,7 @@ export const chatCompletions = (config: GatewayConfig): Endpoint => {
|
|
|
101
105
|
throw new DOMException("Upstream failed", "AbortError");
|
|
102
106
|
},
|
|
103
107
|
timeout: {
|
|
104
|
-
|
|
108
|
+
totalMs: 5 * 60 * 1000,
|
|
105
109
|
},
|
|
106
110
|
experimental_include: {
|
|
107
111
|
requestBody: false,
|
|
@@ -111,7 +115,9 @@ export const chatCompletions = (config: GatewayConfig): Endpoint => {
|
|
|
111
115
|
});
|
|
112
116
|
markPerf(ctx.request, "aiSdkEnd");
|
|
113
117
|
|
|
114
|
-
|
|
118
|
+
ctx.result = toChatCompletionsStream(result, ctx.modelId);
|
|
119
|
+
|
|
120
|
+
return (await hooks?.after?.(ctx as AfterHookContext)) ?? ctx.result;
|
|
115
121
|
}
|
|
116
122
|
|
|
117
123
|
const result = await generateText({
|
|
@@ -130,7 +136,9 @@ export const chatCompletions = (config: GatewayConfig): Endpoint => {
|
|
|
130
136
|
|
|
131
137
|
logger.trace({ requestId: resolveRequestId(ctx.request), result }, "[chat] AI SDK result");
|
|
132
138
|
|
|
133
|
-
|
|
139
|
+
ctx.result = toChatCompletions(result, ctx.modelId);
|
|
140
|
+
|
|
141
|
+
return (await hooks?.after?.(ctx as AfterHookContext)) ?? ctx.result;
|
|
134
142
|
};
|
|
135
143
|
|
|
136
144
|
return { handler: winterCgHandler(handler, config) };
|
|
@@ -2,6 +2,8 @@ import { embedMany, wrapEmbeddingModel } from "ai";
|
|
|
2
2
|
import * as z from "zod/mini";
|
|
3
3
|
|
|
4
4
|
import type {
|
|
5
|
+
AfterHookContext,
|
|
6
|
+
BeforeHookContext,
|
|
5
7
|
GatewayConfig,
|
|
6
8
|
Endpoint,
|
|
7
9
|
GatewayContext,
|
|
@@ -43,15 +45,17 @@ export const embeddings = (config: GatewayConfig): Endpoint => {
|
|
|
43
45
|
}
|
|
44
46
|
ctx.body = parsed.data;
|
|
45
47
|
|
|
48
|
+
ctx.operation = "embeddings";
|
|
49
|
+
ctx.body = (await hooks?.before?.(ctx as BeforeHookContext)) ?? ctx.body;
|
|
50
|
+
|
|
46
51
|
// Resolve model + provider (hooks may override defaults).
|
|
47
52
|
let inputs;
|
|
48
|
-
({ model: ctx.modelId, ...inputs } =
|
|
53
|
+
({ model: ctx.modelId, ...inputs } = ctx.body);
|
|
49
54
|
|
|
50
55
|
ctx.resolvedModelId =
|
|
51
56
|
(await hooks?.resolveModelId?.(ctx as ResolveModelHookContext)) ?? ctx.modelId;
|
|
52
57
|
logger.debug(`[embeddings] resolved ${ctx.modelId} to ${ctx.resolvedModelId}`);
|
|
53
58
|
|
|
54
|
-
ctx.operation = "embeddings";
|
|
55
59
|
const override = await hooks?.resolveProvider?.(ctx as ResolveProviderHookContext);
|
|
56
60
|
ctx.provider =
|
|
57
61
|
override ??
|
|
@@ -94,7 +98,9 @@ export const embeddings = (config: GatewayConfig): Endpoint => {
|
|
|
94
98
|
"[embeddings] AI SDK result",
|
|
95
99
|
);
|
|
96
100
|
|
|
97
|
-
|
|
101
|
+
ctx.result = toEmbeddings(result, ctx.modelId);
|
|
102
|
+
|
|
103
|
+
return (await hooks?.after?.(ctx as AfterHookContext)) ?? ctx.result;
|
|
98
104
|
};
|
|
99
105
|
|
|
100
106
|
return { handler: winterCgHandler(handler, config) };
|
package/src/errors/openai.ts
CHANGED
|
@@ -35,6 +35,7 @@ export function toOpenAIErrorResponse(error: unknown, responseInit?: ResponseIni
|
|
|
35
35
|
let message;
|
|
36
36
|
if (shouldMask) {
|
|
37
37
|
const requestId = resolveRequestId(responseInit);
|
|
38
|
+
// FUTURE: always attach requestId to errors (masked and unmasked)
|
|
38
39
|
message = `${STATUS_CODE(meta.status)} (${requestId})`;
|
|
39
40
|
} else {
|
|
40
41
|
message = meta.message;
|
package/src/lifecycle.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
GatewayConfig,
|
|
3
|
+
GatewayContext,
|
|
4
|
+
OnRequestHookContext,
|
|
5
|
+
OnResponseHookContext,
|
|
6
|
+
} from "./types";
|
|
2
7
|
|
|
3
8
|
import { parseConfig } from "./config";
|
|
4
9
|
import { toOpenAIErrorResponse } from "./errors/openai";
|
|
@@ -16,25 +21,20 @@ export const winterCgHandler = (
|
|
|
16
21
|
|
|
17
22
|
const core = async (ctx: GatewayContext): Promise<void> => {
|
|
18
23
|
try {
|
|
19
|
-
const
|
|
20
|
-
if (
|
|
21
|
-
if (
|
|
22
|
-
ctx.response =
|
|
24
|
+
const onRequest = await parsedConfig.hooks?.onRequest?.(ctx as OnRequestHookContext);
|
|
25
|
+
if (onRequest) {
|
|
26
|
+
if (onRequest instanceof Response) {
|
|
27
|
+
ctx.response = onRequest;
|
|
23
28
|
return;
|
|
24
29
|
}
|
|
25
|
-
ctx.request = maybeApplyRequestPatch(ctx.request,
|
|
30
|
+
ctx.request = maybeApplyRequestPatch(ctx.request, onRequest);
|
|
26
31
|
}
|
|
27
32
|
|
|
28
33
|
ctx.result = await run(ctx);
|
|
29
|
-
|
|
30
|
-
const after = await parsedConfig.hooks?.after?.(ctx as AfterHookContext);
|
|
31
|
-
if (after) ctx.result = after;
|
|
32
|
-
|
|
33
|
-
if (ctx.result instanceof Response) {
|
|
34
|
-
ctx.response = ctx.result;
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
34
|
ctx.response = toResponse(ctx.result, prepareResponseInit(ctx.request));
|
|
35
|
+
|
|
36
|
+
const onResponse = await parsedConfig.hooks?.onResponse?.(ctx as OnResponseHookContext);
|
|
37
|
+
if (onResponse) ctx.response = onResponse;
|
|
38
38
|
} catch (error) {
|
|
39
39
|
logger.error({
|
|
40
40
|
requestId: resolveRequestId(ctx.request)!,
|
package/src/types.ts
CHANGED
|
@@ -7,7 +7,7 @@ import type { ModelCatalog, ModelId } from "./models/types";
|
|
|
7
7
|
import type { ProviderId, ProviderRegistry } from "./providers/types";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
* Request overrides returned from the `
|
|
10
|
+
* Request overrides returned from the `onRequest` hook.
|
|
11
11
|
*/
|
|
12
12
|
export type RequestPatch = {
|
|
13
13
|
/**
|
|
@@ -83,7 +83,8 @@ export type HookContext = Omit<Readonly<GatewayContext>, "state"> & {
|
|
|
83
83
|
|
|
84
84
|
type RequiredHookContext<K extends keyof GatewayContext> = Omit<HookContext, K> &
|
|
85
85
|
Required<Pick<HookContext, K>>;
|
|
86
|
-
export type
|
|
86
|
+
export type OnRequestHookContext = RequiredHookContext<"request">;
|
|
87
|
+
export type BeforeHookContext = RequiredHookContext<"request" | "body" | "operation">;
|
|
87
88
|
export type ResolveModelHookContext = RequiredHookContext<"request" | "body" | "modelId">;
|
|
88
89
|
export type ResolveProviderHookContext = RequiredHookContext<
|
|
89
90
|
"request" | "body" | "modelId" | "resolvedModelId" | "operation"
|
|
@@ -91,6 +92,7 @@ export type ResolveProviderHookContext = RequiredHookContext<
|
|
|
91
92
|
export type AfterHookContext = RequiredHookContext<
|
|
92
93
|
"request" | "result" | "provider" | "resolvedModelId" | "operation"
|
|
93
94
|
>;
|
|
95
|
+
export type OnResponseHookContext = RequiredHookContext<"request" | "response">;
|
|
94
96
|
|
|
95
97
|
/**
|
|
96
98
|
* Hooks to plugin to the gateway lifecycle.
|
|
@@ -101,9 +103,20 @@ export type GatewayHooks = {
|
|
|
101
103
|
* @returns Optional RequestPatch to merge into headers / override body,
|
|
102
104
|
* or Response to short-circuit the request.
|
|
103
105
|
*/
|
|
106
|
+
onRequest?: (
|
|
107
|
+
ctx: OnRequestHookContext,
|
|
108
|
+
) => void | RequestPatch | Response | Promise<void | RequestPatch | Response>;
|
|
109
|
+
/**
|
|
110
|
+
* Runs after request JSON is parsed and validated for chat completions / embeddings.
|
|
111
|
+
* @returns Replacement parsed body, or undefined to keep original.
|
|
112
|
+
*/
|
|
104
113
|
before?: (
|
|
105
114
|
ctx: BeforeHookContext,
|
|
106
|
-
) =>
|
|
115
|
+
) =>
|
|
116
|
+
| void
|
|
117
|
+
| ChatCompletionsBody
|
|
118
|
+
| EmbeddingsBody
|
|
119
|
+
| Promise<void | ChatCompletionsBody | EmbeddingsBody>;
|
|
107
120
|
/**
|
|
108
121
|
* Maps a user-provided model ID or alias to a canonical ID.
|
|
109
122
|
* @returns Canonical model ID or undefined to keep original.
|
|
@@ -118,7 +131,7 @@ export type GatewayHooks = {
|
|
|
118
131
|
) => ProviderV3 | void | Promise<ProviderV3 | void>;
|
|
119
132
|
/**
|
|
120
133
|
* Runs after the endpoint handler.
|
|
121
|
-
* @returns
|
|
134
|
+
* @returns Result to replace, or undefined to keep original.
|
|
122
135
|
*/
|
|
123
136
|
after?: (
|
|
124
137
|
ctx: AfterHookContext,
|
|
@@ -127,6 +140,11 @@ export type GatewayHooks = {
|
|
|
127
140
|
| object
|
|
128
141
|
| ReadableStream<Uint8Array>
|
|
129
142
|
| Promise<void | object | ReadableStream<Uint8Array>>;
|
|
143
|
+
/**
|
|
144
|
+
* Runs after the lifecycle has produced the final Response.
|
|
145
|
+
* @returns Replacement Response, or undefined to keep original.
|
|
146
|
+
*/
|
|
147
|
+
onResponse?: (ctx: OnResponseHookContext) => void | Response | Promise<void | Response>;
|
|
130
148
|
};
|
|
131
149
|
|
|
132
150
|
/**
|