@ai-sdk/gateway 4.0.129 → 4.0.131

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.
@@ -91,7 +91,12 @@ You can use the following optional settings to customize the AI Gateway provider
91
91
 
92
92
  Use a different URL prefix for API calls. The default prefix is `https://ai-gateway.vercel.sh/v4/ai`.
93
93
 
94
- - **apiKey** _string_
94
+ - **apiKey** _string | PromiseLike<string> | (() => string | PromiseLike<string>)_
95
+
96
+ Pass a callback to resolve the API key before each request. The callback can
97
+ return a string or a promise; token caching and renewal are handled by the caller.
98
+ A callback or promise selects API key authentication and must resolve to a
99
+ non-empty string; lookup failures do not switch authentication methods.
95
100
 
96
101
  API key or Vercel access token that is being sent using the `Authorization`
97
102
  header. It defaults to the `AI_GATEWAY_API_KEY` environment variable.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ai-sdk/gateway",
3
3
  "private": false,
4
- "version": "4.0.129",
4
+ "version": "4.0.131",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
7
7
  "sideEffects": false,
@@ -30,8 +30,8 @@
30
30
  }
31
31
  },
32
32
  "dependencies": {
33
- "@ai-sdk/provider": "4.0.34",
34
- "@ai-sdk/provider-utils": "5.0.70",
33
+ "@ai-sdk/provider": "4.0.35",
34
+ "@ai-sdk/provider-utils": "5.0.72",
35
35
  "@vercel/oidc": "3.2.0"
36
36
  },
