ai-gateway-proxy 0.0.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.
@@ -0,0 +1,329 @@
1
+ import { LanguageModelV4CallOptions, LanguageModelV4Content, LanguageModelV4FinishReason, LanguageModelV4Usage, SharedV4Warning, SharedV4ProviderMetadata, LanguageModelV4StreamPart } from '@ai-sdk/provider';
2
+
3
+ type GatewayAuthToken = {
4
+ token: string;
5
+ authMethod: "api-key" | "oidc";
6
+ };
7
+ /**
8
+ * Get authentication token for the AI Gateway.
9
+ * Tries API key first (AI_GATEWAY_API_KEY env var), then falls back to Vercel OIDC.
10
+ * Returns `null` when neither is available, after logging why OIDC failed, and the proxy then answers 500.
11
+ */
12
+ declare function getGatewayAuthToken(): Promise<GatewayAuthToken | null>;
13
+
14
+ /**
15
+ * The response body from the AI Gateway for non-streaming requests.
16
+ * For streaming requests, this is aggregated from the stream events.
17
+ */
18
+ type GatewayResponse = {
19
+ content: LanguageModelV4Content[];
20
+ finishReason: LanguageModelV4FinishReason;
21
+ /** `undefined` when a stream ended without a `finish` event, so the usage is unknown. */
22
+ usage: LanguageModelV4Usage | undefined;
23
+ warnings: SharedV4Warning[];
24
+ providerMetadata?: SharedV4ProviderMetadata;
25
+ /**
26
+ * The provider's response id, model and start time, from a stream's `response-metadata` parts.
27
+ * `timestamp` is the string sent over the wire, not a `Date`. Neither the proxy nor the client reads it from a result.
28
+ */
29
+ response?: GatewayResponseMetadata;
30
+ };
31
+ /**
32
+ * What a stream's `response-metadata` parts say about the response, the latest value of each field winning.
33
+ */
34
+ type GatewayResponseMetadata = {
35
+ id?: string;
36
+ modelId?: string;
37
+ timestamp?: string;
38
+ };
39
+ /**
40
+ * Why a stream had no `finish` event; the cancel reason is in `cancelReason`.
41
+ */
42
+ type IncompleteStream = {
43
+ reason: "ended";
44
+ } | {
45
+ reason: "error";
46
+ error: unknown;
47
+ } | {
48
+ reason: "cancelled";
49
+ cancelReason?: unknown;
50
+ };
51
+ /**
52
+ * Error response from the AI Gateway.
53
+ */
54
+ type GatewayError = {
55
+ error: {
56
+ message: string;
57
+ type: string;
58
+ param?: unknown;
59
+ code?: string | number | null;
60
+ };
61
+ /** The gateway's id for the failed generation, which `@ai-sdk/gateway` appends to the error message. */
62
+ generationId?: string | null;
63
+ };
64
+ /**
65
+ * The kind of a call that is not a language model call, named after the `@ai-sdk/gateway` model it comes from.
66
+ */
67
+ type ModelCallKind = "embedding" | "image" | "video" | "speech" | "transcription" | "reranking" | "decision" | "batch";
68
+ /**
69
+ * What `beforeRawRequest` receives.
70
+ */
71
+ type RawRequestContext = {
72
+ kind: ModelCallKind;
73
+ /** The upstream path relative to `baseUrl`, such as `embedding-model` or `batch/start`. */
74
+ path: string;
75
+ /** The `ai-model-id` header the client sent, which names the model; a batch names it per request too. */
76
+ modelId: string | undefined;
77
+ /** The parsed JSON body, unchecked. */
78
+ request: unknown;
79
+ };
80
+ /**
81
+ * What `afterRawResponse` receives.
82
+ */
83
+ type RawResponseContext = RawRequestContext & {
84
+ /**
85
+ * The response the proxy would send: the upstream status, `content-type` and allowlisted
86
+ * headers, with the upstream body on success and a `GatewayError` JSON body on failure.
87
+ * Its body can be read once, so read `response.clone()` to leave it for the client.
88
+ */
89
+ response: Response;
90
+ };
91
+ /**
92
+ * Options for creating a gateway proxy.
93
+ */
94
+ type CreateGatewayProxyOptions = {
95
+ /**
96
+ * Base URL for the AI Gateway. A trailing slash is accepted.
97
+ * Request paths are resolved under it and cannot leave it; one that would returns 400 Invalid path.
98
+ * @default "https://ai-gateway.vercel.sh/v4/ai"
99
+ */
100
+ baseUrl?: string;
101
+ /**
102
+ * Additional headers to send with requests.
103
+ * The proxy sets `authorization`, `ai-gateway-auth-method` and `ai-gateway-protocol-version` after these, so they win.
104
+ * On requests with a body it also sets `content-type`, drops entries naming the model call
105
+ * (`ai-language-model-*`, `ai-model-id`, `ai-*-model-specification-version`) in favour of the
106
+ * incoming request's, and lets the incoming `ai-o11y-*`, `idempotency-key` and `accept` override these.
107
+ */
108
+ headers?: Record<string, string>;
109
+ /**
110
+ * The name of the catch-all parameter in the route.
111
+ * @default "segments"
112
+ * @example
113
+ * // For Next.js route `app/api/ai/[...path]/route.ts`
114
+ * createGatewayProxy({ segmentsParam: "path" })
115
+ */
116
+ segmentsParam?: string;
117
+ /**
118
+ * Custom function to extract the path from the request.
119
+ * Use this for frameworks without catch-all route support (e.g., Hono, Express).
120
+ *
121
+ * When provided, `segmentsParam` is ignored.
122
+ *
123
+ * Return a path relative to `baseUrl`, without a leading `/`.
124
+ * A path that starts with `/`, contains `://`, or has a segment that fails to decode or decodes
125
+ * to `.` or `..` or contains `/`, `\` or a NUL byte returns 400 Invalid path.
126
+ *
127
+ * @example
128
+ * // Hono with a base path
129
+ * createGatewayProxy({
130
+ * extractPath: (request) => {
131
+ * const url = new URL(request.url);
132
+ * return url.pathname.replace(/^\/api\/ai\//, "");
133
+ * }
134
+ * })
135
+ */
136
+ extractPath?: (request: Request) => string | Promise<string>;
137
+ /**
138
+ * Called before the request is sent to the gateway.
139
+ * Allows modification of the request body. Returning `undefined` or `null` sends the
140
+ * parsed body unchanged and logs an error. Any other result that is not an object, such as
141
+ * a string or an array, is not sent: the client gets a 500 and the gateway is not called.
142
+ *
143
+ * Not called for GET, HEAD or other requests without a body, which are forwarded as is.
144
+ * A body that is JSON but not an object gets a 400 before this or any other language model
145
+ * hook runs.
146
+ *
147
+ * Not called when the path relative to `baseUrl` is exactly one of the paths `@ai-sdk/gateway`
148
+ * uses for other calls: `embedding-model`, `image-model`, `video-model`, `video-model/start`,
149
+ * `video-model/status`, `speech-model`, `transcription-model`, `reranking-model`,
150
+ * `decision-model`, `batch/start`, `batch/status`, `batch/results` and `batch/cancel`.
151
+ * Every other path runs it, whatever headers the request sends.
152
+ */
153
+ beforeRequest?: (context: {
154
+ request: LanguageModelV4CallOptions;
155
+ /** The `ai-language-model-id` header the client sent, which names the model; the body does not. */
156
+ modelId?: string;
157
+ }) => LanguageModelV4CallOptions | Promise<LanguageModelV4CallOptions>;
158
+ /**
159
+ * Called after a successful response is received from the gateway.
160
+ * Allows modification of the response body.
161
+ *
162
+ * For non-streaming responses, the returned object replaces the JSON body sent to the client.
163
+ * A result with a missing or wrong-typed field is not sent: the client gets a 500 naming the
164
+ * field, and on a stream the original finish event is forwarded. `null` stands for an absent
165
+ * field, and `warnings` may be left out. An absent `usage` keeps the gateway's, on both paths.
166
+ *
167
+ * For streaming responses, this is called when the stream completes with
168
+ * the aggregated response. The content has already been streamed, but you
169
+ * can modify the finish event metadata (usage, finishReason, providerMetadata).
170
+ * If the hook throws while handling a stream, the error is logged and the
171
+ * original finish event is forwarded unchanged. It runs once per stream: a
172
+ * later finish event gets the same rewrite, or none if the hook failed.
173
+ *
174
+ * A stream that ends, fails or is cancelled before its finish event calls the hook once
175
+ * with the content received so far, `incomplete` set, `response.usage` undefined and
176
+ * `response.finishReason` `error` when the read failed or the stream sent an `error` part,
177
+ * `other` otherwise. Nothing can be rewritten then, so the
178
+ * returned value is ignored and a throw is logged.
179
+ *
180
+ * Not called for an event stream or JSON Lines body answering a request not marked
181
+ * `ai-language-model-streaming: true`, such as a video model call or batch results, which is
182
+ * forwarded as is.
183
+ *
184
+ * Not called for a 204 or 205 response, which is forwarded without a body.
185
+ *
186
+ * Not called for GET, HEAD or other requests without a body, which are forwarded as is.
187
+ *
188
+ * Not called when the path relative to `baseUrl` is exactly one of the paths `@ai-sdk/gateway`
189
+ * uses for other calls: `embedding-model`, `image-model`, `video-model`, `video-model/start`,
190
+ * `video-model/status`, `speech-model`, `transcription-model`, `reranking-model`,
191
+ * `decision-model`, `batch/start`, `batch/status`, `batch/results` and `batch/cancel`.
192
+ * Every other path runs it, whatever headers the request sends.
193
+ */
194
+ afterResponse?: (context: {
195
+ request: LanguageModelV4CallOptions;
196
+ /** The `ai-language-model-id` header the client sent, which names the model; the body does not. */
197
+ modelId?: string;
198
+ response: GatewayResponse;
199
+ /** Set only for a stream that did not deliver its `finish` event. */
200
+ incomplete?: IncompleteStream;
201
+ }) => GatewayResponse | Promise<GatewayResponse>;
202
+ /**
203
+ * Called when the gateway returns an error response.
204
+ * Allows custom error handling or modification.
205
+ *
206
+ * A returned error body is sent as JSON with the upstream status.
207
+ * A returned `Response` is sent as is.
208
+ * Returning `undefined`, `null` or a body without a string `error.message` (or with a
209
+ * non-string `error.type`, an `error.code` that is neither a string nor a number, or a
210
+ * non-string `generationId`) logs an error and sends the upstream error body instead, and so
211
+ * does a hook that throws.
212
+ *
213
+ * Not called for GET, HEAD or other requests without a body, which are forwarded as is.
214
+ *
215
+ * Not called when the path relative to `baseUrl` is exactly one of the paths `@ai-sdk/gateway`
216
+ * uses for other calls: `embedding-model`, `image-model`, `video-model`, `video-model/start`,
217
+ * `video-model/status`, `speech-model`, `transcription-model`, `reranking-model`,
218
+ * `decision-model`, `batch/start`, `batch/status`, `batch/results` and `batch/cancel`.
219
+ * Every other path runs it, whatever headers the request sends.
220
+ */
221
+ onError?: (context: {
222
+ request: LanguageModelV4CallOptions;
223
+ /** The `ai-language-model-id` header the client sent, which names the model; the body does not. */
224
+ modelId?: string;
225
+ error: GatewayError;
226
+ status: number;
227
+ }) => GatewayError | Response | Promise<GatewayError | Response>;
228
+ /**
229
+ * Called before a call that is not a language model call is sent to the gateway: a request
230
+ * with a body whose path relative to `baseUrl` is exactly `embedding-model`, `image-model`,
231
+ * `video-model`, `video-model/start`, `speech-model`, `transcription-model`,
232
+ * `reranking-model`, `decision-model` or `batch/start`. Job reads (`video-model/status` and
233
+ * `batch/status`, `batch/results`, `batch/cancel`) run no hook.
234
+ *
235
+ * A returned value replaces the body sent upstream; `undefined` or `null` sends the body as is.
236
+ */
237
+ beforeRawRequest?: (context: RawRequestContext) => unknown;
238
+ /**
239
+ * Called with the response to a call `beforeRawRequest` would run for, on success and on an
240
+ * upstream error status, but not on a redirect, which is answered with a 502, nor when the
241
+ * upstream request fails.
242
+ *
243
+ * A returned `Response` is sent instead. Returning nothing sends `response`, which must then
244
+ * be unread; a hook that read it without returning a `Response`, or returned anything else,
245
+ * is a 500. When the hook returns another `Response`, throws or returns anything else, an
246
+ * unread `response.body` is cancelled unless the returned `Response` is built on it.
247
+ */
248
+ afterRawResponse?: (context: RawResponseContext) => Response | undefined | Promise<Response | undefined>;
249
+ };
250
+ /**
251
+ * Route handler function signature for the gateway proxy.
252
+ * Compatible with Next.js App Router and other frameworks.
253
+ * Takes a Fetch API `Request`, not a Node or Express request object.
254
+ */
255
+ type CreateGatewayProxyFn = (request: Request, context?: {
256
+ params: Promise<Record<string, string | string[] | undefined>>;
257
+ }) => Promise<Response>;
258
+ /**
259
+ * Result of createGatewayProxy - a handler function with HTTP method exports.
260
+ */
261
+ type CreateGatewayProxyResult = CreateGatewayProxyFn & {
262
+ GET: CreateGatewayProxyFn;
263
+ POST: CreateGatewayProxyFn;
264
+ PUT: CreateGatewayProxyFn;
265
+ DELETE: CreateGatewayProxyFn;
266
+ PATCH: CreateGatewayProxyFn;
267
+ };
268
+
269
+ /**
270
+ * Creates a proxy handler for the Vercel AI Gateway.
271
+ *
272
+ * Uses AI SDK V4 Language Model.
273
+ * @see https://github.com/vercel/ai/blob/main/packages/gateway/src/gateway-language-model.ts
274
+ */
275
+ declare const createGatewayProxy: ({ baseUrl, headers, segmentsParam, extractPath, beforeRequest, afterResponse, onError, beforeRawRequest, afterRawResponse, }?: CreateGatewayProxyOptions) => CreateGatewayProxyResult;
276
+
277
+ /**
278
+ * Aggregator for collecting stream content.
279
+ * Tracks text deltas, reasoning, tool calls, and other content to build the final GatewayResponse.
280
+ *
281
+ * Uses AI SDK V4 protocol types.
282
+ * @see https://github.com/vercel/ai/blob/main/packages/provider/src/language-model/v4/language-model-v4-stream-part.ts
283
+ */
284
+ declare class StreamContentAggregator {
285
+ private content;
286
+ private warnings;
287
+ private textContent;
288
+ private reasoningContent;
289
+ private finishReason;
290
+ private usage;
291
+ private providerMetadata;
292
+ private response;
293
+ /**
294
+ * Process a stream part and update aggregated state.
295
+ * Uses LanguageModelV4StreamPart type from @ai-sdk/provider.
296
+ *
297
+ * @see https://github.com/vercel/ai/blob/main/packages/provider/src/language-model/v4/language-model-v4-stream-part.ts
298
+ */
299
+ process(part: LanguageModelV4StreamPart): void;
300
+ private recordResponseMetadata;
301
+ private openText;
302
+ private openReasoning;
303
+ /**
304
+ * Get the aggregated response. Before a `finish` part, `usage` is undefined and
305
+ * `finishReason` is `error` after an `error` part and `other` otherwise.
306
+ */
307
+ getResponse(): GatewayResponse;
308
+ }
309
+ /**
310
+ * Creates a transform stream that:
311
+ * 1. Passes through all SSE events immediately
312
+ * 2. Aggregates content using StreamContentAggregator
313
+ * 3. Calls afterResponse once: on the first finish event, or with partial content if the
314
+ * stream ends, fails or is cancelled first
315
+ * 4. Modifies every finish event based on that afterResponse result
316
+ *
317
+ * Pass the client request's `signal` when it is also the upstream fetch's signal: a read that
318
+ * fails because it aborted is then reported as a cancel rather than an upstream failure.
319
+ *
320
+ * Uses EventSourceParserStream from @ai-sdk/provider-utils for SSE parsing.
321
+ * EventSourceParserStream is re-exported from the eventsource-parser package.
322
+ * @see https://github.com/vercel/ai/blob/main/packages/provider-utils/src/parse-json-event-stream.ts
323
+ * @see https://github.com/rexxars/eventsource-parser
324
+ */
325
+ declare function createStreamTransformer(body: ReadableStream<Uint8Array>, requestBody: LanguageModelV4CallOptions, afterResponse: NonNullable<CreateGatewayProxyOptions["afterResponse"]>, signal?: AbortSignal): ReadableStream<Uint8Array>;
326
+
327
+ export { StreamContentAggregator, createGatewayProxy, createStreamTransformer, getGatewayAuthToken };
328
+ export type { CreateGatewayProxyFn, CreateGatewayProxyOptions, CreateGatewayProxyResult, GatewayAuthToken, GatewayError, GatewayResponse, GatewayResponseMetadata, IncompleteStream, ModelCallKind, RawRequestContext, RawResponseContext };
329
+ //# sourceMappingURL=index.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.cts","sources":["../src/auth.ts","../src/types.ts","../src/proxy.ts","../src/stream.ts"],"mappings":";;AAAA,KAAK,gBAAgB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB,mBAAmB,IAAI,OAAO,CAAC,gBAAgB;;ACRhE;AACA;AACA;AACA;AACA,KAAK,eAAe;AACpB,aAAa,sBAAsB;AACnC,kBAAkB,2BAA2B;AAC7C;AACA,WAAW,oBAAoB;AAC/B,cAAc,eAAe;AAC7B,uBAAuB,wBAAwB;AAC/C;AACA;AACA;AACA;AACA,eAAe,uBAAuB;AACtC;AACA;AACA;AACA;AACA,KAAK,uBAAuB;AAC5B;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK,gBAAgB;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK,YAAY;AACjB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK,aAAa;AAClB;AACA;AACA;AACA,KAAK,iBAAiB;AACtB,UAAU,aAAa;AACvB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK,kBAAkB,GAAG,iBAAiB;AAC3C;AACA;AACA;AACA;AACA;AACA,cAAc,QAAQ;AACtB;AACA;AACA;AACA;AACA,KAAK,yBAAyB;AAC9B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc,MAAM;AACpB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,4BAA4B,OAAO,cAAc,OAAO;AACxD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB,0BAA0B;AAC3C;AACA;AACA,UAAU,0BAA0B,GAAG,OAAO,CAAC,0BAA0B;AACzE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB,0BAA0B;AAC3C;AACA;AACA,kBAAkB,eAAe;AACjC;AACA,qBAAqB,gBAAgB;AACrC,UAAU,eAAe,GAAG,OAAO,CAAC,eAAe;AACnD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB,0BAA0B;AAC3C;AACA;AACA,eAAe,YAAY;AAC3B;AACA,UAAU,YAAY,GAAG,QAAQ,GAAG,OAAO,CAAC,YAAY,GAAG,QAAQ;AACnE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iCAAiC,iBAAiB;AAClD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iCAAiC,kBAAkB,KAAK,QAAQ,eAAe,OAAO,CAAC,QAAQ;AAC/F;AACA;AACA;AACA;AACA;AACA;AACA,KAAK,oBAAoB,aAAa,OAAO;AAC7C,YAAY,OAAO,CAAC,MAAM;AAC1B,MAAM,OAAO,CAAC,QAAQ;AACtB;AACA;AACA;AACA,KAAK,wBAAwB,GAAG,oBAAoB;AACpD,SAAS,oBAAoB;AAC7B,UAAU,oBAAoB;AAC9B,SAAS,oBAAoB;AAC7B,YAAY,oBAAoB;AAChC,WAAW,oBAAoB;AAC/B;;AC7PA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc,kBAAkB,kIAAkI,yBAAyB,KAAK,wBAAwB;;ACLxN;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc,uBAAuB;AACrC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,kBAAkB,yBAAyB;AAC3C;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mBAAmB,eAAe;AAClC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,iBAAiB,uBAAuB,OAAO,cAAc,CAAC,UAAU,gBAAgB,0BAA0B,iBAAiB,WAAW,CAAC,yBAAyB,6BAA6B,WAAW,GAAG,cAAc,CAAC,UAAU;;","names":[]}