@ai-sdk/gateway 0.0.0-64aae7dd-20260114144918 → 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.
- package/CHANGELOG.md +49 -4
- package/dist/index.d.mts +20 -10
- package/dist/index.d.ts +20 -10
- package/dist/index.js +62 -25
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +62 -25
- package/dist/index.mjs.map +1 -1
- package/docs/00-ai-gateway.mdx +625 -0
- package/package.json +12 -5
- package/src/errors/as-gateway-error.ts +33 -0
- package/src/errors/create-gateway-error.test.ts +590 -0
- package/src/errors/create-gateway-error.ts +132 -0
- package/src/errors/extract-api-call-response.test.ts +270 -0
- package/src/errors/extract-api-call-response.ts +15 -0
- package/src/errors/gateway-authentication-error.ts +84 -0
- package/src/errors/gateway-error-types.test.ts +278 -0
- package/src/errors/gateway-error.ts +47 -0
- package/src/errors/gateway-internal-server-error.ts +33 -0
- package/src/errors/gateway-invalid-request-error.ts +33 -0
- package/src/errors/gateway-model-not-found-error.ts +47 -0
- package/src/errors/gateway-rate-limit-error.ts +33 -0
- package/src/errors/gateway-response-error.ts +42 -0
- package/src/errors/index.ts +16 -0
- package/src/errors/parse-auth-method.test.ts +136 -0
- package/src/errors/parse-auth-method.ts +23 -0
- package/src/gateway-config.ts +7 -0
- package/src/gateway-embedding-model-settings.ts +22 -0
- package/src/gateway-embedding-model.test.ts +213 -0
- package/src/gateway-embedding-model.ts +109 -0
- package/src/gateway-fetch-metadata.test.ts +774 -0
- package/src/gateway-fetch-metadata.ts +127 -0
- package/src/gateway-image-model-settings.ts +12 -0
- package/src/gateway-image-model.test.ts +823 -0
- package/src/gateway-image-model.ts +145 -0
- package/src/gateway-language-model-settings.ts +159 -0
- package/src/gateway-language-model.test.ts +1485 -0
- package/src/gateway-language-model.ts +212 -0
- package/src/gateway-model-entry.ts +58 -0
- package/src/gateway-provider-options.ts +66 -0
- package/src/gateway-provider.test.ts +1210 -0
- package/src/gateway-provider.ts +284 -0
- package/src/gateway-tools.ts +15 -0
- package/src/index.ts +27 -0
- package/src/tool/perplexity-search.ts +294 -0
- package/src/vercel-environment.test.ts +65 -0
- package/src/vercel-environment.ts +6 -0
- package/src/version.ts +6 -0
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import {
|
|
2
|
+
loadOptionalSetting,
|
|
3
|
+
withoutTrailingSlash,
|
|
4
|
+
type FetchFunction,
|
|
5
|
+
} from '@ai-sdk/provider-utils';
|
|
6
|
+
import { asGatewayError, GatewayAuthenticationError } from './errors';
|
|
7
|
+
import {
|
|
8
|
+
GATEWAY_AUTH_METHOD_HEADER,
|
|
9
|
+
parseAuthMethod,
|
|
10
|
+
} from './errors/parse-auth-method';
|
|
11
|
+
import {
|
|
12
|
+
GatewayFetchMetadata,
|
|
13
|
+
type GatewayFetchMetadataResponse,
|
|
14
|
+
type GatewayCreditsResponse,
|
|
15
|
+
} from './gateway-fetch-metadata';
|
|
16
|
+
import { GatewayLanguageModel } from './gateway-language-model';
|
|
17
|
+
import { GatewayEmbeddingModel } from './gateway-embedding-model';
|
|
18
|
+
import { GatewayImageModel } from './gateway-image-model';
|
|
19
|
+
import type { GatewayEmbeddingModelId } from './gateway-embedding-model-settings';
|
|
20
|
+
import type { GatewayImageModelId } from './gateway-image-model-settings';
|
|
21
|
+
import { gatewayTools } from './gateway-tools';
|
|
22
|
+
import { getVercelOidcToken, getVercelRequestId } from './vercel-environment';
|
|
23
|
+
import type { GatewayModelId } from './gateway-language-model-settings';
|
|
24
|
+
import type {
|
|
25
|
+
LanguageModelV3,
|
|
26
|
+
EmbeddingModelV3,
|
|
27
|
+
ImageModelV3,
|
|
28
|
+
ProviderV3,
|
|
29
|
+
} from '@ai-sdk/provider';
|
|
30
|
+
import { withUserAgentSuffix } from '@ai-sdk/provider-utils';
|
|
31
|
+
import { VERSION } from './version';
|
|
32
|
+
|
|
33
|
+
export interface GatewayProvider extends ProviderV3 {
|
|
34
|
+
(modelId: GatewayModelId): LanguageModelV3;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
Creates a model for text generation.
|
|
38
|
+
*/
|
|
39
|
+
languageModel(modelId: GatewayModelId): LanguageModelV3;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
Returns available providers and models for use with the remote provider.
|
|
43
|
+
*/
|
|
44
|
+
getAvailableModels(): Promise<GatewayFetchMetadataResponse>;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
Returns credit information for the authenticated user.
|
|
48
|
+
*/
|
|
49
|
+
getCredits(): Promise<GatewayCreditsResponse>;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
Creates a model for generating text embeddings.
|
|
53
|
+
*/
|
|
54
|
+
embeddingModel(modelId: GatewayEmbeddingModelId): EmbeddingModelV3;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @deprecated Use `embeddingModel` instead.
|
|
58
|
+
*/
|
|
59
|
+
textEmbeddingModel(modelId: GatewayEmbeddingModelId): EmbeddingModelV3;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
Creates a model for generating images.
|
|
63
|
+
*/
|
|
64
|
+
imageModel(modelId: GatewayImageModelId): ImageModelV3;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
Gateway-specific tools executed server-side.
|
|
68
|
+
*/
|
|
69
|
+
tools: typeof gatewayTools;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface GatewayProviderSettings {
|
|
73
|
+
/**
|
|
74
|
+
The base URL prefix for API calls. Defaults to `https://ai-gateway.vercel.sh/v1/ai`.
|
|
75
|
+
*/
|
|
76
|
+
baseURL?: string;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
API key that is being sent using the `Authorization` header.
|
|
80
|
+
*/
|
|
81
|
+
apiKey?: string;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
Custom headers to include in the requests.
|
|
85
|
+
*/
|
|
86
|
+
headers?: Record<string, string>;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
Custom fetch implementation. You can use it as a middleware to intercept requests,
|
|
90
|
+
or to provide a custom fetch implementation for e.g. testing.
|
|
91
|
+
*/
|
|
92
|
+
fetch?: FetchFunction;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
How frequently to refresh the metadata cache in milliseconds.
|
|
96
|
+
*/
|
|
97
|
+
metadataCacheRefreshMillis?: number;
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* @internal For testing purposes only
|
|
101
|
+
*/
|
|
102
|
+
_internal?: {
|
|
103
|
+
currentDate?: () => Date;
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const AI_GATEWAY_PROTOCOL_VERSION = '0.0.1';
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
Create a remote provider instance.
|
|
111
|
+
*/
|
|
112
|
+
export function createGatewayProvider(
|
|
113
|
+
options: GatewayProviderSettings = {},
|
|
114
|
+
): GatewayProvider {
|
|
115
|
+
let pendingMetadata: Promise<GatewayFetchMetadataResponse> | null = null;
|
|
116
|
+
let metadataCache: GatewayFetchMetadataResponse | null = null;
|
|
117
|
+
const cacheRefreshMillis =
|
|
118
|
+
options.metadataCacheRefreshMillis ?? 1000 * 60 * 5;
|
|
119
|
+
let lastFetchTime = 0;
|
|
120
|
+
|
|
121
|
+
const baseURL =
|
|
122
|
+
withoutTrailingSlash(options.baseURL) ??
|
|
123
|
+
'https://ai-gateway.vercel.sh/v3/ai';
|
|
124
|
+
|
|
125
|
+
const getHeaders = async () => {
|
|
126
|
+
try {
|
|
127
|
+
const auth = await getGatewayAuthToken(options);
|
|
128
|
+
return withUserAgentSuffix(
|
|
129
|
+
{
|
|
130
|
+
Authorization: `Bearer ${auth.token}`,
|
|
131
|
+
'ai-gateway-protocol-version': AI_GATEWAY_PROTOCOL_VERSION,
|
|
132
|
+
[GATEWAY_AUTH_METHOD_HEADER]: auth.authMethod,
|
|
133
|
+
...options.headers,
|
|
134
|
+
},
|
|
135
|
+
`ai-sdk/gateway/${VERSION}`,
|
|
136
|
+
);
|
|
137
|
+
} catch (error) {
|
|
138
|
+
throw GatewayAuthenticationError.createContextualError({
|
|
139
|
+
apiKeyProvided: false,
|
|
140
|
+
oidcTokenProvided: false,
|
|
141
|
+
statusCode: 401,
|
|
142
|
+
cause: error,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const createO11yHeaders = () => {
|
|
148
|
+
const deploymentId = loadOptionalSetting({
|
|
149
|
+
settingValue: undefined,
|
|
150
|
+
environmentVariableName: 'VERCEL_DEPLOYMENT_ID',
|
|
151
|
+
});
|
|
152
|
+
const environment = loadOptionalSetting({
|
|
153
|
+
settingValue: undefined,
|
|
154
|
+
environmentVariableName: 'VERCEL_ENV',
|
|
155
|
+
});
|
|
156
|
+
const region = loadOptionalSetting({
|
|
157
|
+
settingValue: undefined,
|
|
158
|
+
environmentVariableName: 'VERCEL_REGION',
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
return async () => {
|
|
162
|
+
const requestId = await getVercelRequestId();
|
|
163
|
+
return {
|
|
164
|
+
...(deploymentId && { 'ai-o11y-deployment-id': deploymentId }),
|
|
165
|
+
...(environment && { 'ai-o11y-environment': environment }),
|
|
166
|
+
...(region && { 'ai-o11y-region': region }),
|
|
167
|
+
...(requestId && { 'ai-o11y-request-id': requestId }),
|
|
168
|
+
};
|
|
169
|
+
};
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
const createLanguageModel = (modelId: GatewayModelId) => {
|
|
173
|
+
return new GatewayLanguageModel(modelId, {
|
|
174
|
+
provider: 'gateway',
|
|
175
|
+
baseURL,
|
|
176
|
+
headers: getHeaders,
|
|
177
|
+
fetch: options.fetch,
|
|
178
|
+
o11yHeaders: createO11yHeaders(),
|
|
179
|
+
});
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const getAvailableModels = async () => {
|
|
183
|
+
const now = options._internal?.currentDate?.().getTime() ?? Date.now();
|
|
184
|
+
if (!pendingMetadata || now - lastFetchTime > cacheRefreshMillis) {
|
|
185
|
+
lastFetchTime = now;
|
|
186
|
+
|
|
187
|
+
pendingMetadata = new GatewayFetchMetadata({
|
|
188
|
+
baseURL,
|
|
189
|
+
headers: getHeaders,
|
|
190
|
+
fetch: options.fetch,
|
|
191
|
+
})
|
|
192
|
+
.getAvailableModels()
|
|
193
|
+
.then(metadata => {
|
|
194
|
+
metadataCache = metadata;
|
|
195
|
+
return metadata;
|
|
196
|
+
})
|
|
197
|
+
.catch(async (error: unknown) => {
|
|
198
|
+
throw await asGatewayError(
|
|
199
|
+
error,
|
|
200
|
+
await parseAuthMethod(await getHeaders()),
|
|
201
|
+
);
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return metadataCache ? Promise.resolve(metadataCache) : pendingMetadata;
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
const getCredits = async () => {
|
|
209
|
+
return new GatewayFetchMetadata({
|
|
210
|
+
baseURL,
|
|
211
|
+
headers: getHeaders,
|
|
212
|
+
fetch: options.fetch,
|
|
213
|
+
})
|
|
214
|
+
.getCredits()
|
|
215
|
+
.catch(async (error: unknown) => {
|
|
216
|
+
throw await asGatewayError(
|
|
217
|
+
error,
|
|
218
|
+
await parseAuthMethod(await getHeaders()),
|
|
219
|
+
);
|
|
220
|
+
});
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
const provider = function (modelId: GatewayModelId) {
|
|
224
|
+
if (new.target) {
|
|
225
|
+
throw new Error(
|
|
226
|
+
'The Gateway Provider model function cannot be called with the new keyword.',
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return createLanguageModel(modelId);
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
provider.specificationVersion = 'v3' as const;
|
|
234
|
+
provider.getAvailableModels = getAvailableModels;
|
|
235
|
+
provider.getCredits = getCredits;
|
|
236
|
+
provider.imageModel = (modelId: GatewayImageModelId) => {
|
|
237
|
+
return new GatewayImageModel(modelId, {
|
|
238
|
+
provider: 'gateway',
|
|
239
|
+
baseURL,
|
|
240
|
+
headers: getHeaders,
|
|
241
|
+
fetch: options.fetch,
|
|
242
|
+
o11yHeaders: createO11yHeaders(),
|
|
243
|
+
});
|
|
244
|
+
};
|
|
245
|
+
provider.languageModel = createLanguageModel;
|
|
246
|
+
const createEmbeddingModel = (modelId: GatewayEmbeddingModelId) => {
|
|
247
|
+
return new GatewayEmbeddingModel(modelId, {
|
|
248
|
+
provider: 'gateway',
|
|
249
|
+
baseURL,
|
|
250
|
+
headers: getHeaders,
|
|
251
|
+
fetch: options.fetch,
|
|
252
|
+
o11yHeaders: createO11yHeaders(),
|
|
253
|
+
});
|
|
254
|
+
};
|
|
255
|
+
provider.embeddingModel = createEmbeddingModel;
|
|
256
|
+
provider.textEmbeddingModel = createEmbeddingModel;
|
|
257
|
+
provider.tools = gatewayTools;
|
|
258
|
+
|
|
259
|
+
return provider;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export const gateway = createGatewayProvider();
|
|
263
|
+
|
|
264
|
+
export async function getGatewayAuthToken(
|
|
265
|
+
options: GatewayProviderSettings,
|
|
266
|
+
): Promise<{ token: string; authMethod: 'api-key' | 'oidc' }> {
|
|
267
|
+
const apiKey = loadOptionalSetting({
|
|
268
|
+
settingValue: options.apiKey,
|
|
269
|
+
environmentVariableName: 'AI_GATEWAY_API_KEY',
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
if (apiKey) {
|
|
273
|
+
return {
|
|
274
|
+
token: apiKey,
|
|
275
|
+
authMethod: 'api-key',
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const oidcToken = await getVercelOidcToken();
|
|
280
|
+
return {
|
|
281
|
+
token: oidcToken,
|
|
282
|
+
authMethod: 'oidc',
|
|
283
|
+
};
|
|
284
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { perplexitySearch } from './tool/perplexity-search';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Gateway-specific provider-defined tools.
|
|
5
|
+
*/
|
|
6
|
+
export const gatewayTools = {
|
|
7
|
+
/**
|
|
8
|
+
* Search the web using Perplexity's Search API for real-time information,
|
|
9
|
+
* news, research papers, and articles.
|
|
10
|
+
*
|
|
11
|
+
* Provides ranked search results with advanced filtering options including
|
|
12
|
+
* domain, language, date range, and recency filters.
|
|
13
|
+
*/
|
|
14
|
+
perplexitySearch,
|
|
15
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export type { GatewayModelId } from './gateway-language-model-settings';
|
|
2
|
+
export type {
|
|
3
|
+
GatewayLanguageModelEntry,
|
|
4
|
+
GatewayLanguageModelSpecification,
|
|
5
|
+
} from './gateway-model-entry';
|
|
6
|
+
export type { GatewayCreditsResponse } from './gateway-fetch-metadata';
|
|
7
|
+
export type { GatewayLanguageModelEntry as GatewayModelEntry } from './gateway-model-entry';
|
|
8
|
+
export {
|
|
9
|
+
createGatewayProvider,
|
|
10
|
+
createGatewayProvider as createGateway,
|
|
11
|
+
gateway,
|
|
12
|
+
} from './gateway-provider';
|
|
13
|
+
export type {
|
|
14
|
+
GatewayProvider,
|
|
15
|
+
GatewayProviderSettings,
|
|
16
|
+
} from './gateway-provider';
|
|
17
|
+
export type { GatewayProviderOptions } from './gateway-provider-options';
|
|
18
|
+
export {
|
|
19
|
+
GatewayError,
|
|
20
|
+
GatewayAuthenticationError,
|
|
21
|
+
GatewayInvalidRequestError,
|
|
22
|
+
GatewayRateLimitError,
|
|
23
|
+
GatewayModelNotFoundError,
|
|
24
|
+
GatewayInternalServerError,
|
|
25
|
+
GatewayResponseError,
|
|
26
|
+
} from './errors';
|
|
27
|
+
export type { GatewayErrorResponse } from './errors';
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createProviderToolFactoryWithOutputSchema,
|
|
3
|
+
lazySchema,
|
|
4
|
+
zodSchema,
|
|
5
|
+
} from '@ai-sdk/provider-utils';
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
|
|
8
|
+
export interface PerplexitySearchConfig {
|
|
9
|
+
/**
|
|
10
|
+
* Default maximum number of search results to return (1-20, default: 10).
|
|
11
|
+
*/
|
|
12
|
+
maxResults?: number;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Default maximum tokens to extract per search result page (256-2048, default: 2048).
|
|
16
|
+
*/
|
|
17
|
+
maxTokensPerPage?: number;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Default maximum total tokens across all search results (default: 25000, max: 1000000).
|
|
21
|
+
*/
|
|
22
|
+
maxTokens?: number;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Default two-letter ISO 3166-1 alpha-2 country code for regional search results.
|
|
26
|
+
* Examples: 'US', 'GB', 'FR'
|
|
27
|
+
*/
|
|
28
|
+
country?: string;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Default list of domains to include or exclude from search results (max 20).
|
|
32
|
+
* To include: ['nature.com', 'science.org']
|
|
33
|
+
* To exclude: ['-example.com', '-spam.net']
|
|
34
|
+
*/
|
|
35
|
+
searchDomainFilter?: string[];
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Default list of ISO 639-1 language codes to filter results (max 10, lowercase).
|
|
39
|
+
* Examples: ['en', 'fr', 'de']
|
|
40
|
+
*/
|
|
41
|
+
searchLanguageFilter?: string[];
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Default recency filter for results.
|
|
45
|
+
* Cannot be combined with searchAfterDate/searchBeforeDate at runtime.
|
|
46
|
+
*/
|
|
47
|
+
searchRecencyFilter?: 'day' | 'week' | 'month' | 'year';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface PerplexitySearchResult {
|
|
51
|
+
/** Title of the search result */
|
|
52
|
+
title: string;
|
|
53
|
+
/** URL of the search result */
|
|
54
|
+
url: string;
|
|
55
|
+
/** Text snippet/preview of the content */
|
|
56
|
+
snippet: string;
|
|
57
|
+
/** Publication date of the content */
|
|
58
|
+
date?: string;
|
|
59
|
+
/** Last updated date of the content */
|
|
60
|
+
lastUpdated?: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface PerplexitySearchResponse {
|
|
64
|
+
/** Array of search results */
|
|
65
|
+
results: PerplexitySearchResult[];
|
|
66
|
+
/** Unique identifier for this search request */
|
|
67
|
+
id: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface PerplexitySearchError {
|
|
71
|
+
/** Error type */
|
|
72
|
+
error: 'api_error' | 'rate_limit' | 'timeout' | 'invalid_input' | 'unknown';
|
|
73
|
+
/** HTTP status code if applicable */
|
|
74
|
+
statusCode?: number;
|
|
75
|
+
/** Human-readable error message */
|
|
76
|
+
message: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface PerplexitySearchInput {
|
|
80
|
+
/**
|
|
81
|
+
* Search query (string) or multiple queries (array of up to 5 strings).
|
|
82
|
+
* Multi-query searches return combined results from all queries.
|
|
83
|
+
*/
|
|
84
|
+
query: string | string[];
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Maximum number of search results to return (1-20, default: 10).
|
|
88
|
+
*/
|
|
89
|
+
max_results?: number;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Maximum number of tokens to extract per search result page (256-2048, default: 2048).
|
|
93
|
+
*/
|
|
94
|
+
max_tokens_per_page?: number;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Maximum total tokens across all search results (default: 25000, max: 1000000).
|
|
98
|
+
*/
|
|
99
|
+
max_tokens?: number;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Two-letter ISO 3166-1 alpha-2 country code for regional search results.
|
|
103
|
+
* Examples: 'US', 'GB', 'FR'
|
|
104
|
+
*/
|
|
105
|
+
country?: string;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* List of domains to include or exclude from search results (max 20).
|
|
109
|
+
* To include: ['nature.com', 'science.org']
|
|
110
|
+
* To exclude: ['-example.com', '-spam.net']
|
|
111
|
+
*/
|
|
112
|
+
search_domain_filter?: string[];
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* List of ISO 639-1 language codes to filter results (max 10, lowercase).
|
|
116
|
+
* Examples: ['en', 'fr', 'de']
|
|
117
|
+
*/
|
|
118
|
+
search_language_filter?: string[];
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Include only results published after this date.
|
|
122
|
+
* Format: 'MM/DD/YYYY' (e.g., '3/1/2025')
|
|
123
|
+
* Cannot be used with search_recency_filter.
|
|
124
|
+
*/
|
|
125
|
+
search_after_date?: string;
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Include only results published before this date.
|
|
129
|
+
* Format: 'MM/DD/YYYY' (e.g., '3/15/2025')
|
|
130
|
+
* Cannot be used with search_recency_filter.
|
|
131
|
+
*/
|
|
132
|
+
search_before_date?: string;
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Include only results last updated after this date.
|
|
136
|
+
* Format: 'MM/DD/YYYY' (e.g., '3/1/2025')
|
|
137
|
+
* Cannot be used with search_recency_filter.
|
|
138
|
+
*/
|
|
139
|
+
last_updated_after_filter?: string;
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Include only results last updated before this date.
|
|
143
|
+
* Format: 'MM/DD/YYYY' (e.g., '3/15/2025')
|
|
144
|
+
* Cannot be used with search_recency_filter.
|
|
145
|
+
*/
|
|
146
|
+
last_updated_before_filter?: string;
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Filter results by relative time period.
|
|
150
|
+
* Cannot be used with search_after_date or search_before_date.
|
|
151
|
+
*/
|
|
152
|
+
search_recency_filter?: 'day' | 'week' | 'month' | 'year';
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export type PerplexitySearchOutput =
|
|
156
|
+
| PerplexitySearchResponse
|
|
157
|
+
| PerplexitySearchError;
|
|
158
|
+
|
|
159
|
+
const perplexitySearchInputSchema = lazySchema(() =>
|
|
160
|
+
zodSchema(
|
|
161
|
+
z.object({
|
|
162
|
+
query: z
|
|
163
|
+
.union([z.string(), z.array(z.string())])
|
|
164
|
+
.describe(
|
|
165
|
+
'Search query (string) or multiple queries (array of up to 5 strings). Multi-query searches return combined results from all queries.',
|
|
166
|
+
),
|
|
167
|
+
|
|
168
|
+
max_results: z
|
|
169
|
+
.number()
|
|
170
|
+
.optional()
|
|
171
|
+
.describe(
|
|
172
|
+
'Maximum number of search results to return (1-20, default: 10)',
|
|
173
|
+
),
|
|
174
|
+
|
|
175
|
+
max_tokens_per_page: z
|
|
176
|
+
.number()
|
|
177
|
+
.optional()
|
|
178
|
+
.describe(
|
|
179
|
+
'Maximum number of tokens to extract per search result page (256-2048, default: 2048)',
|
|
180
|
+
),
|
|
181
|
+
|
|
182
|
+
max_tokens: z
|
|
183
|
+
.number()
|
|
184
|
+
.optional()
|
|
185
|
+
.describe(
|
|
186
|
+
'Maximum total tokens across all search results (default: 25000, max: 1000000)',
|
|
187
|
+
),
|
|
188
|
+
|
|
189
|
+
country: z
|
|
190
|
+
.string()
|
|
191
|
+
.optional()
|
|
192
|
+
.describe(
|
|
193
|
+
"Two-letter ISO 3166-1 alpha-2 country code for regional search results (e.g., 'US', 'GB', 'FR')",
|
|
194
|
+
),
|
|
195
|
+
|
|
196
|
+
search_domain_filter: z
|
|
197
|
+
.array(z.string())
|
|
198
|
+
.optional()
|
|
199
|
+
.describe(
|
|
200
|
+
"List of domains to include or exclude from search results (max 20). To include: ['nature.com', 'science.org']. To exclude: ['-example.com', '-spam.net']",
|
|
201
|
+
),
|
|
202
|
+
|
|
203
|
+
search_language_filter: z
|
|
204
|
+
.array(z.string())
|
|
205
|
+
.optional()
|
|
206
|
+
.describe(
|
|
207
|
+
"List of ISO 639-1 language codes to filter results (max 10, lowercase). Examples: ['en', 'fr', 'de']",
|
|
208
|
+
),
|
|
209
|
+
|
|
210
|
+
search_after_date: z
|
|
211
|
+
.string()
|
|
212
|
+
.optional()
|
|
213
|
+
.describe(
|
|
214
|
+
"Include only results published after this date. Format: 'MM/DD/YYYY' (e.g., '3/1/2025'). Cannot be used with search_recency_filter.",
|
|
215
|
+
),
|
|
216
|
+
|
|
217
|
+
search_before_date: z
|
|
218
|
+
.string()
|
|
219
|
+
.optional()
|
|
220
|
+
.describe(
|
|
221
|
+
"Include only results published before this date. Format: 'MM/DD/YYYY' (e.g., '3/15/2025'). Cannot be used with search_recency_filter.",
|
|
222
|
+
),
|
|
223
|
+
|
|
224
|
+
last_updated_after_filter: z
|
|
225
|
+
.string()
|
|
226
|
+
.optional()
|
|
227
|
+
.describe(
|
|
228
|
+
"Include only results last updated after this date. Format: 'MM/DD/YYYY' (e.g., '3/1/2025'). Cannot be used with search_recency_filter.",
|
|
229
|
+
),
|
|
230
|
+
|
|
231
|
+
last_updated_before_filter: z
|
|
232
|
+
.string()
|
|
233
|
+
.optional()
|
|
234
|
+
.describe(
|
|
235
|
+
"Include only results last updated before this date. Format: 'MM/DD/YYYY' (e.g., '3/15/2025'). Cannot be used with search_recency_filter.",
|
|
236
|
+
),
|
|
237
|
+
|
|
238
|
+
search_recency_filter: z
|
|
239
|
+
.enum(['day', 'week', 'month', 'year'])
|
|
240
|
+
.optional()
|
|
241
|
+
.describe(
|
|
242
|
+
'Filter results by relative time period. Cannot be used with search_after_date or search_before_date.',
|
|
243
|
+
),
|
|
244
|
+
}),
|
|
245
|
+
),
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
const perplexitySearchOutputSchema = lazySchema(() =>
|
|
249
|
+
zodSchema(
|
|
250
|
+
z.union([
|
|
251
|
+
// Success response
|
|
252
|
+
z.object({
|
|
253
|
+
results: z.array(
|
|
254
|
+
z.object({
|
|
255
|
+
title: z.string(),
|
|
256
|
+
url: z.string(),
|
|
257
|
+
snippet: z.string(),
|
|
258
|
+
date: z.string().optional(),
|
|
259
|
+
lastUpdated: z.string().optional(),
|
|
260
|
+
}),
|
|
261
|
+
),
|
|
262
|
+
id: z.string(),
|
|
263
|
+
}),
|
|
264
|
+
// Error response
|
|
265
|
+
z.object({
|
|
266
|
+
error: z.enum([
|
|
267
|
+
'api_error',
|
|
268
|
+
'rate_limit',
|
|
269
|
+
'timeout',
|
|
270
|
+
'invalid_input',
|
|
271
|
+
'unknown',
|
|
272
|
+
]),
|
|
273
|
+
statusCode: z.number().optional(),
|
|
274
|
+
message: z.string(),
|
|
275
|
+
}),
|
|
276
|
+
]),
|
|
277
|
+
),
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
export const perplexitySearchToolFactory =
|
|
281
|
+
createProviderToolFactoryWithOutputSchema<
|
|
282
|
+
PerplexitySearchInput,
|
|
283
|
+
PerplexitySearchOutput,
|
|
284
|
+
PerplexitySearchConfig
|
|
285
|
+
>({
|
|
286
|
+
id: 'gateway.perplexity_search',
|
|
287
|
+
inputSchema: perplexitySearchInputSchema,
|
|
288
|
+
outputSchema: perplexitySearchOutputSchema,
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
export const perplexitySearch = (
|
|
292
|
+
config: PerplexitySearchConfig = {},
|
|
293
|
+
): ReturnType<typeof perplexitySearchToolFactory> =>
|
|
294
|
+
perplexitySearchToolFactory(config);
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { getVercelOidcToken, getVercelRequestId } from './vercel-environment';
|
|
3
|
+
|
|
4
|
+
const SYMBOL_FOR_REQ_CONTEXT = Symbol.for('@vercel/request-context');
|
|
5
|
+
|
|
6
|
+
describe('getVercelRequestId', () => {
|
|
7
|
+
const originalSymbolValue = (globalThis as any)[SYMBOL_FOR_REQ_CONTEXT];
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
(globalThis as any)[SYMBOL_FOR_REQ_CONTEXT] = undefined;
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
if (originalSymbolValue) {
|
|
15
|
+
(globalThis as any)[SYMBOL_FOR_REQ_CONTEXT] = originalSymbolValue;
|
|
16
|
+
} else {
|
|
17
|
+
(globalThis as any)[SYMBOL_FOR_REQ_CONTEXT] = undefined;
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('should get request ID from request headers when available', async () => {
|
|
22
|
+
(globalThis as any)[SYMBOL_FOR_REQ_CONTEXT] = {
|
|
23
|
+
get: () => ({
|
|
24
|
+
headers: {
|
|
25
|
+
'x-vercel-id': 'req_1234567890abcdef',
|
|
26
|
+
},
|
|
27
|
+
}),
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const requestId = await getVercelRequestId();
|
|
31
|
+
expect(requestId).toBe('req_1234567890abcdef');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should return undefined when request ID header is not available', async () => {
|
|
35
|
+
(globalThis as any)[SYMBOL_FOR_REQ_CONTEXT] = {
|
|
36
|
+
get: () => ({ headers: {} }),
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const requestId = await getVercelRequestId();
|
|
40
|
+
expect(requestId).toBeUndefined();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should return undefined when no headers are available', async () => {
|
|
44
|
+
(globalThis as any)[SYMBOL_FOR_REQ_CONTEXT] = {
|
|
45
|
+
get: () => ({}),
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const requestId = await getVercelRequestId();
|
|
49
|
+
expect(requestId).toBeUndefined();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should handle missing request context gracefully', async () => {
|
|
53
|
+
(globalThis as any)[SYMBOL_FOR_REQ_CONTEXT] = undefined;
|
|
54
|
+
|
|
55
|
+
const requestId = await getVercelRequestId();
|
|
56
|
+
expect(requestId).toBeUndefined();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should handle missing get method in request context', async () => {
|
|
60
|
+
(globalThis as any)[SYMBOL_FOR_REQ_CONTEXT] = {};
|
|
61
|
+
|
|
62
|
+
const requestId = await getVercelRequestId();
|
|
63
|
+
expect(requestId).toBeUndefined();
|
|
64
|
+
});
|
|
65
|
+
});
|