37
37
  "devDependencies": {
@@ -31,6 +31,15 @@ export async function asGatewayError(
31
31
  error: unknown,
32
32
  authMethod?: 'api-key' | 'oidc',
33
33
  ) {
34
+ // Caller cancellation is not a gateway failure and must not become a retryable 500.
35
+ if (
36
+ (error instanceof Error ||
37
+ (typeof DOMException === 'function' && error instanceof DOMException)) &&
38
+ (error.name === 'AbortError' || error.name === 'TimeoutError')
39
+ ) {
40
+ throw error;
41
+ }
42
+
34
43
  if (GatewayError.isInstance(error)) {
35
44
  return error;
36
45
  }
@@ -259,6 +259,7 @@ export type GatewayModelId =
259
259
  | 'openai/o3-mini'
260
260
  | 'openai/o3-pro'
261
261
  | 'openai/o4-mini'
262
+ | 'particle/particle-1'
262
263
  | 'perplexity/sonar'
263
264
  | 'poolside/laguna-s-2.1'
264
265
  | 'poolside/laguna-s-2.1-free'
@@ -5,10 +5,12 @@ import {
5
5
  createJsonResponseHandler,
6
6
  getErrorMessage,
7
7
  loadOptionalSetting,
8
+ resolve,
8
9
  postJsonToApi,
9
10
  withoutTrailingSlash,
10
11
  withUserAgentSuffix,
11
12
  type FetchFunction,
13
+ type Resolvable,
12
14
  type WebSocketConstructor,
13
15
  } from '@ai-sdk/provider-utils';
14
16
  import { z } from './zod';
@@ -57,20 +59,21 @@ import type { GatewayVideoModelId } from './gateway-video-model-settings';
57
59
  import { gatewayTools } from './gateway-tools';
58
60
  import { getVercelOidcToken, getVercelRequestId } from './vercel-environment';
59
61
  import type { GatewayModelId } from './gateway-language-model-settings';
60
- import type {
61
- EmbeddingModelV4,
62
- Experimental_BatchV4 as BatchV4,
63
- ImageModelV4,
64
- RerankingModelV4,
65
- SpeechModelV4,
66
- TranscriptionModelV4,
67
- Experimental_VideoModelV4,
68
- Experimental_DecisionModelV4,
69
- Experimental_StreamingSpeechModelV4,
70
- Experimental_RealtimeFactoryV4 as RealtimeFactoryV4,
71
- Experimental_RealtimeFactoryV4GetTokenOptions as RealtimeFactoryV4GetTokenOptions,
72
- ProviderV4,
73
- LanguageModelV4,
62
+ import {
63
+ LoadAPIKeyError,
64
+ type EmbeddingModelV4,
65
+ type Experimental_BatchV4 as BatchV4,
66
+ type ImageModelV4,
67
+ type RerankingModelV4,
68
+ type SpeechModelV4,
69
+ type TranscriptionModelV4,
70
+ type Experimental_VideoModelV4,
71
+ type Experimental_DecisionModelV4,
72
+ type Experimental_StreamingSpeechModelV4,
73
+ type Experimental_RealtimeFactoryV4 as RealtimeFactoryV4,
74
+ type Experimental_RealtimeFactoryV4GetTokenOptions as RealtimeFactoryV4GetTokenOptions,
75
+ type ProviderV4,
76
+ type LanguageModelV4,
74
77
  } from '@ai-sdk/provider';
75
78
  import { VERSION } from './version';
76
79
 
@@ -306,8 +309,9 @@ export interface GatewayProviderSettings {
306
309
  /**
307
310
  * API key or Vercel access token that is being sent using the `Authorization`
308
311
  * header. It defaults to the `AI_GATEWAY_API_KEY` environment variable.
312
+ * A callback is resolved before each request and must return a non-empty key.
309
313
  */
310
- apiKey?: string;
314
+ apiKey?: Resolvable<string>;
311
315
 
312
316
  /**
313
317
  * Vercel team ID or slug to scope requests for access tokens that can access
@@ -392,9 +396,13 @@ export function createGateway(
392
396
  `ai-sdk-gateway/${VERSION}`,
393
397
  );
394
398
 
395
- const getHeaders = async () => {
399
+ const getHeaders = async (
400
+ onResolved?: (headers: Record<string, string | undefined>) => void,
401
+ ) => {
396
402
  try {
397
- return createAuthHeaders(await getGatewayAuthToken(options));
403
+ const headers = createAuthHeaders(await getGatewayAuthToken(options));
404
+ onResolved?.(headers);
405
+ return headers;
398
406
  } catch (error) {
399
407
  throw GatewayAuthenticationError.createContextualError({
400
408
  apiKeyProvided: false,
@@ -520,9 +528,13 @@ export function createGateway(
520
528
  if (!pendingMetadata || now - lastFetchTime > cacheRefreshMillis) {
521
529
  lastFetchTime = now;
522
530
 
531
+ let resolvedHeaders: Record<string, string | undefined> | undefined;
523
532
  pendingMetadata = new GatewayFetchMetadata({
524
533
  baseURL,
525
- headers: getHeaders,
534
+ headers: () =>
535
+ getHeaders(headers => {
536
+ resolvedHeaders = headers;
537
+ }),
526
538
  fetch: options.fetch,
527
539
  })
528
540
  .getAvailableModels()
@@ -533,7 +545,7 @@ export function createGateway(
533
545
  .catch(async (error: unknown) => {
534
546
  throw await asGatewayError(
535
547
  error,
536
- await parseAuthMethod(await getHeaders()),
548
+ await parseAuthMethod(resolvedHeaders ?? {}),
537
549
  );
538
550
  });
539
551
  }
@@ -542,46 +554,58 @@ export function createGateway(
542
554
  };
543
555
 
544
556
  const getCredits = async () => {
557
+ let resolvedHeaders: Record<string, string | undefined> | undefined;
545
558
  return new GatewayFetchMetadata({
546
559
  baseURL,
547
- headers: getHeaders,
560
+ headers: () =>
561
+ getHeaders(headers => {
562
+ resolvedHeaders = headers;
563
+ }),
548
564
  fetch: options.fetch,
549
565
  })
550
566
  .getCredits()
551
567
  .catch(async (error: unknown) => {
552
568
  throw await asGatewayError(
553
569
  error,
554
- await parseAuthMethod(await getHeaders()),
570
+ await parseAuthMethod(resolvedHeaders ?? {}),
555
571
  );
556
572
  });
557
573
  };
558
574
 
559
575
  const getSpendReport = async (params: GatewaySpendReportParams) => {
576
+ let resolvedHeaders: Record<string, string | undefined> | undefined;
560
577
  return new GatewaySpendReport({
561
578
  baseURL,
562
- headers: getHeaders,
579
+ headers: () =>
580
+ getHeaders(headers => {
581
+ resolvedHeaders = headers;
582
+ }),
563
583
  fetch: options.fetch,
564
584
  })
565
585
  .getSpendReport(params)
566
586
  .catch(async (error: unknown) => {
567
587
  throw await asGatewayError(
568
588
  error,
569
- await parseAuthMethod(await getHeaders()),
589
+ await parseAuthMethod(resolvedHeaders ?? {}),
570
590
  );
571
591
  });
572
592
  };
573
593
 
574
594
  const getGenerationInfo = async (params: GatewayGenerationInfoParams) => {
595
+ let resolvedHeaders: Record<string, string | undefined> | undefined;
575
596
  return new GatewayGenerationInfoFetcher({
576
597
  baseURL,
577
- headers: getHeaders,
598
+ headers: () =>
599
+ getHeaders(headers => {
600
+ resolvedHeaders = headers;
601
+ }),
578
602
  fetch: options.fetch,
579
603
  })
580
604
  .getGenerationInfo(params)
581
605
  .catch(async (error: unknown) => {
582
606
  throw await asGatewayError(
583
607
  error,
584
- await parseAuthMethod(await getHeaders()),
608
+ await parseAuthMethod(resolvedHeaders ?? {}),
585
609
  );
586
610
  });
587
611
  };
@@ -770,8 +794,22 @@ export const gateway = createGateway();
770
794
  export async function getGatewayAuthToken(
771
795
  options: GatewayProviderSettings,
772
796
  ): Promise<{ token: string; authMethod: 'api-key' | 'oidc' }> {
797
+ const resolvedApiKey =
798
+ typeof options.apiKey === 'string' || options.apiKey == null
799
+ ? options.apiKey
800
+ : await resolve(options.apiKey);
801
+ if (
802
+ options.apiKey != null &&
803
+ typeof options.apiKey !== 'string' &&
804
+ (typeof resolvedApiKey !== 'string' || !resolvedApiKey.trim())
805
+ ) {
806
+ throw new LoadAPIKeyError({
807
+ message: 'AI Gateway API key must be a non-empty string.',
808
+ });
809
+ }
810
+
773
811
  const apiKey = loadOptionalSetting({
774
- settingValue: options.apiKey,
812
+ settingValue: resolvedApiKey,
775
813
  environmentVariableName: 'AI_GATEWAY_API_KEY',
776
814
  });
777
815
 
@@ -10,7 +10,10 @@ export async function errorControllerWithGatewayError<PART>(
10
10
  error: unknown,
11
11
  authMethod: 'api-key' | 'oidc' | undefined,
12
12
  ): Promise<void> {
13
- controller.error(await asGatewayError(error, authMethod));
13
+ // Cancellation errors are rethrown by asGatewayError and must still reach the stream.
14
+ controller.error(
15
+ await asGatewayError(error, authMethod).catch(error => error),
16
+ );
14
17
  }
15
18
 
16
19
  /**