ai-gateway-proxy 0.5.0 → 0.7.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 CHANGED
@@ -132,19 +132,28 @@ Some gateway client calls do not go through the proxy:
132
132
 
133
133
  ## Authentication
134
134
 
135
- The proxy gets a credential on every request with `getGatewayAuthToken()`, in this order:
135
+ Without the `apiKey` option, the proxy gets a credential on every request with `getGatewayAuthToken()`, in this order:
136
136
 
137
137
  1. **API key:** the `AI_GATEWAY_API_KEY` environment variable
138
138
  2. **OIDC:** a Vercel OIDC token, available when deployed on Vercel
139
139
 
140
140
  `getGatewayAuthToken()` is exported. It resolves to `{ token, authMethod: "api-key" | "oidc" }`, or `null` when neither is available, in which case the proxy answers `500` with a `proxy_error` (see below). The proxy sends the token upstream as `authorization: Bearer <token>` with `ai-gateway-auth-method`, so the credential never reaches the client.
141
141
 
142
+ To take the key from somewhere else, such as a secret store or a key per tenant, pass `apiKey`: a string, or a function of the incoming `Request` that returns one or a promise of one. It runs on every request, before the body is read, so read the request's headers or URL, not its body. When `apiKey` is set it replaces the environment variable and OIDC: an empty or missing result, or a throw, answers `500` with a `proxy_error` without calling the gateway, and never falls back to the operator's own credential.
143
+
144
+ ```typescript
145
+ const proxy = createGatewayProxy({
146
+ apiKey: async (request) => keyForTenant(request.headers.get("x-tenant-id")),
147
+ });
148
+ ```
149
+
142
150
  ## Options
143
151
 
144
152
  `createGatewayProxy(options?)` returns a handler `(request: Request, context?) => Promise<Response>`. The same function is also exposed as its `GET`, `POST`, `PUT`, `DELETE` and `PATCH` properties.
145
153
 
146
154
  | Option | Default | Description |
147
155
  | --- | --- | --- |
156
+ | `apiKey` | `AI_GATEWAY_API_KEY`, then Vercel OIDC | `string \| (request) => string \| undefined \| Promise<...>`. The gateway API key, resolved on every request; see Authentication. |
148
157
  | `baseUrl` | `https://ai-gateway.vercel.sh/v4/ai` | Gateway URL that the extracted path and the incoming query string are appended to. |
149
158
  | `headers` | none | Extra headers for every upstream request. The proxy's own `authorization`, `ai-gateway-auth-method` and `ai-gateway-protocol-version` override entries with the same name in any casing, as does `content-type` on requests with a body. On requests with a body, the incoming request's `ai-language-model-*`, `ai-model-id` and `ai-*-model-specification-version` headers replace any entries describing the model call, and its `ai-o11y-*`, `idempotency-key` and `accept` headers override entries with the same name. No other incoming header is forwarded, so the client's `authorization` and cookies never reach the gateway. |
150
159
  | `segmentsParam` | `"segments"` | Name of the catch-all route param read from `context.params` when `extractPath` is not set, joined with `/`. Use `"path"` for `app/api/ai/[...path]/route.ts`. |
@@ -155,7 +164,7 @@ The proxy gets a credential on every request with `getGatewayAuthToken()`, in th
155
164
  | `beforeRawRequest` | none | `({ kind, path, modelId, request }) => unknown`. Runs on the calls that are not language model calls; see below. |
156
165
  | `afterRawResponse` | none | `({ kind, path, modelId, request, response }) => Response \| undefined`. Runs on the response to those calls; see below. |
157
166
 
