@ai-sdk/gateway 0.0.0-70e0935a-20260114150030 → 0.0.0-98261322-20260122142521

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.
Files changed (47) hide show
  1. package/CHANGELOG.md +49 -4
  2. package/dist/index.d.mts +20 -10
  3. package/dist/index.d.ts +20 -10
  4. package/dist/index.js +62 -25
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +62 -25
  7. package/dist/index.mjs.map +1 -1
  8. package/docs/00-ai-gateway.mdx +625 -0
  9. package/package.json +12 -5
  10. package/src/errors/as-gateway-error.ts +33 -0
  11. package/src/errors/create-gateway-error.test.ts +590 -0
  12. package/src/errors/create-gateway-error.ts +132 -0
  13. package/src/errors/extract-api-call-response.test.ts +270 -0
  14. package/src/errors/extract-api-call-response.ts +15 -0
  15. package/src/errors/gateway-authentication-error.ts +84 -0
  16. package/src/errors/gateway-error-types.test.ts +278 -0
  17. package/src/errors/gateway-error.ts +47 -0
  18. package/src/errors/gateway-internal-server-error.ts +33 -0
  19. package/src/errors/gateway-invalid-request-error.ts +33 -0
  20. package/src/errors/gateway-model-not-found-error.ts +47 -0
  21. package/src/errors/gateway-rate-limit-error.ts +33 -0
  22. package/src/errors/gateway-response-error.ts +42 -0
  23. package/src/errors/index.ts +16 -0
  24. package/src/errors/parse-auth-method.test.ts +136 -0
  25. package/src/errors/parse-auth-method.ts +23 -0
  26. package/src/gateway-config.ts +7 -0
  27. package/src/gateway-embedding-model-settings.ts +22 -0
  28. package/src/gateway-embedding-model.test.ts +213 -0
  29. package/src/gateway-embedding-model.ts +109 -0
  30. package/src/gateway-fetch-metadata.test.ts +774 -0
  31. package/src/gateway-fetch-metadata.ts +127 -0
  32. package/src/gateway-image-model-settings.ts +12 -0
  33. package/src/gateway-image-model.test.ts +823 -0
  34. package/src/gateway-image-model.ts +145 -0
  35. package/src/gateway-language-model-settings.ts +159 -0
  36. package/src/gateway-language-model.test.ts +1485 -0
  37. package/src/gateway-language-model.ts +212 -0
  38. package/src/gateway-model-entry.ts +58 -0
  39. package/src/gateway-provider-options.ts +66 -0
  40. package/src/gateway-provider.test.ts +1210 -0
  41. package/src/gateway-provider.ts +284 -0
  42. package/src/gateway-tools.ts +15 -0
  43. package/src/index.ts +27 -0
  44. package/src/tool/perplexity-search.ts +294 -0
  45. package/src/vercel-environment.test.ts +65 -0
  46. package/src/vercel-environment.ts +6 -0
  47. package/src/version.ts +6 -0
