@ai-sdk/gateway 4.0.0-canary.98 → 4.0.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/CHANGELOG.md +301 -0
- package/dist/index.d.ts +278 -27
- package/dist/index.js +595 -121
- package/dist/index.js.map +1 -1
- package/docs/00-ai-gateway.mdx +179 -0
- package/package.json +4 -4
- package/src/errors/create-gateway-error.ts +16 -0
- package/src/errors/gateway-failed-dependency-error.ts +35 -0
- package/src/errors/gateway-forbidden-error.ts +34 -0
- package/src/errors/index.ts +2 -0
- package/src/gateway-embedding-model.ts +24 -1
- package/src/gateway-image-model.ts +5 -0
- package/src/gateway-language-model-settings.ts +5 -4
- package/src/gateway-language-model.ts +25 -19
- package/src/gateway-provider-options.ts +50 -116
- package/src/gateway-provider.ts +113 -0
- package/src/gateway-realtime-auth.ts +126 -0
- package/src/gateway-realtime-model-settings.ts +6 -0
- package/src/gateway-realtime-model.ts +118 -0
- package/src/gateway-reranking-model.ts +24 -1
- package/src/gateway-speech-model-settings.ts +5 -1
- package/src/gateway-speech-model.ts +5 -0
- package/src/gateway-tools.ts +10 -0
- package/src/gateway-transcription-model-settings.ts +6 -1
- package/src/gateway-transcription-model.ts +5 -0
- package/src/gateway-video-model-settings.ts +0 -2
- package/src/gateway-video-model.ts +7 -0
- package/src/index.ts +11 -0
- package/src/tool/exa-search.ts +352 -0
package/docs/00-ai-gateway.mdx
CHANGED
|
@@ -235,6 +235,98 @@ console.log(ranking);
|
|
|
235
235
|
|
|
236
236
|
Reranking models are useful for improving search results in retrieval-augmented generation (RAG) pipelines by re-scoring candidate documents after an initial retrieval step.
|
|
237
237
|
|
|
238
|
+
## Realtime Models
|
|
239
|
+
|
|
240
|
+
<Note type="warning">
|
|
241
|
+
Realtime support is experimental and the API may change in patch releases.
|
|
242
|
+
</Note>
|
|
243
|
+
|
|
244
|
+
You can create realtime models for bidirectional audio/text WebSocket sessions using the `experimental_realtime` method on the provider instance. The first argument is the model ID in the format `creator/model-name`:
|
|
245
|
+
|
|
246
|
+
```ts
|
|
247
|
+
import { gateway } from '@ai-sdk/gateway';
|
|
248
|
+
|
|
249
|
+
const model = gateway.experimental_realtime('openai/gpt-realtime-2');
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
The Gateway normalizes realtime the same way it normalizes every other modality: your client speaks the [normalized AI SDK realtime protocol](/docs/ai-sdk-core/realtime) and the Gateway translates to and from the upstream provider server-side. This means the same client code works regardless of which provider backs the model.
|
|
253
|
+
|
|
254
|
+
Realtime sessions run in the browser and require a short-lived Gateway client secret created on your server with `gateway.experimental_realtime.getToken()`. The method uses your Gateway credential (`apiKey`, `AI_GATEWAY_API_KEY`, or Vercel OIDC token) to mint a `vcst_` client secret and returns the WebSocket URL for the model.
|
|
255
|
+
|
|
256
|
+
```ts filename='app/api/realtime/setup/route.ts'
|
|
257
|
+
import { gateway } from 'ai';
|
|
258
|
+
|
|
259
|
+
export async function POST() {
|
|
260
|
+
const token = await gateway.experimental_realtime.getToken({
|
|
261
|
+
model: 'openai/gpt-realtime-2',
|
|
262
|
+
expiresAfterSeconds: 60 * 10,
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
return Response.json(token);
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
Use the matching Gateway realtime model in the browser. Creating the model is
|
|
270
|
+
safe in browser code; only `getToken()` is server-side.
|
|
271
|
+
|
|
272
|
+
```tsx filename='app/realtime/page.tsx'
|
|
273
|
+
'use client';
|
|
274
|
+
|
|
275
|
+
import { experimental_useRealtime } from '@ai-sdk/react';
|
|
276
|
+
import { gateway } from 'ai';
|
|
277
|
+
|
|
278
|
+
export default function RealtimePage() {
|
|
279
|
+
const realtime = experimental_useRealtime({
|
|
280
|
+
model: gateway.experimental_realtime('openai/gpt-realtime-2'),
|
|
281
|
+
api: {
|
|
282
|
+
token: '/api/realtime/setup',
|
|
283
|
+
},
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
// ...
|
|
287
|
+
}
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
Do not expose your Gateway API key or OIDC token to browser clients. Only return
|
|
291
|
+
the short-lived setup response from `getToken()`.
|
|
292
|
+
|
|
293
|
+
<Note>
|
|
294
|
+
The Gateway WebSocket route transports the short-lived auth token via the
|
|
295
|
+
versioned `Sec-WebSocket-Protocol` subprotocol and the model id via the
|
|
296
|
+
`?ai-model-id=` query - the WebSocket transport of the `Authorization` and
|
|
297
|
+
`ai-model-id` headers the HTTP routes use. Subprotocol values must fit the
|
|
298
|
+
WebSocket token grammar, and the complete `Sec-WebSocket-Protocol` header
|
|
299
|
+
should stay compact (under an 8 KiB safe header budget).
|
|
300
|
+
</Note>
|
|
301
|
+
|
|
302
|
+
### Provider Options
|
|
303
|
+
|
|
304
|
+
Gateway [provider options](#gateway-provider-options) — `tags`, `user`, `byok`, compliance flags, and so on — are set under `providerOptions.gateway` in the **session configuration**, mirroring how they ride the request body on the non-realtime routes. Include them in the session configuration you send to the Gateway, and the Gateway applies them server-side:
|
|
305
|
+
|
|
306
|
+
```ts
|
|
307
|
+
import type { GatewayProviderOptions } from '@ai-sdk/gateway';
|
|
308
|
+
|
|
309
|
+
const gatewayOptions: GatewayProviderOptions = {
|
|
310
|
+
tags: ['cooking-coach', 'v2'],
|
|
311
|
+
user: 'user-123',
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
const sessionConfig = {
|
|
315
|
+
instructions: 'You are a concise voice assistant.',
|
|
316
|
+
providerOptions: { gateway: gatewayOptions },
|
|
317
|
+
};
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
The `GatewayProviderOptions` type is exported from `@ai-sdk/gateway` for stable client-facing fields. The type also accepts service-owned option keys so the Gateway service can add or change server-side options without requiring an SDK release for every schema change. Runtime validation is owned by the Gateway service.
|
|
321
|
+
|
|
322
|
+
<Note>
|
|
323
|
+
Provider options are sent in the initial session update after the socket
|
|
324
|
+
opens, so connect-time options such as `byok` and quota selection require a
|
|
325
|
+
Gateway that resolves them from that update. Routing knobs like `order` /
|
|
326
|
+
`only` do not apply to realtime, where the Gateway selects the
|
|
327
|
+
WebSocket-capable provider.
|
|
328
|
+
</Note>
|
|
329
|
+
|
|
238
330
|
## Available Models
|
|
239
331
|
|
|
240
332
|
The AI Gateway supports models from OpenAI, Anthropic, Google, Meta, xAI, Mistral, DeepSeek, Amazon Bedrock, Cohere, Perplexity, Alibaba, and other providers.
|
|
@@ -563,6 +655,93 @@ for await (const part of result.stream) {
|
|
|
563
655
|
}
|
|
564
656
|
```
|
|
565
657
|
|
|
658
|
+
#### Exa Search
|
|
659
|
+
|
|
660
|
+
The Exa Search tool enables models to search the web using [Exa's Search API](https://exa.ai/docs/reference/search-api-guide-for-coding-agents). This tool is executed by the AI Gateway and returns token-efficient web excerpts for agent workflows.
|
|
661
|
+
|
|
662
|
+
```ts
|
|
663
|
+
import { gateway, generateText } from 'ai';
|
|
664
|
+
|
|
665
|
+
const result = await generateText({
|
|
666
|
+
model: 'openai/gpt-5.4-nano',
|
|
667
|
+
prompt:
|
|
668
|
+
'Find the latest AI regulation updates and summarize the key changes.',
|
|
669
|
+
tools: {
|
|
670
|
+
exa_search: gateway.tools.exaSearch(),
|
|
671
|
+
},
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
console.log(result.text);
|
|
675
|
+
console.log('Tool calls:', JSON.stringify(result.toolCalls, null, 2));
|
|
676
|
+
console.log('Tool results:', JSON.stringify(result.toolResults, null, 2));
|
|
677
|
+
```
|
|
678
|
+
|
|
679
|
+
You can also configure the search with optional parameters:
|
|
680
|
+
|
|
681
|
+
```ts
|
|
682
|
+
import { gateway, generateText } from 'ai';
|
|
683
|
+
|
|
684
|
+
const result = await generateText({
|
|
685
|
+
model: 'openai/gpt-5.4-nano',
|
|
686
|
+
prompt: 'Find recent AI regulation news from trusted sources.',
|
|
687
|
+
tools: {
|
|
688
|
+
exa_search: gateway.tools.exaSearch({
|
|
689
|
+
type: 'fast',
|
|
690
|
+
numResults: 5,
|
|
691
|
+
category: 'news',
|
|
692
|
+
includeDomains: ['reuters.com', 'bbc.com', 'nytimes.com'],
|
|
693
|
+
contents: {
|
|
694
|
+
highlights: true,
|
|
695
|
+
maxAgeHours: 24,
|
|
696
|
+
},
|
|
697
|
+
}),
|
|
698
|
+
},
|
|
699
|
+
});
|
|
700
|
+
|
|
701
|
+
console.log(result.text);
|
|
702
|
+
console.log('Tool calls:', JSON.stringify(result.toolCalls, null, 2));
|
|
703
|
+
console.log('Tool results:', JSON.stringify(result.toolResults, null, 2));
|
|
704
|
+
```
|
|
705
|
+
|
|
706
|
+
The Exa Search tool supports the following optional configuration options:
|
|
707
|
+
|
|
708
|
+
- **type** _'auto' | 'fast' | 'instant'_
|
|
709
|
+
|
|
710
|
+
Search mode. `'auto'` is the default balance of speed and quality.
|
|
711
|
+
|
|
712
|
+
- **numResults** _number_
|
|
713
|
+
|
|
714
|
+
Maximum number of results to return (1-100, default: 10).
|
|
715
|
+
|
|
716
|
+
- **category** _'company' | 'people' | 'research paper' | 'news' | 'personal site' | 'financial report'_
|
|
717
|
+
|
|
718
|
+
Focus results on a specific content type.
|
|
719
|
+
|
|
720
|
+
- **includeDomains** / **excludeDomains** _string[]_
|
|
721
|
+
|
|
722
|
+
Restrict or exclude search results by domain.
|
|
723
|
+
|
|
724
|
+
- **startPublishedDate** / **endPublishedDate** _string_
|
|
725
|
+
|
|
726
|
+
Filter results by ISO 8601 publication date.
|
|
727
|
+
|
|
728
|
+
- **contents** _object_
|
|
729
|
+
|
|
730
|
+
Control result extraction and freshness:
|
|
731
|
+
- `text` - Return full page text, optionally capped with `maxCharacters`
|
|
732
|
+
- `highlights` - Return token-efficient excerpts
|
|
733
|
+
- `maxAgeHours` - Control cached content freshness
|
|
734
|
+
- `livecrawlTimeout` - Livecrawl timeout in milliseconds
|
|
735
|
+
- `subpages` / `subpageTarget` - Crawl related subpages
|
|
736
|
+
- `extras.links` / `extras.imageLinks` - Extract links from pages
|
|
737
|
+
|
|
738
|
+
The tool works with both `generateText` and `streamText`.
|
|
739
|
+
|
|
740
|
+
This initial Gateway integration supports Exa's plain Search modes and
|
|
741
|
+
token-efficient content controls. Deep synthesis modes and generated summaries
|
|
742
|
+
are intentionally not exposed yet because they have separate pricing from
|
|
743
|
+
standard Search.
|
|
744
|
+
|
|
566
745
|
#### Parallel Search
|
|
567
746
|
|
|
568
747
|
The Parallel Search tool enables models to search the web using [Parallel AI's Search API](https://docs.parallel.ai/api-reference/search-beta/search). This tool is optimized for LLM consumption, returning relevant excerpts from web pages that can replace multiple keyword searches with a single call.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-sdk/gateway",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "4.0.0
|
|
4
|
+
"version": "4.0.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"sideEffects": false,
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@vercel/oidc": "3.2.0",
|
|
34
|
-
"@ai-sdk/provider": "4.0.0
|
|
35
|
-
"@ai-sdk/provider-utils": "5.0.0
|
|
34
|
+
"@ai-sdk/provider": "4.0.0",
|
|
35
|
+
"@ai-sdk/provider-utils": "5.0.0"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@types/node": "22.19.19",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"tsx": "4.19.2",
|
|
41
41
|
"typescript": "5.8.3",
|
|
42
42
|
"zod": "3.25.76",
|
|
43
|
-
"@ai-sdk/test-server": "2.0.0
|
|
43
|
+
"@ai-sdk/test-server": "2.0.0",
|
|
44
44
|
"@vercel/ai-tsconfig": "0.0.0"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
@@ -8,6 +8,8 @@ import {
|
|
|
8
8
|
modelNotFoundParamSchema,
|
|
9
9
|
} from './gateway-model-not-found-error';
|
|
10
10
|
import { GatewayInternalServerError } from './gateway-internal-server-error';
|
|
11
|
+
import { GatewayFailedDependencyError } from './gateway-failed-dependency-error';
|
|
12
|
+
import { GatewayForbiddenError } from './gateway-forbidden-error';
|
|
11
13
|
import { GatewayResponseError } from './gateway-response-error';
|
|
12
14
|
import {
|
|
13
15
|
lazySchema,
|
|
@@ -101,6 +103,20 @@ export async function createGatewayErrorFromResponse({
|
|
|
101
103
|
cause,
|
|
102
104
|
generationId,
|
|
103
105
|
});
|
|
106
|
+
case 'failed_dependency':
|
|
107
|
+
return new GatewayFailedDependencyError({
|
|
108
|
+
message,
|
|
109
|
+
statusCode,
|
|
110
|
+
cause,
|
|
111
|
+
generationId,
|
|
112
|
+
});
|
|
113
|
+
case 'forbidden':
|
|
114
|
+
return new GatewayForbiddenError({
|
|
115
|
+
message,
|
|
116
|
+
statusCode,
|
|
117
|
+
cause,
|
|
118
|
+
generationId,
|
|
119
|
+
});
|
|
104
120
|
default:
|
|
105
121
|
return new GatewayInternalServerError({
|
|
106
122
|
message,
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { GatewayError } from './gateway-error';
|
|
2
|
+
|
|
3
|
+
const name = 'GatewayFailedDependencyError';
|
|
4
|
+
const marker = `vercel.ai.gateway.error.${name}`;
|
|
5
|
+
const symbol = Symbol.for(marker);
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The request could not be fulfilled because a dependency it relied on was not
|
|
9
|
+
* available on the credentials or provider used to serve it (HTTP 424). Not
|
|
10
|
+
* retryable — the caller must change the request.
|
|
11
|
+
*/
|
|
12
|
+
export class GatewayFailedDependencyError extends GatewayError {
|
|
13
|
+
private readonly [symbol] = true; // used in isInstance
|
|
14
|
+
|
|
15
|
+
readonly name = name;
|
|
16
|
+
readonly type = 'failed_dependency';
|
|
17
|
+
|
|
18
|
+
constructor({
|
|
19
|
+
message = 'Failed dependency',
|
|
20
|
+
statusCode = 424,
|
|
21
|
+
cause,
|
|
22
|
+
generationId,
|
|
23
|
+
}: {
|
|
24
|
+
message?: string;
|
|
25
|
+
statusCode?: number;
|
|
26
|
+
cause?: unknown;
|
|
27
|
+
generationId?: string;
|
|
28
|
+
} = {}) {
|
|
29
|
+
super({ message, statusCode, cause, generationId });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
static isInstance(error: unknown): error is GatewayFailedDependencyError {
|
|
33
|
+
return GatewayError.hasMarker(error) && symbol in error;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { GatewayError } from './gateway-error';
|
|
2
|
+
|
|
3
|
+
const name = 'GatewayForbiddenError';
|
|
4
|
+
const marker = `vercel.ai.gateway.error.${name}`;
|
|
5
|
+
const symbol = Symbol.for(marker);
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Forbidden - the request was rejected by policy (e.g. a routing rule),
|
|
9
|
+
* not an authentication failure.
|
|
10
|
+
*/
|
|
11
|
+
export class GatewayForbiddenError extends GatewayError {
|
|
12
|
+
private readonly [symbol] = true; // used in isInstance
|
|
13
|
+
|
|
14
|
+
readonly name = name;
|
|
15
|
+
readonly type = 'forbidden';
|
|
16
|
+
|
|
17
|
+
constructor({
|
|
18
|
+
message = 'Forbidden',
|
|
19
|
+
statusCode = 403,
|
|
20
|
+
cause,
|
|
21
|
+
generationId,
|
|
22
|
+
}: {
|
|
23
|
+
message?: string;
|
|
24
|
+
statusCode?: number;
|
|
25
|
+
cause?: unknown;
|
|
26
|
+
generationId?: string;
|
|
27
|
+
} = {}) {
|
|
28
|
+
super({ message, statusCode, cause, generationId });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
static isInstance(error: unknown): error is GatewayForbiddenError {
|
|
32
|
+
return GatewayError.hasMarker(error) && symbol in error;
|
|
33
|
+
}
|
|
34
|
+
}
|
package/src/errors/index.ts
CHANGED
|
@@ -6,6 +6,8 @@ export {
|
|
|
6
6
|
export { extractApiCallResponse } from './extract-api-call-response';
|
|
7
7
|
export { GatewayError } from './gateway-error';
|
|
8
8
|
export { GatewayAuthenticationError } from './gateway-authentication-error';
|
|
9
|
+
export { GatewayFailedDependencyError } from './gateway-failed-dependency-error';
|
|
10
|
+
export { GatewayForbiddenError } from './gateway-forbidden-error';
|
|
9
11
|
export { GatewayInternalServerError } from './gateway-internal-server-error';
|
|
10
12
|
export { GatewayInvalidRequestError } from './gateway-invalid-request-error';
|
|
11
13
|
export {
|
|
@@ -98,7 +98,7 @@ export class GatewayEmbeddingModel implements EmbeddingModelV4 {
|
|
|
98
98
|
providerMetadata:
|
|
99
99
|
responseBody.providerMetadata as unknown as SharedV4ProviderMetadata,
|
|
100
100
|
response: { headers: responseHeaders, body: rawValue },
|
|
101
|
-
warnings: [],
|
|
101
|
+
warnings: responseBody.warnings ?? [],
|
|
102
102
|
};
|
|
103
103
|
} catch (error) {
|
|
104
104
|
throw await asGatewayError(
|
|
@@ -120,11 +120,34 @@ export class GatewayEmbeddingModel implements EmbeddingModelV4 {
|
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
+
const gatewayEmbeddingWarningSchema = z.discriminatedUnion('type', [
|
|
124
|
+
z.object({
|
|
125
|
+
type: z.literal('unsupported'),
|
|
126
|
+
feature: z.string(),
|
|
127
|
+
details: z.string().optional(),
|
|
128
|
+
}),
|
|
129
|
+
z.object({
|
|
130
|
+
type: z.literal('compatibility'),
|
|
131
|
+
feature: z.string(),
|
|
132
|
+
details: z.string().optional(),
|
|
133
|
+
}),
|
|
134
|
+
z.object({
|
|
135
|
+
type: z.literal('deprecated'),
|
|
136
|
+
setting: z.string(),
|
|
137
|
+
message: z.string(),
|
|
138
|
+
}),
|
|
139
|
+
z.object({
|
|
140
|
+
type: z.literal('other'),
|
|
141
|
+
message: z.string(),
|
|
142
|
+
}),
|
|
143
|
+
]);
|
|
144
|
+
|
|
123
145
|
const gatewayEmbeddingResponseSchema = lazySchema(() =>
|
|
124
146
|
zodSchema(
|
|
125
147
|
z.object({
|
|
126
148
|
embeddings: z.array(z.array(z.number())),
|
|
127
149
|
usage: z.object({ tokens: z.number() }).nullish(),
|
|
150
|
+
warnings: z.array(gatewayEmbeddingWarningSchema).optional(),
|
|
128
151
|
providerMetadata: z
|
|
129
152
|
.record(z.string(), z.record(z.string(), z.unknown()))
|
|
130
153
|
.optional(),
|
|
@@ -167,6 +167,11 @@ const gatewayImageWarningSchema = z.discriminatedUnion('type', [
|
|
|
167
167
|
feature: z.string(),
|
|
168
168
|
details: z.string().optional(),
|
|
169
169
|
}),
|
|
170
|
+
z.object({
|
|
171
|
+
type: z.literal('deprecated'),
|
|
172
|
+
setting: z.string(),
|
|
173
|
+
message: z.string(),
|
|
174
|
+
}),
|
|
170
175
|
z.object({
|
|
171
176
|
type: z.literal('other'),
|
|
172
177
|
message: z.string(),
|
|
@@ -53,8 +53,6 @@ export type GatewayModelId =
|
|
|
53
53
|
| 'deepseek/deepseek-v3.2-thinking'
|
|
54
54
|
| 'deepseek/deepseek-v4-flash'
|
|
55
55
|
| 'deepseek/deepseek-v4-pro'
|
|
56
|
-
| 'google/gemini-2.0-flash'
|
|
57
|
-
| 'google/gemini-2.0-flash-lite'
|
|
58
56
|
| 'google/gemini-2.5-flash'
|
|
59
57
|
| 'google/gemini-2.5-flash-image'
|
|
60
58
|
| 'google/gemini-2.5-flash-lite'
|
|
@@ -112,10 +110,10 @@ export type GatewayModelId =
|
|
|
112
110
|
| 'mistral/pixtral-large'
|
|
113
111
|
| 'moonshotai/kimi-k2'
|
|
114
112
|
| 'moonshotai/kimi-k2-thinking'
|
|
115
|
-
| 'moonshotai/kimi-k2-thinking-turbo'
|
|
116
|
-
| 'moonshotai/kimi-k2-turbo'
|
|
117
113
|
| 'moonshotai/kimi-k2.5'
|
|
118
114
|
| 'moonshotai/kimi-k2.6'
|
|
115
|
+
| 'moonshotai/kimi-k2.7-code'
|
|
116
|
+
| 'moonshotai/kimi-k2.7-code-highspeed'
|
|
119
117
|
| 'morph/morph-v3-fast'
|
|
120
118
|
| 'morph/morph-v3-large'
|
|
121
119
|
| 'nvidia/nemotron-3-nano-30b-a3b'
|
|
@@ -167,6 +165,7 @@ export type GatewayModelId =
|
|
|
167
165
|
| 'perplexity/sonar'
|
|
168
166
|
| 'perplexity/sonar-pro'
|
|
169
167
|
| 'perplexity/sonar-reasoning-pro'
|
|
168
|
+
| 'sakana/fugu-ultra'
|
|
170
169
|
| 'stepfun/step-3.5-flash'
|
|
171
170
|
| 'stepfun/step-3.7-flash'
|
|
172
171
|
| 'xai/grok-4.1-fast-non-reasoning'
|
|
@@ -195,5 +194,7 @@ export type GatewayModelId =
|
|
|
195
194
|
| 'zai/glm-5'
|
|
196
195
|
| 'zai/glm-5-turbo'
|
|
197
196
|
| 'zai/glm-5.1'
|
|
197
|
+
| 'zai/glm-5.2'
|
|
198
|
+
| 'zai/glm-5.2-fast'
|
|
198
199
|
| 'zai/glm-5v-turbo'
|
|
199
200
|
| (string & {});
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
LanguageModelV4,
|
|
3
3
|
LanguageModelV4CallOptions,
|
|
4
|
-
LanguageModelV4FilePart,
|
|
5
4
|
LanguageModelV4StreamPart,
|
|
6
5
|
LanguageModelV4GenerateResult,
|
|
7
6
|
LanguageModelV4StreamResult,
|
|
@@ -191,30 +190,27 @@ export class GatewayLanguageModel implements LanguageModelV4 {
|
|
|
191
190
|
}
|
|
192
191
|
}
|
|
193
192
|
|
|
194
|
-
private isFilePart(part: unknown) {
|
|
195
|
-
return (
|
|
196
|
-
part && typeof part === 'object' && 'type' in part && part.type === 'file'
|
|
197
|
-
);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
193
|
/**
|
|
201
|
-
* Encodes inline `Uint8Array` file
|
|
194
|
+
* Encodes inline `Uint8Array` file data to a base64 string in place.
|
|
202
195
|
* @param options - The options to encode.
|
|
203
|
-
* @returns The options with the file
|
|
196
|
+
* @returns The options with the file data encoded.
|
|
204
197
|
*/
|
|
205
198
|
private maybeEncodeFileParts(options: LanguageModelV4CallOptions) {
|
|
206
199
|
for (const message of options.prompt) {
|
|
200
|
+
if (!Array.isArray(message.content)) {
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
207
203
|
for (const part of message.content) {
|
|
208
|
-
if (
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
data
|
|
217
|
-
}
|
|
204
|
+
if (part.type === 'file' || part.type === 'reasoning-file') {
|
|
205
|
+
part.data = maybeBase64EncodeFileData(part.data);
|
|
206
|
+
} else if (
|
|
207
|
+
part.type === 'tool-result' &&
|
|
208
|
+
part.output.type === 'content'
|
|
209
|
+
) {
|
|
210
|
+
for (const contentPart of part.output.value) {
|
|
211
|
+
if (contentPart.type === 'file') {
|
|
212
|
+
contentPart.data = maybeBase64EncodeFileData(contentPart.data);
|
|
213
|
+
}
|
|
218
214
|
}
|
|
219
215
|
}
|
|
220
216
|
}
|
|
@@ -234,3 +230,13 @@ export class GatewayLanguageModel implements LanguageModelV4 {
|
|
|
234
230
|
};
|
|
235
231
|
}
|
|
236
232
|
}
|
|
233
|
+
|
|
234
|
+
function maybeBase64EncodeFileData<T extends { type: string }>(data: T): T {
|
|
235
|
+
if (data.type === 'data') {
|
|
236
|
+
const bytes = (data as { data?: unknown }).data;
|
|
237
|
+
if (bytes instanceof Uint8Array) {
|
|
238
|
+
return { ...data, data: Buffer.from(bytes).toString('base64') } as T;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return data;
|
|
242
|
+
}
|
|
@@ -1,118 +1,52 @@
|
|
|
1
|
-
import {
|
|
2
|
-
lazySchema,
|
|
3
|
-
zodSchema,
|
|
4
|
-
type InferSchema,
|
|
5
|
-
} from '@ai-sdk/provider-utils';
|
|
6
|
-
import { z } from 'zod/v4';
|
|
7
|
-
|
|
8
1
|
// https://vercel.com/docs/ai-gateway/provider-options
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
*
|
|
57
|
-
* Each provider can have multiple credentials (tried in order).
|
|
58
|
-
*
|
|
59
|
-
* Examples:
|
|
60
|
-
* - Simple: `{ 'anthropic': [{ apiKey: 'sk-ant-...' }] }`
|
|
61
|
-
* - Multiple: `{ 'vertex': [{ projectId: 'proj-1', privateKey: '...' }, { projectId: 'proj-2', privateKey: '...' }] }`
|
|
62
|
-
* - Multi-provider: `{ 'anthropic': [{ apiKey: '...' }], 'bedrock': [{ accessKeyId: '...', secretAccessKey: '...' }] }`
|
|
63
|
-
*/
|
|
64
|
-
byok: z
|
|
65
|
-
.record(z.string(), z.array(z.record(z.string(), z.unknown())))
|
|
66
|
-
.optional(),
|
|
67
|
-
/**
|
|
68
|
-
* Whether to filter by only providers that have zero data retention
|
|
69
|
-
* agreements with Vercel for AI Gateway. When using BYOK credentials,
|
|
70
|
-
* this filter is not applied. If BYOK credentials fail and the request
|
|
71
|
-
* falls back to system credentials, only providers with zero data
|
|
72
|
-
* retention agreements will be used.
|
|
73
|
-
*/
|
|
74
|
-
zeroDataRetention: z.boolean().optional(),
|
|
75
|
-
/**
|
|
76
|
-
* Whether to filter by only providers that do not train on prompt data.
|
|
77
|
-
* When using BYOK credentials, this filter is not applied. If BYOK
|
|
78
|
-
* credentials fail and the request falls back to system credentials,
|
|
79
|
-
* only providers that have agreements with Vercel for AI Gateway to not
|
|
80
|
-
* use prompts for model training will be used.
|
|
81
|
-
*/
|
|
82
|
-
disallowPromptTraining: z.boolean().optional(),
|
|
83
|
-
/**
|
|
84
|
-
* Whether to filter by only providers that are HIPAA compliant with
|
|
85
|
-
* Vercel AI Gateway. When enabled, only providers that have agreements
|
|
86
|
-
* with Vercel AI Gateway for HIPAA compliance will be used.
|
|
87
|
-
*/
|
|
88
|
-
hipaaCompliant: z.boolean().optional(),
|
|
89
|
-
/**
|
|
90
|
-
* The unique identifier for the entity against which quota is tracked.
|
|
91
|
-
*
|
|
92
|
-
* Used for quota management and enforcement purposes.
|
|
93
|
-
*/
|
|
94
|
-
quotaEntityId: z.string().optional(),
|
|
95
|
-
/**
|
|
96
|
-
* Per-provider timeouts for BYOK credentials in milliseconds.
|
|
97
|
-
* Controls how long to wait for a provider to start responding
|
|
98
|
-
* before falling back to the next available provider.
|
|
99
|
-
*
|
|
100
|
-
* Example: `{ byok: { openai: 5000, anthropic: 2000 } }`
|
|
101
|
-
*/
|
|
102
|
-
providerTimeouts: z
|
|
103
|
-
.object({
|
|
104
|
-
byok: z.record(z.string(), z.number().int().min(1000)).optional(),
|
|
105
|
-
})
|
|
106
|
-
.optional(),
|
|
107
|
-
/**
|
|
108
|
-
* Unified service tier intent. Translated by the gateway into the
|
|
109
|
-
* per-provider option each provider expects, and overrides any tier
|
|
110
|
-
* also set in the per-provider options. Leave unset for provider
|
|
111
|
-
* default.
|
|
112
|
-
*/
|
|
113
|
-
serviceTier: z.enum(['flex', 'priority']).optional(),
|
|
114
|
-
}),
|
|
115
|
-
),
|
|
116
|
-
);
|
|
2
|
+
export type GatewayProviderOptions = {
|
|
3
|
+
/**
|
|
4
|
+
* Service-owned options may be added by the Gateway without requiring an SDK
|
|
5
|
+
* release. The Gateway service validates and applies the runtime schema.
|
|
6
|
+
*/
|
|
7
|
+
[key: string]: unknown;
|
|
8
|
+
|
|
9
|
+
/** Request-scoped BYOK credentials to use instead of cached credentials. */
|
|
10
|
+
byok?: Record<string, Array<Record<string, unknown>>>;
|
|
11
|
+
|
|
12
|
+
/** Enables automatic caching behavior when supported by the Gateway. */
|
|
13
|
+
caching?: 'auto';
|
|
14
|
+
|
|
15
|
+
/** Filter to providers that do not train on prompt data. */
|
|
16
|
+
disallowPromptTraining?: boolean;
|
|
17
|
+
|
|
18
|
+
/** Filter to providers that are HIPAA compliant with Vercel AI Gateway. */
|
|
19
|
+
hipaaCompliant?: boolean;
|
|
20
|
+
|
|
21
|
+
/** Array of model slugs specifying fallback models to use in order. */
|
|
22
|
+
models?: string[];
|
|
23
|
+
|
|
24
|
+
/** Array of provider slugs that are the only ones allowed to be used. */
|
|
25
|
+
only?: string[];
|
|
26
|
+
|
|
27
|
+
/** Array of provider slugs specifying the provider attempt order. */
|
|
28
|
+
order?: string[];
|
|
29
|
+
|
|
30
|
+
/** Per-provider timeouts for BYOK credentials in milliseconds. */
|
|
31
|
+
providerTimeouts?: {
|
|
32
|
+
byok?: Record<string, number>;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/** Entity identifier against which quota is tracked. */
|
|
36
|
+
quotaEntityId?: string;
|
|
37
|
+
|
|
38
|
+
/** Unified service tier intent. */
|
|
39
|
+
serviceTier?: 'flex' | 'priority';
|
|
40
|
+
|
|
41
|
+
/** Sort providers by a performance or cost metric before routing. */
|
|
42
|
+
sort?: 'cost' | 'tps' | 'ttft';
|
|
43
|
+
|
|
44
|
+
/** User-specified tags for reporting and filtering usage. */
|
|
45
|
+
tags?: string[];
|
|
46
|
+
|
|
47
|
+
/** End-user identifier for spend tracking and attribution. */
|
|
48
|
+
user?: string;
|
|
117
49
|
|
|
118
|
-
|
|
50
|
+
/** Filter to providers with zero data retention agreements. */
|
|
51
|
+
zeroDataRetention?: boolean;
|
|
52
|
+
};
|