158
- Each hook may also return a promise. `beforeRequest`, `afterResponse` and `onError` are typed for language model calls, and every hook runs only for requests with a body. Their `modelId` is the client's `ai-language-model-id` header, such as `openai/gpt-5`, since the call body does not name the model; it is `undefined` when the request has no such header. A `GET` or `HEAD` request, such as the provider's `GET /config` model list, and any other request whose body is missing or empty, such as a bodyless `DELETE`, is forwarded without a body and without running any hook, and the upstream status and body come back unchanged, except that a redirect is answered with a 502 as below. A request body that neither `beforeRequest` nor `beforeRawRequest` receives is sent upstream as the client sent it; once either hook runs, the body it returns or leaves in place is serialised again.
167
+ Each hook may also return a promise. `beforeRequest`, `afterResponse` and `onError` are typed for language model calls, and every hook runs only for requests with a body. Their `modelId` is the client's `ai-language-model-id` header, such as `openai/gpt-5`, since the call body does not name the model; it is `undefined` when the request has no such header. A `GET` or `HEAD` request, such as the provider's `GET /config` model list, and any other request whose body is missing or empty, such as a bodyless `DELETE`, is forwarded without a body and without running any hook, and the upstream status and body come back unchanged, except that a redirect is answered with a 502 and a gateway `authentication_error` is retyped as below. A request body that neither `beforeRequest` nor `beforeRawRequest` receives is sent upstream as the client sent it; once either hook runs, the body it returns or leaves in place is serialised again.
159
168
 
160
169
  Path matching fails closed: the language model hooks run on every request with a body except one whose path relative to `baseUrl` is exactly one of the paths `@ai-sdk/gateway` uses for calls that are not language model calls:
161
170
 
@@ -250,7 +259,8 @@ Apart from forwarding the gateway's own responses, the handler can answer with a
250
259
  | Status | Type | Message | When |
251
260
  | --- | --- | --- | --- |
252
261
  | 500 | `proxy_error` | `AI Gateway authentication not configured: set AI_GATEWAY_API_KEY or deploy on Vercel with OIDC enabled` | `getGatewayAuthToken()` returned `null`. The type is not `authentication_error`, which `@ai-sdk/gateway` replaces with advice about the client's own key. |
253
- | upstream, such as 401 | `proxy_error` | `AI Gateway rejected the proxy's credential: <message>` | The gateway answered a request with a body with an error typed `authentication_error`; see `onError` above. The other fields of the error are kept. |
262
+ | 500 | `proxy_error` | `AI Gateway authentication not configured: the apiKey option gave no key` | The `apiKey` option resolved to an empty or missing key, or threw; the reason is logged without the key. The gateway is not called. |
263
+ | upstream, such as 401 | `proxy_error` | `AI Gateway rejected the proxy's credential: <message>` | The gateway answered with an error typed `authentication_error`, for a call with a body or a bodyless one such as `GET /config`; see `onError` above. The other fields of the error are kept. |
254
264
  | 400 | `invalid_request_error` | `Invalid path` | The path is not a safe relative path under `baseUrl`. |
255
265
  | 400 | `invalid_request_error` | `Invalid JSON body` | A request other than `GET` or `HEAD` has a non-empty body that is not JSON. |
256
266
  | 400 | `invalid_request_error` | `Invalid request body: a language model call must be a JSON object` | The body is JSON but not an object, such as an array or `null`, on a path that runs `beforeRequest`, `afterResponse` or `onError`, and one of them is set. No hook runs and the gateway is not called. |
package/dist/index.cjs CHANGED
@@ -27,6 +27,26 @@ var providerUtils = require('@ai-sdk/provider-utils');
27
27
  return null;
28
28
  }
29
29
  }
30
+ /**
31
+ * Resolves the `apiKey` option for one request. Returns `null` when it gives no key or throws,
32
+ * after logging why, and never falls back to the environment or OIDC.
33
+ */ async function resolveApiKeyOption(apiKey, request) {
34
+ try {
35
+ const key = typeof apiKey === "function" ? await apiKey(request) : apiKey;
36
+ if (typeof key === "string" && key !== "") {
37
+ return {
38
+ token: key,
39
+ authMethod: "api-key"
40
+ };
41
+ }
42
+ console.error("AI Gateway auth: the apiKey option gave no key");
43
+ return null;
44
+ } catch (error) {
45
+ // Only the message: the error object could carry token material.
46
+ console.error("AI Gateway auth: the apiKey option failed:", error instanceof Error ? error.message : String(error));
47
+ return null;
48
+ }
49
+ }
30
50
 