@@ -0,0 +1,132 @@
1
+ import { z } from 'zod/v4';
2
+ import type { GatewayError } from './gateway-error';
3
+ import { GatewayAuthenticationError } from './gateway-authentication-error';
4
+ import { GatewayInvalidRequestError } from './gateway-invalid-request-error';
5
+ import { GatewayRateLimitError } from './gateway-rate-limit-error';
6
+ import {
7
+ GatewayModelNotFoundError,
8
+ modelNotFoundParamSchema,
9
+ } from './gateway-model-not-found-error';
10
+ import { GatewayInternalServerError } from './gateway-internal-server-error';
11
+ import { GatewayResponseError } from './gateway-response-error';
12
+ import {
13
+ InferSchema,
14
+ lazySchema,
15
+ safeValidateTypes,
16
+ validateTypes,
17
+ zodSchema,
18
+ } from '@ai-sdk/provider-utils';
19
+
20
+ export async function createGatewayErrorFromResponse({
21
+ response,
22
+ statusCode,
23
+ defaultMessage = 'Gateway request failed',
24
+ cause,
25
+ authMethod,
26
+ }: {
27
+ response: unknown;
28
+ statusCode: number;
29
+ defaultMessage?: string;
30
+ cause?: unknown;
31
+ authMethod?: 'api-key' | 'oidc';
32
+ }): Promise<GatewayError> {
33
+ const parseResult = await safeValidateTypes({
34
+ value: response,
35
+ schema: gatewayErrorResponseSchema,
36
+ });
37
+
38
+ if (!parseResult.success) {
39
+ // Try to extract generationId even if validation failed
40
+ const rawGenerationId =
41
+ typeof response === 'object' &&
42
+ response !== null &&
43
+ 'generationId' in response
44
+ ? (response as { generationId?: string }).generationId
45
+ : undefined;
46
+
47
+ return new GatewayResponseError({
48
+ message: `Invalid error response format: ${defaultMessage}`,
49
+ statusCode,
50
+ response,
51
+ validationError: parseResult.error,
52
+ cause,
53
+ generationId: rawGenerationId,
54
+ });
55
+ }
56
+
57
+ const validatedResponse: GatewayErrorResponse = parseResult.value;
58
+ const errorType = validatedResponse.error.type;
59
+ const message = validatedResponse.error.message;
60
+ const generationId = validatedResponse.generationId ?? undefined;
61
+
62
+ switch (errorType) {
63
+ case 'authentication_error':
64
+ return GatewayAuthenticationError.createContextualError({
65
+ apiKeyProvided: authMethod === 'api-key',
66
+ oidcTokenProvided: authMethod === 'oidc',
67
+ statusCode,
68
+ cause,
69
+ generationId,
70
+ });
71
+ case 'invalid_request_error':
72
+ return new GatewayInvalidRequestError({
73
+ message,
74
+ statusCode,
75
+ cause,
76
+ generationId,
77
+ });
78
+ case 'rate_limit_exceeded':
79
+ return new GatewayRateLimitError({
80
+ message,
81
+ statusCode,
82
+ cause,
83
+ generationId,
84
+ });
85
+ case 'model_not_found': {
86
+ const modelResult = await safeValidateTypes({
87
+ value: validatedResponse.error.param,
88
+ schema: modelNotFoundParamSchema,
89
+ });
90
+
91
+ return new GatewayModelNotFoundError({
92
+ message,
93
+ statusCode,
94
+ modelId: modelResult.success ? modelResult.value.modelId : undefined,
95
+ cause,
96
+ generationId,
97
+ });
98
+ }
99
+ case 'internal_server_error':
100
+ return new GatewayInternalServerError({
101
+ message,
102
+ statusCode,
103
+ cause,
104
+ generationId,
105
+ });
106
+ default:
107
+ return new GatewayInternalServerError({
108
+ message,
109
+ statusCode,
110
+ cause,
111
+ generationId,
112
+ });
113
+ }
114
+ }
115
+
116
+ const gatewayErrorResponseSchema = lazySchema(() =>
117
+ zodSchema(
118
+ z.object({
119
+ error: z.object({
120
+ message: z.string(),
121
+ type: z.string().nullish(),
122
+ param: z.unknown().nullish(),
123
+ code: z.union([z.string(), z.number()]).nullish(),
124
+ }),
125
+ generationId: z.string().nullish(),
126
+ }),
127
+ ),
128
+ );
129
+
130
+ export type GatewayErrorResponse = InferSchema<
131
+ typeof gatewayErrorResponseSchema
132
+ >;
@@ -0,0 +1,270 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { APICallError } from '@ai-sdk/provider';
3
+ import { extractApiCallResponse } from './extract-api-call-response';
4
+
5
+ describe('extractResponseFromAPICallError', () => {
6
+ describe('when error.data is available', () => {
7
+ it('should return error.data when successfully parsed by AI SDK', () => {
8
+ const parsedData = {
9
+ error: { message: 'Parsed error', type: 'authentication_error' },
10
+ };
11
+ const apiCallError = new APICallError({
12
+ message: 'Request failed',
13
+ statusCode: 401,
14
+ data: parsedData,
15
+ responseHeaders: {},
16
+ responseBody: JSON.stringify({ different: 'data' }),
17
+ url: 'http://test.url',
18
+ requestBodyValues: {},
19
+ });
20
+
21
+ const result = extractApiCallResponse(apiCallError);
22
+
23
+ expect(result).toBe(parsedData); // Should prefer parsed data over responseBody
24
+ });
25
+
26
+ it('should return error.data even when it is null', () => {
27
+ const apiCallError = new APICallError({
28
+ message: 'Request failed',
29
+ statusCode: 500,
30
+ data: null,
31
+ responseHeaders: {},
32
+ responseBody: '{"fallback": "data"}',
33
+ url: 'http://test.url',
34
+ requestBodyValues: {},
35
+ });
36
+
37
+ const result = extractApiCallResponse(apiCallError);
38
+
39
+ expect(result).toBeNull(); // Should return null, not fallback to responseBody
40
+ });
41
+
42
+ it('should return error.data even when it is an empty object', () => {
43
+ const emptyData = {};
44
+ const apiCallError = new APICallError({
45
+ message: 'Request failed',
46
+ statusCode: 400,
47
+ data: emptyData,
48
+ responseHeaders: {},
49
+ responseBody: '{"fallback": "data"}',
50
+ url: 'http://test.url',
51
+ requestBodyValues: {},
52
+ });
53
+
54
+ const result = extractApiCallResponse(apiCallError);
55
+
56
+ expect(result).toBe(emptyData); // Should return empty object, not fallback
57
+ });
58
+ });
59
+
60
+ describe('when error.data is undefined', () => {
61
+ it('should parse and return responseBody as JSON when valid', () => {
62
+ const responseData = {
63
+ ferror: { message: 'Malformed error', type: 'model_not_found' },
64
+ };
65
+ const apiCallError = new APICallError({
66
+ message: 'Request failed',
67
+ statusCode: 404,
68
+ data: undefined,
69
+ responseHeaders: {},
70
+ responseBody: JSON.stringify(responseData),
71
+ url: 'http://test.url',
72
+ requestBodyValues: {},
73
+ });
74
+
75
+ const result = extractApiCallResponse(apiCallError);
76
+
77
+ expect(result).toEqual(responseData);
78
+ });
79
+
80
+ it('should return raw responseBody when JSON parsing fails', () => {
81
+ const invalidJson = 'This is not valid JSON';
82
+ const apiCallError = new APICallError({
83
+ message: 'Request failed',
84
+ statusCode: 500,
85
+ data: undefined,
86
+ responseHeaders: {},
87
+ responseBody: invalidJson,
88
+ url: 'http://test.url',
89
+ requestBodyValues: {},
90
+ });
91
+
92
+ const result = extractApiCallResponse(apiCallError);
93
+
94
+ expect(result).toBe(invalidJson);
95
+ });
96
+
97
+ it('should handle HTML error responses', () => {
98
+ const htmlResponse =
99
+ '<html><body><h1>500 Internal Server Error</h1></body></html>';
100
+ const apiCallError = new APICallError({
101
+ message: 'Request failed',
102
+ statusCode: 500,
103
+ data: undefined,
104
+ responseHeaders: {},
105
+ responseBody: htmlResponse,
106
+ url: 'http://test.url',
107
+ requestBodyValues: {},
108
+ });
109
+
110
+ const result = extractApiCallResponse(apiCallError);
111
+
112
+ expect(result).toBe(htmlResponse);
113
+ });
114
+
115
+ it('should handle empty string responseBody', () => {
116
+ const apiCallError = new APICallError({
117
+ message: 'Request failed',
118
+ statusCode: 502,
119
+ data: undefined,
120
+ responseHeaders: {},
121
+ responseBody: '',
122
+ url: 'http://test.url',
123
+ requestBodyValues: {},
124
+ });
125
+
126
+ const result = extractApiCallResponse(apiCallError);
127
+
128
+ expect(result).toBe('');
129
+ });
130
+
131
+ it('should handle malformed JSON gracefully', () => {
132
+ const malformedJson = '{"incomplete": json';
133
+ const apiCallError = new APICallError({
134
+ message: 'Request failed',
135
+ statusCode: 500,
136
+ data: undefined,
137
+ responseHeaders: {},
138
+ responseBody: malformedJson,
139
+ url: 'http://test.url',
140
+ requestBodyValues: {},
141
+ });
142
+
143
+ const result = extractApiCallResponse(apiCallError);
144
+
145
+ expect(result).toBe(malformedJson); // Should return raw string, not throw
146
+ });
147
+
148
+ it('should parse complex nested JSON structures', () => {
149
+ const complexData = {
150
+ error: {
151
+ message: 'Complex error',
152
+ type: 'validation_error',
153
+ details: {
154
+ field: 'prompt',
155
+ issues: [
156
+ { code: 'too_long', message: 'Prompt exceeds maximum length' },
157
+ {
158
+ code: 'invalid_format',
159
+ message: 'Contains invalid characters',
160
+ },
161
+ ],
162
+ },
163
+ },
164
+ metadata: {
165
+ requestId: '12345',
166
+ timestamp: '2024-01-01T00:00:00Z',
167
+ },
168
+ };
169
+
170
+ const apiCallError = new APICallError({
171
+ message: 'Request failed',
172
+ statusCode: 400,
173
+ data: undefined,
174
+ responseHeaders: {},
175
+ responseBody: JSON.stringify(complexData),
176
+ url: 'http://test.url',
177
+ requestBodyValues: {},
178
+ });
179
+
180
+ const result = extractApiCallResponse(apiCallError);
181
+
182
+ expect(result).toEqual(complexData);
183
+ });
184
+ });
185
+
186
+ describe('when responseBody is not available', () => {
187
+ it('should return empty object when both data and responseBody are undefined', () => {
188
+ const apiCallError = new APICallError({
189
+ message: 'Request failed',
190
+ statusCode: 500,
191
+ data: undefined,
192
+ responseHeaders: {},
193
+ responseBody: undefined as any, // Simulating missing responseBody
194
+ url: 'http://test.url',
195
+ requestBodyValues: {},
196
+ });
197
+
198
+ const result = extractApiCallResponse(apiCallError);
199
+
200
+ expect(result).toEqual({});
201
+ });
202
+
203
+ it('should return empty object when responseBody is null', () => {
204
+ const apiCallError = new APICallError({
205
+ message: 'Request failed',
206
+ statusCode: 500,
207
+ data: undefined,
208
+ responseHeaders: {},
209
+ responseBody: null as any,
210
+ url: 'http://test.url',
211
+ requestBodyValues: {},
212
+ });
213
+
214
+ const result = extractApiCallResponse(apiCallError);
215
+
216
+ expect(result).toEqual({});
217
+ });
218
+ });
219
+
220
+ describe('edge cases', () => {
221
+ it('should handle numeric responseBody', () => {
222
+ const apiCallError = new APICallError({
223
+ message: 'Request failed',
224
+ statusCode: 500,
225
+ data: undefined,
226
+ responseHeaders: {},
227
+ responseBody: '404',
228
+ url: 'http://test.url',
229
+ requestBodyValues: {},
230
+ });
231
+
232
+ const result = extractApiCallResponse(apiCallError);
233
+
234
+ expect(result).toBe(404); // Should parse as number
235
+ });
236
+
237
+ it('should handle boolean responseBody', () => {
238
+ const apiCallError = new APICallError({
239
+ message: 'Request failed',
240
+ statusCode: 500,
241
+ data: undefined,
242
+ responseHeaders: {},
243
+ responseBody: 'true',
244
+ url: 'http://test.url',
245
+ requestBodyValues: {},
246
+ });
247
+
248
+ const result = extractApiCallResponse(apiCallError);
249
+
250
+ expect(result).toBe(true); // Should parse as boolean
251
+ });
252
+
253
+ it('should handle array responseBody', () => {
254
+ const arrayData = ['error1', 'error2', 'error3'];
255
+ const apiCallError = new APICallError({
256
+ message: 'Request failed',
257
+ statusCode: 400,
258
+ data: undefined,
259
+ responseHeaders: {},
260
+ responseBody: JSON.stringify(arrayData),
261
+ url: 'http://test.url',
262
+ requestBodyValues: {},
263
+ });
264
+
265
+ const result = extractApiCallResponse(apiCallError);
266
+
267
+ expect(result).toEqual(arrayData);
268
+ });
269
+ });
270
+ });
@@ -0,0 +1,15 @@
1
+ import type { APICallError } from '@ai-sdk/provider';
2
+
3
+ export function extractApiCallResponse(error: APICallError): unknown {
4
+ if (error.data !== undefined) {
5
+ return error.data;
6
+ }
7
+ if (error.responseBody != null) {
8
+ try {
9
+ return JSON.parse(error.responseBody);
10
+ } catch {
11
+ return error.responseBody;
12
+ }
13
+ }
14
+ return {};
15
+ }
@@ -0,0 +1,84 @@
1
+ import { GatewayError } from './gateway-error';
2
+
3
+ const name = 'GatewayAuthenticationError';
4
+ const marker = `vercel.ai.gateway.error.${name}`;
5
+ const symbol = Symbol.for(marker);
6
+
7
+ /**
8
+ * Authentication failed - invalid API key or OIDC token
9
+ */
10
+ export class GatewayAuthenticationError extends GatewayError {
11
+ private readonly [symbol] = true; // used in isInstance
12
+
13
+ readonly name = name;
14
+ readonly type = 'authentication_error';
15
+
16
+ constructor({
17
+ message = 'Authentication failed',
18
+ statusCode = 401,
19
+ cause,
20
+ generationId,
21
+ }: {
22
+ message?: string;
23
+ statusCode?: number;
24
+ cause?: unknown;
25
+ generationId?: string;
26
+ } = {}) {
27
+ super({ message, statusCode, cause, generationId });
28
+ }
29
+
30
+ static isInstance(error: unknown): error is GatewayAuthenticationError {
31
+ return GatewayError.hasMarker(error) && symbol in error;
32
+ }
33
+
34
+ /**
35
+ * Creates a contextual error message when authentication fails
36
+ */
37
+ static createContextualError({
38
+ apiKeyProvided,
39
+ oidcTokenProvided,
40
+ message = 'Authentication failed',
41
+ statusCode = 401,
42
+ cause,
43
+ generationId,
44
+ }: {
45
+ apiKeyProvided: boolean;
46
+ oidcTokenProvided: boolean;
47
+ message?: string;
48
+ statusCode?: number;
49
+ cause?: unknown;
50
+ generationId?: string;
51
+ }): GatewayAuthenticationError {
52
+ let contextualMessage: string;
53
+
54
+ if (apiKeyProvided) {
55
+ contextualMessage = `AI Gateway authentication failed: Invalid API key.
56
+
57
+ Create a new API key: https://vercel.com/d?to=%2F%5Bteam%5D%2F%7E%2Fai%2Fapi-keys
58
+
59
+ Provide via 'apiKey' option or 'AI_GATEWAY_API_KEY' environment variable.`;
60
+ } else if (oidcTokenProvided) {
61
+ contextualMessage = `AI Gateway authentication failed: Invalid OIDC token.
62
+
63
+ Run 'npx vercel link' to link your project, then 'vc env pull' to fetch the token.
64
+
65
+ Alternatively, use an API key: https://vercel.com/d?to=%2F%5Bteam%5D%2F%7E%2Fai%2Fapi-keys`;
66
+ } else {
67
+ contextualMessage = `AI Gateway authentication failed: No authentication provided.
68
+
69
+ Option 1 - API key:
70
+ Create an API key: https://vercel.com/d?to=%2F%5Bteam%5D%2F%7E%2Fai%2Fapi-keys
71
+ Provide via 'apiKey' option or 'AI_GATEWAY_API_KEY' environment variable.
72
+
73
+ Option 2 - OIDC token:
74
+ Run 'npx vercel link' to link your project, then 'vc env pull' to fetch the token.`;
75
+ }
76
+
77
+ return new GatewayAuthenticationError({
78
+ message: contextualMessage,
79
+ statusCode,
80
+ cause,
81
+ generationId,
82
+ });
83
+ }
84
+ }