31
51
  function isRecord(value) {
32
52
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -515,6 +535,7 @@ const NULL_BODY_STATUS = new Set([
515
535
  const ANSWERED_CALL_FAILURE_STATUS = 424;
516
536
  // Successful bodies that are a sequence of values rather than one JSON value: event streams and JSON Lines.
517
537
  const STREAM_CONTENT_TYPE = /^\s*(?:text\/event-stream|application\/(?:x-ndjson|jsonl|jsonlines|x-jsonlines))\s*(?:;|$)/i;
538
+ const JSON_CONTENT_TYPE = /^\s*application\/(?:[\w.+-]+\+)?json\s*(?:;|$)/i;
518
539
  /**
519
540
  * Extracts the path from the request using either a custom extractor or route params.
520
541
  */ async function extractPathFromRequest(request, context, options) {
@@ -652,11 +673,35 @@ function proxyFailureResponse(request, error) {
652
673
  if (isRedirect(res)) {
653
674
  return redirectRefusedResponse(res);
654
675
  }
655
- return passThroughResponse(res);
676
+ return await bodylessUpstreamResponse(res);
656
677
  } catch (error) {
657
678
  return proxyFailureResponse(request, error);
658
679
  }
659
680
  }
681
+ /**
682
+ * Passes a bodyless request's response through, except that a JSON error typed
683
+ * `authentication_error` is retyped as for a call with a body. Only JSON error bodies are read.
684
+ */ async function bodylessUpstreamResponse(res) {
685
+ const contentType = res.headers.get("content-type") ?? "application/json";
686
+ if (res.ok || !res.body || !JSON_CONTENT_TYPE.test(contentType)) {
687
+ return passThroughResponse(res);
688
+ }
689
+ const text = await res.text();
690
+ const parsed = parseJsonOrUndefined(text);
691
+ const body = isGatewayError(parsed) && parsed.error.type === "authentication_error" ? JSON.stringify(retypeProxyCredentialError(parsed)) : text;
692
+ return new Response(body, {
693
+ status: res.status,
694
+ statusText: res.statusText,
695
+ headers: responseHeaders(res, contentType)
696
+ });
697
+ }
698
+ function parseJsonOrUndefined(text) {
699
+ try {
700
+ return JSON.parse(text);
701
+ } catch {
702
+ return undefined;
703
+ }
704
+ }
660
705
  function isGatewayError(value) {
661
706
  if (typeof value !== "object" || value === null) {
662
707
  return false;
@@ -928,13 +973,12 @@ async function runAfterRawResponse(afterRawResponse, context) {
928
973
  *
929
974
  * Uses AI SDK V4 Language Model.
930
975
  * @see https://github.com/vercel/ai/blob/main/packages/gateway/src/gateway-language-model.ts
931
- */ const createGatewayProxy = ({ baseUrl = DEFAULT_BASE_URL, headers, segmentsParam = "segments", extractPath, beforeRequest, afterResponse, onError, beforeRawRequest, afterRawResponse } = {})=>{
976
+ */ const createGatewayProxy = ({ apiKey, baseUrl = DEFAULT_BASE_URL, headers, segmentsParam = "segments", extractPath, beforeRequest, afterResponse, onError, beforeRawRequest, afterRawResponse } = {})=>{
932
977
  const handler = async (request, context)=>{
933
- // Authenticate with the gateway
934
- const auth = await getGatewayAuthToken();
978
+ const auth = apiKey === undefined ? await getGatewayAuthToken() : await resolveApiKeyOption(apiKey, request);
935
979
  if (!auth) {
936
980
  // Not authentication_error: @ai-sdk/gateway replaces that message with advice about the client's own key.
937
- return proxyErrorResponse(500, "proxy_error", "AI Gateway authentication not configured: set AI_GATEWAY_API_KEY or deploy on Vercel with OIDC enabled");
981
+ return proxyErrorResponse(500, "proxy_error", apiKey === undefined ? "AI Gateway authentication not configured: set AI_GATEWAY_API_KEY or deploy on Vercel with OIDC enabled" : "AI Gateway authentication not configured: the apiKey option gave no key");
938
982
  }
939
983
  try {
940
984
  // Build upstream URL
package/dist/index.d.cts CHANGED
@@ -10,6 +10,10 @@ type GatewayAuthToken = {
10
10
  * Returns `null` when neither is available, after logging why OIDC failed, and the proxy then answers 500.
11
11
  */
12
12
  declare function getGatewayAuthToken(): Promise<GatewayAuthToken | null>;
13
+ /**
14
+ * A gateway API key, or a function of the incoming request that returns one.
15
+ */
16
+ type GatewayApiKey = string | ((request: Request) => string | null | undefined | Promise<string | null | undefined>);
13
17
 
14
18
  /**
15
19
  * The response body from the AI Gateway for non-streaming requests.
@@ -92,6 +96,13 @@ type RawResponseContext = RawRequestContext & {
92
96
  * Options for creating a gateway proxy.
93
97
  */
94
98
  type CreateGatewayProxyOptions = {
99
+ /**
100
+ * The gateway API key, or a function of the incoming request that returns it, resolved on every request.
101
+ * When set, it replaces `AI_GATEWAY_API_KEY` and Vercel OIDC: an empty result or a throw answers
102
+ * 500 `proxy_error` without calling the gateway. Read the request's headers or URL, not its body.
103
+ * @default the `AI_GATEWAY_API_KEY` environment variable, then Vercel OIDC
104
+ */
105
+ apiKey?: GatewayApiKey;
95
106
  /**
96
107
  * Base URL for the AI Gateway. A trailing slash is accepted.
97
108
  * Request paths are resolved under it and cannot leave it; one that would returns 400 Invalid path.
@@ -275,7 +286,7 @@ type CreateGatewayProxyResult = CreateGatewayProxyFn & {
275
286
  * Uses AI SDK V4 Language Model.
276
287
  * @see https://github.com/vercel/ai/blob/main/packages/gateway/src/gateway-language-model.ts
277
288
  */
278
- declare const createGatewayProxy: ({ baseUrl, headers, segmentsParam, extractPath, beforeRequest, afterResponse, onError, beforeRawRequest, afterRawResponse, }?: CreateGatewayProxyOptions) => CreateGatewayProxyResult;
289
+ declare const createGatewayProxy: ({ apiKey, baseUrl, headers, segmentsParam, extractPath, beforeRequest, afterResponse, onError, beforeRawRequest, afterRawResponse, }?: CreateGatewayProxyOptions) => CreateGatewayProxyResult;
279
290
 
280
291
  /**
281
292
  * Aggregator for collecting stream content.
@@ -328,5 +339,5 @@ declare class StreamContentAggregator {
328
339
  declare function createStreamTransformer(body: ReadableStream<Uint8Array>, requestBody: LanguageModelV4CallOptions, afterResponse: NonNullable<CreateGatewayProxyOptions["afterResponse"]>, signal?: AbortSignal): ReadableStream<Uint8Array>;
329
340
 
330
341
  export { StreamContentAggregator, createGatewayProxy, createStreamTransformer, getGatewayAuthToken };
331
- export type { CreateGatewayProxyFn, CreateGatewayProxyOptions, CreateGatewayProxyResult, GatewayAuthToken, GatewayError, GatewayResponse, GatewayResponseMetadata, IncompleteStream, ModelCallKind, RawRequestContext, RawResponseContext };
342
+ export type { CreateGatewayProxyFn, CreateGatewayProxyOptions, CreateGatewayProxyResult, GatewayApiKey, GatewayAuthToken, GatewayError, GatewayResponse, GatewayResponseMetadata, IncompleteStream, ModelCallKind, RawRequestContext, RawResponseContext };
332
343
  //# sourceMappingURL=index.d.cts.map
@@ -1 +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;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;;AChQA;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":[]}
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;AAChE;AACA;AACA;AACA,KAAK,aAAa,uBAAuB,OAAO,iCAAiC,OAAO;;ACXxF;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,aAAa,aAAa;AAC1B;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;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;;ACxQA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc,kBAAkB,0IAA0I,yBAAyB,KAAK,wBAAwB;;ACLhO;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":[]}
package/dist/index.d.ts CHANGED
@@ -10,6 +10,10 @@ type GatewayAuthToken = {
10
10
  * Returns `null` when neither is available, after logging why OIDC failed, and the proxy then answers 500.
11
11
  */
12
12
  declare function getGatewayAuthToken(): Promise<GatewayAuthToken | null>;
13
+ /**
14
+ * A gateway API key, or a function of the incoming request that returns one.
15
+ */
16
+ type GatewayApiKey = string | ((request: Request) => string | null | undefined | Promise<string | null | undefined>);
13
17
 
14
18
  /**
15
19
  * The response body from the AI Gateway for non-streaming requests.
@@ -92,6 +96,13 @@ type RawResponseContext = RawRequestContext & {
92
96
  * Options for creating a gateway proxy.
93
97
  */
94
98
  type CreateGatewayProxyOptions = {
99
+ /**
100
+ * The gateway API key, or a function of the incoming request that returns it, resolved on every request.
101
+ * When set, it replaces `AI_GATEWAY_API_KEY` and Vercel OIDC: an empty result or a throw answers
102
+ * 500 `proxy_error` without calling the gateway. Read the request's headers or URL, not its body.
103
+ * @default the `AI_GATEWAY_API_KEY` environment variable, then Vercel OIDC
104
+ */
105
+ apiKey?: GatewayApiKey;
95
106
  /**
96
107
  * Base URL for the AI Gateway. A trailing slash is accepted.
97
108
  * Request paths are resolved under it and cannot leave it; one that would returns 400 Invalid path.
@@ -275,7 +286,7 @@ type CreateGatewayProxyResult = CreateGatewayProxyFn & {
275
286
  * Uses AI SDK V4 Language Model.
276
287
  * @see https://github.com/vercel/ai/blob/main/packages/gateway/src/gateway-language-model.ts
277
288
  */
278
- declare const createGatewayProxy: ({ baseUrl, headers, segmentsParam, extractPath, beforeRequest, afterResponse, onError, beforeRawRequest, afterRawResponse, }?: CreateGatewayProxyOptions) => CreateGatewayProxyResult;
289
+ declare const createGatewayProxy: ({ apiKey, baseUrl, headers, segmentsParam, extractPath, beforeRequest, afterResponse, onError, beforeRawRequest, afterRawResponse, }?: CreateGatewayProxyOptions) => CreateGatewayProxyResult;
279
290
 
280
291
  /**
281
292
  * Aggregator for collecting stream content.
@@ -328,5 +339,5 @@ declare class StreamContentAggregator {
328
339
  declare function createStreamTransformer(body: ReadableStream<Uint8Array>, requestBody: LanguageModelV4CallOptions, afterResponse: NonNullable<CreateGatewayProxyOptions["afterResponse"]>, signal?: AbortSignal): ReadableStream<Uint8Array>;
329
340
 
330
341
  export { StreamContentAggregator, createGatewayProxy, createStreamTransformer, getGatewayAuthToken };
331
- export type { CreateGatewayProxyFn, CreateGatewayProxyOptions, CreateGatewayProxyResult, GatewayAuthToken, GatewayError, GatewayResponse, GatewayResponseMetadata, IncompleteStream, ModelCallKind, RawRequestContext, RawResponseContext };
342
+ export type { CreateGatewayProxyFn, CreateGatewayProxyOptions, CreateGatewayProxyResult, GatewayApiKey, GatewayAuthToken, GatewayError, GatewayResponse, GatewayResponseMetadata, IncompleteStream, ModelCallKind, RawRequestContext, RawResponseContext };
332
343
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","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;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;;AChQA;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":[]}
1
+ {"version":3,"file":"index.d.ts","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;AAChE;AACA;AACA;AACA,KAAK,aAAa,uBAAuB,OAAO,iCAAiC,OAAO;;ACXxF;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,aAAa,aAAa;AAC1B;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;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;;ACxQA;AACA;AACA;AACA;AACA;AACA;AACA,cAAc,kBAAkB,0IAA0I,yBAAyB,KAAK,wBAAwB;;ACLhO;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":[]}
package/dist/index.mjs CHANGED
@@ -25,6 +25,26 @@ import { EventSourceParserStream } from '@ai-sdk/provider-utils';
25
25
  return null;
26
26
  }
27
27
  }
28
+ /**
29
+ * Resolves the `apiKey` option for one request. Returns `null` when it gives no key or throws,
30
+ * after logging why, and never falls back to the environment or OIDC.
31
+ */ async function resolveApiKeyOption(apiKey, request) {
32
+ try {
33
+ const key = typeof apiKey === "function" ? await apiKey(request) : apiKey;
34
+ if (typeof key === "string" && key !== "") {
35
+ return {
36
+ token: key,
37
+ authMethod: "api-key"
38
+ };
39
+ }
40
+ console.error("AI Gateway auth: the apiKey option gave no key");
41
+ return null;
42
+ } catch (error) {
43
+ // Only the message: the error object could carry token material.
44
+ console.error("AI Gateway auth: the apiKey option failed:", error instanceof Error ? error.message : String(error));
45
+ return null;
46
+ }
47
+ }
28
48
 
29
49
  function isRecord(value) {
30
50
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -513,6 +533,7 @@ const NULL_BODY_STATUS = new Set([
513
533
  const ANSWERED_CALL_FAILURE_STATUS = 424;
514
534
  // Successful bodies that are a sequence of values rather than one JSON value: event streams and JSON Lines.
515
535
  const STREAM_CONTENT_TYPE = /^\s*(?:text\/event-stream|application\/(?:x-ndjson|jsonl|jsonlines|x-jsonlines))\s*(?:;|$)/i;
536
+ const JSON_CONTENT_TYPE = /^\s*application\/(?:[\w.+-]+\+)?json\s*(?:;|$)/i;
516
537
  /**
517
538
  * Extracts the path from the request using either a custom extractor or route params.
518
539
  */ async function extractPathFromRequest(request, context, options) {
@@ -650,11 +671,35 @@ function proxyFailureResponse(request, error) {
650
671
  if (isRedirect(res)) {
651
672
  return redirectRefusedResponse(res);
652
673
  }
653
- return passThroughResponse(res);
674
+ return await bodylessUpstreamResponse(res);
654
675
  } catch (error) {
655
676
  return proxyFailureResponse(request, error);
656
677
  }
657
678
  }
679
+ /**
680
+ * Passes a bodyless request's response through, except that a JSON error typed
681
+ * `authentication_error` is retyped as for a call with a body. Only JSON error bodies are read.
682
+ */ async function bodylessUpstreamResponse(res) {
683
+ const contentType = res.headers.get("content-type") ?? "application/json";
684
+ if (res.ok || !res.body || !JSON_CONTENT_TYPE.test(contentType)) {
685
+ return passThroughResponse(res);
686
+ }
687
+ const text = await res.text();
688
+ const parsed = parseJsonOrUndefined(text);
689
+ const body = isGatewayError(parsed) && parsed.error.type === "authentication_error" ? JSON.stringify(retypeProxyCredentialError(parsed)) : text;
690
+ return new Response(body, {
691
+ status: res.status,
692
+ statusText: res.statusText,
693
+ headers: responseHeaders(res, contentType)
694
+ });
695
+ }
696
+ function parseJsonOrUndefined(text) {
697
+ try {
698
+ return JSON.parse(text);
699
+ } catch {
700
+ return undefined;
701
+ }
702
+ }
658
703
  function isGatewayError(value) {
659
704
  if (typeof value !== "object" || value === null) {
660
705
  return false;
@@ -926,13 +971,12 @@ async function runAfterRawResponse(afterRawResponse, context) {
926
971
  *
927
972
  * Uses AI SDK V4 Language Model.
928
973
  * @see https://github.com/vercel/ai/blob/main/packages/gateway/src/gateway-language-model.ts
929
- */ const createGatewayProxy = ({ baseUrl = DEFAULT_BASE_URL, headers, segmentsParam = "segments", extractPath, beforeRequest, afterResponse, onError, beforeRawRequest, afterRawResponse } = {})=>{
974
+ */ const createGatewayProxy = ({ apiKey, baseUrl = DEFAULT_BASE_URL, headers, segmentsParam = "segments", extractPath, beforeRequest, afterResponse, onError, beforeRawRequest, afterRawResponse } = {})=>{
930
975
  const handler = async (request, context)=>{
931
- // Authenticate with the gateway
932
- const auth = await getGatewayAuthToken();
976
+ const auth = apiKey === undefined ? await getGatewayAuthToken() : await resolveApiKeyOption(apiKey, request);
933
977
  if (!auth) {
934
978
  // Not authentication_error: @ai-sdk/gateway replaces that message with advice about the client's own key.
935
- return proxyErrorResponse(500, "proxy_error", "AI Gateway authentication not configured: set AI_GATEWAY_API_KEY or deploy on Vercel with OIDC enabled");
979
+ return proxyErrorResponse(500, "proxy_error", apiKey === undefined ? "AI Gateway authentication not configured: set AI_GATEWAY_API_KEY or deploy on Vercel with OIDC enabled" : "AI Gateway authentication not configured: the apiKey option gave no key");
936
980
  }
937
981
  try {
938
982
  // Build upstream URL
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-gateway-proxy",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "license": "MIT",