@ai-sdk/gateway 4.0.0-beta.11 → 4.0.0-beta.110

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 (46) hide show
  1. package/CHANGELOG.md +756 -4
  2. package/dist/index.d.ts +314 -42
  3. package/dist/index.js +1413 -397
  4. package/dist/index.js.map +1 -1
  5. package/docs/00-ai-gateway.mdx +447 -61
  6. package/package.json +14 -14
  7. package/src/errors/as-gateway-error.ts +2 -1
  8. package/src/errors/create-gateway-error.ts +17 -3
  9. package/src/errors/gateway-authentication-error.ts +8 -5
  10. package/src/errors/gateway-error.ts +8 -0
  11. package/src/errors/gateway-failed-dependency-error.ts +35 -0
  12. package/src/errors/gateway-forbidden-error.ts +34 -0
  13. package/src/errors/gateway-response-error.ts +1 -1
  14. package/src/errors/index.ts +2 -0
  15. package/src/errors/parse-auth-method.ts +1 -2
  16. package/src/gateway-config.ts +1 -1
  17. package/src/gateway-embedding-model-settings.ts +1 -0
  18. package/src/gateway-embedding-model.ts +62 -15
  19. package/src/gateway-fetch-metadata.ts +51 -38
  20. package/src/gateway-generation-info.ts +149 -0
  21. package/src/gateway-headers.ts +3 -0
  22. package/src/gateway-image-model-settings.ts +8 -1
  23. package/src/gateway-image-model.ts +46 -21
  24. package/src/gateway-language-model-settings.ts +45 -26
  25. package/src/gateway-language-model.ts +72 -42
  26. package/src/gateway-model-entry.ts +15 -3
  27. package/src/gateway-provider-options.ts +50 -78
  28. package/src/gateway-provider.ts +296 -35
  29. package/src/gateway-realtime-auth.ts +126 -0
  30. package/src/gateway-realtime-model-settings.ts +1 -0
  31. package/src/gateway-realtime-model.ts +118 -0
  32. package/src/gateway-reranking-model-settings.ts +7 -0
  33. package/src/gateway-reranking-model.ts +142 -0
  34. package/src/gateway-speech-model-settings.ts +1 -0
  35. package/src/gateway-speech-model.ts +145 -0
  36. package/src/gateway-spend-report.ts +193 -0
  37. package/src/gateway-transcription-model-settings.ts +1 -0
  38. package/src/gateway-transcription-model.ts +155 -0
  39. package/src/gateway-video-model-settings.ts +4 -0
  40. package/src/gateway-video-model.ts +29 -19
  41. package/src/index.ts +30 -5
  42. package/src/tool/parallel-search.ts +10 -11
  43. package/src/tool/perplexity-search.ts +10 -11
  44. package/dist/index.d.mts +0 -602
  45. package/dist/index.mjs +0 -1539
  46. package/dist/index.mjs.map +0 -1
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Shared WebSocket subprotocol contract for AI Gateway realtime auth.
3
+ *
4
+ * The browser `WebSocket` API cannot set request headers, so the Gateway auth
5
+ * (bearer) token is carried through the `Sec-WebSocket-Protocol` handshake
6
+ * instead of an `Authorization` header — the same workaround OpenAI uses for
7
+ * `openai-insecure-api-key.<token>`.
8
+ *
9
+ * This module is the single source of truth for that contract so the client and
10
+ * the Gateway server can't drift: the client encodes values with
11
+ * `getGatewayRealtimeProtocols`, and the Gateway server decodes them with
12
+ * `getGatewayRealtimeAuthToken` / `getGatewayRealtimeTeamIdOrSlug`.
13
+ *
14
+ * WebSocket subprotocol values must fit the RFC token grammar. The auth token is
15
+ * sent as-is, so callers must use tokens that are valid subprotocol tokens; the
16
+ * optional team scope is base64url-encoded by this module. Keep the complete
17
+ * `Sec-WebSocket-Protocol` header compact (target under an 8 KiB safe header
18
+ * budget) because intermediaries may reject large upgrade headers.
19
+ */
20
+
21
+ /**
22
+ * Marker subprotocol offered on every handshake so the Gateway can echo a
23
+ * negotiated subprotocol on the 101 response (some clients require the server to
24
+ * select one of the offered subprotocols).
25
+ */
26
+ export const GATEWAY_REALTIME_SUBPROTOCOL = 'ai-gateway-realtime.v1';
27
+
28
+ /** Subprotocol prefix that carries the Gateway auth (bearer) token. */
29
+ export const GATEWAY_AUTH_SUBPROTOCOL_PREFIX = 'ai-gateway-auth.';
30
+
31
+ /** Subprotocol prefix that carries optional Vercel team scoping. */
32
+ export const GATEWAY_TEAM_SUBPROTOCOL_PREFIX = 'ai-gateway-team.';
33
+
34
+ /**
35
+ * Client-side: build the WebSocket subprotocols that carry `token` to the
36
+ * Gateway. Pass the result as the second argument to `new WebSocket(url, ...)`.
37
+ */
38
+ export function getGatewayRealtimeProtocols(
39
+ token: string,
40
+ options?: { teamIdOrSlug?: string },
41
+ ): string[] {
42
+ const protocols = [
43
+ GATEWAY_REALTIME_SUBPROTOCOL,
44
+ `${GATEWAY_AUTH_SUBPROTOCOL_PREFIX}${token}`,
45
+ ];
46
+
47
+ if (options?.teamIdOrSlug) {
48
+ protocols.push(
49
+ `${GATEWAY_TEAM_SUBPROTOCOL_PREFIX}${encodeSubprotocolValue(options.teamIdOrSlug)}`,
50
+ );
51
+ }
52
+
53
+ return protocols;
54
+ }
55
+
56
+ /**
57
+ * Server-side: extract the Gateway auth (bearer) token from a
58
+ * `Sec-WebSocket-Protocol` header value, or `undefined` when it is absent or
59
+ * empty. The Gateway upgrade handler turns this into an
60
+ * `Authorization: Bearer <token>` before its normal auth path.
61
+ *
62
+ * Accepts the raw header value (subprotocols are comma-separated and may carry
63
+ * surrounding whitespace).
64
+ */
65
+ export function getGatewayRealtimeAuthToken(
66
+ secWebSocketProtocol: string | null | undefined,
67
+ ): string | undefined {
68
+ // `findProtocol` trims the protocol, so the sliced token needs no further
69
+ // trimming; `|| undefined` collapses the empty-token case.
70
+ return (
71
+ findProtocol(secWebSocketProtocol, GATEWAY_AUTH_SUBPROTOCOL_PREFIX)?.slice(
72
+ GATEWAY_AUTH_SUBPROTOCOL_PREFIX.length,
73
+ ) || undefined
74
+ );
75
+ }
76
+
77
+ /**
78
+ * Server-side: extract the optional Vercel team ID or slug from the
79
+ * `Sec-WebSocket-Protocol` header value. Team scoping is base64url-encoded so
80
+ * arbitrary team slugs stay within the WebSocket subprotocol token grammar.
81
+ */
82
+ export function getGatewayRealtimeTeamIdOrSlug(
83
+ secWebSocketProtocol: string | null | undefined,
84
+ ): string | undefined {
85
+ const encoded = findProtocol(
86
+ secWebSocketProtocol,
87
+ GATEWAY_TEAM_SUBPROTOCOL_PREFIX,
88
+ )?.slice(GATEWAY_TEAM_SUBPROTOCOL_PREFIX.length);
89
+ if (!encoded) return undefined;
90
+
91
+ try {
92
+ return decodeSubprotocolValue(encoded) || undefined;
93
+ } catch {
94
+ return undefined;
95
+ }
96
+ }
97
+
98
+ function findProtocol(
99
+ secWebSocketProtocol: string | null | undefined,
100
+ prefix: string,
101
+ ): string | undefined {
102
+ return secWebSocketProtocol
103
+ ?.split(',')
104
+ .map(protocol => protocol.trim())
105
+ .find(protocol => protocol.startsWith(prefix));
106
+ }
107
+
108
+ function encodeSubprotocolValue(value: string): string {
109
+ const bytes = new TextEncoder().encode(value);
110
+ let binary = '';
111
+ for (const byte of bytes) {
112
+ binary += String.fromCharCode(byte);
113
+ }
114
+ return btoa(binary)
115
+ .replace(/\+/g, '-')
116
+ .replace(/\//g, '_')
117
+ .replace(/=+$/u, '');
118
+ }
119
+
120
+ function decodeSubprotocolValue(value: string): string {
121
+ const base64 = value.replace(/-/g, '+').replace(/_/g, '/');
122
+ const padding = '='.repeat((4 - (base64.length % 4)) % 4);
123
+ const binary = atob(`${base64}${padding}`);
124
+ const bytes = Uint8Array.from(binary, char => char.charCodeAt(0));
125
+ return new TextDecoder().decode(bytes);
126
+ }
@@ -0,0 +1 @@
1
+ export type GatewayRealtimeModelId = string & {};
@@ -0,0 +1,118 @@
1
+ import type {
2
+ Experimental_RealtimeModelV4 as RealtimeModelV4,
3
+ Experimental_RealtimeModelV4ClientEvent as RealtimeModelV4ClientEvent,
4
+ Experimental_RealtimeModelV4ClientSecretOptions as RealtimeModelV4ClientSecretOptions,
5
+ Experimental_RealtimeModelV4ClientSecretResult as RealtimeModelV4ClientSecretResult,
6
+ Experimental_RealtimeModelV4ServerEvent as RealtimeModelV4ServerEvent,
7
+ Experimental_RealtimeModelV4SessionConfig as RealtimeModelV4SessionConfig,
8
+ } from '@ai-sdk/provider';
9
+ import { getGatewayRealtimeProtocols } from './gateway-realtime-auth';
10
+
11
+ export type GatewayRealtimeModelConfig = {
12
+ provider: string;
13
+ baseURL: string;
14
+ teamIdOrSlug?: string;
15
+ /**
16
+ * Mints a short-lived client secret (`vcst_`) for this model via the
17
+ * Gateway's `/v1/realtime/client-secrets` endpoint. Implemented by the
18
+ * provider because minting requires the long-lived Gateway credential
19
+ * (API key / OIDC) and must run server-side.
20
+ */
21
+ createClientSecret: (params: {
22
+ modelId: string;
23
+ expiresAfterSeconds?: number;
24
+ }) => PromiseLike<{ token: string; expiresAt?: number }>;
25
+ };
26
+
27
+ /**
28
+ * Realtime model backed by the AI Gateway.
29
+ *
30
+ * The Gateway normalizes realtime exactly like it normalizes every other
31
+ * modality: the client speaks the normalized AI SDK realtime protocol and the
32
+ * Gateway translates to and from the upstream provider server-side. This model
33
+ * is therefore a thin identity codec over that normalized protocol — only the
34
+ * connection and authentication are Gateway-specific.
35
+ */
36
+ export class GatewayRealtimeModel implements RealtimeModelV4 {
37
+ readonly specificationVersion = 'v4' as const;
38
+ readonly provider: string;
39
+ readonly modelId: string;
40
+
41
+ private readonly config: GatewayRealtimeModelConfig;
42
+
43
+ constructor(modelId: string, config: GatewayRealtimeModelConfig) {
44
+ this.modelId = modelId;
45
+ this.provider = config.provider;
46
+ this.config = config;
47
+ }
48
+
49
+ /**
50
+ * Mints a single-use, short-lived client secret (`vcst_`) the browser uses to
51
+ * open the realtime WebSocket without ever holding the long-lived Gateway
52
+ * credential. The customer's server calls this (via
53
+ * `gateway.experimental_realtime.getToken`) and hands the returned token to
54
+ * the browser, which connects with it through the `ai-gateway-auth.<token>`
55
+ * subprotocol. `expiresAfterSeconds` is forwarded to the mint endpoint;
56
+ * `sessionConfig` is intentionally unused here — it is applied later via the
57
+ * normalized `session-update` event.
58
+ */
59
+ async doCreateClientSecret(
60
+ options?: RealtimeModelV4ClientSecretOptions,
61
+ ): Promise<RealtimeModelV4ClientSecretResult> {
62
+ const secret = await this.config.createClientSecret({
63
+ modelId: this.modelId,
64
+ ...(options?.expiresAfterSeconds != null && {
65
+ expiresAfterSeconds: options.expiresAfterSeconds,
66
+ }),
67
+ });
68
+ return {
69
+ token: secret.token,
70
+ url: toGatewayRealtimeUrl(this.config.baseURL, this.modelId),
71
+ ...(secret.expiresAt != null && { expiresAt: secret.expiresAt }),
72
+ };
73
+ }
74
+
75
+ getWebSocketConfig(options: { token: string; url: string }): {
76
+ url: string;
77
+ protocols?: string[];
78
+ } {
79
+ return {
80
+ url: options.url,
81
+ protocols: getGatewayRealtimeProtocols(options.token, {
82
+ teamIdOrSlug: this.config.teamIdOrSlug,
83
+ }),
84
+ };
85
+ }
86
+
87
+ parseServerEvent(raw: unknown): RealtimeModelV4ServerEvent {
88
+ // The Gateway emits normalized AI SDK realtime events, so no
89
+ // provider-specific mapping is needed on the client.
90
+ return raw as RealtimeModelV4ServerEvent;
91
+ }
92
+
93
+ serializeClientEvent(event: RealtimeModelV4ClientEvent): unknown {
94
+ // The Gateway accepts normalized AI SDK realtime events directly.
95
+ return event;
96
+ }
97
+
98
+ buildSessionConfig(config: RealtimeModelV4SessionConfig): unknown {
99
+ // The session config is already normalized; the Gateway maps it to the
100
+ // upstream provider's session payload server-side.
101
+ return config;
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Build the Gateway realtime WebSocket URL. The HTTP(S) base URL is upgraded to
107
+ * WS(S) and the model id rides the `?ai-model-id=` query — the WS transport of
108
+ * the `ai-model-id` header the HTTP routes use, since a browser `WebSocket`
109
+ * cannot set headers. The model id is passed through verbatim; the Gateway owns
110
+ * resolution (including the bare → `openai/` qualification), exactly like the
111
+ * non-realtime routes. The query is slash-safe for qualified ids such as
112
+ * `openai/gpt-realtime-2`.
113
+ */
114
+ function toGatewayRealtimeUrl(baseURL: string, modelId: string): string {
115
+ const url = new URL(`${baseURL.replace(/^http/, 'ws')}/realtime-model`);
116
+ url.searchParams.set('ai-model-id', modelId);
117
+ return url.toString();
118
+ }
@@ -0,0 +1,7 @@
1
+ export type GatewayRerankingModelId =
2
+ | 'cohere/rerank-v3.5'
3
+ | 'cohere/rerank-v4-fast'
4
+ | 'cohere/rerank-v4-pro'
5
+ | 'voyage/rerank-2.5'
6
+ | 'voyage/rerank-2.5-lite'
7
+ | (string & {});
@@ -0,0 +1,142 @@
1
+ import type {
2
+ RerankingModelV4,
3
+ SharedV4ProviderMetadata,
4
+ } from '@ai-sdk/provider';
5
+ import {
6
+ combineHeaders,
7
+ createJsonErrorResponseHandler,
8
+ createJsonResponseHandler,
9
+ lazySchema,
10
+ postJsonToApi,
11
+ resolve,
12
+ zodSchema,
13
+ type Resolvable,
14
+ } from '@ai-sdk/provider-utils';
15
+ import { z } from 'zod/v4';
16
+ import { asGatewayError } from './errors';
17
+ import { parseAuthMethod } from './errors/parse-auth-method';
18
+ import type { GatewayConfig } from './gateway-config';
19
+
20
+ export class GatewayRerankingModel implements RerankingModelV4 {
21
+ readonly specificationVersion = 'v4';
22
+
23
+ constructor(
24
+ readonly modelId: string,
25
+ private readonly config: GatewayConfig & {
26
+ provider: string;
27
+ o11yHeaders: Resolvable<Record<string, string>>;
28
+ },
29
+ ) {}
30
+
31
+ get provider(): string {
32
+ return this.config.provider;
33
+ }
34
+
35
+ async doRerank({
36
+ documents,
37
+ query,
38
+ topN,
39
+ headers,
40
+ abortSignal,
41
+ providerOptions,
42
+ }: Parameters<RerankingModelV4['doRerank']>[0]): Promise<
43
+ Awaited<ReturnType<RerankingModelV4['doRerank']>>
44
+ > {
45
+ const resolvedHeaders = this.config.headers
46
+ ? await resolve(this.config.headers)
47
+ : undefined;
48
+ try {
49
+ const {
50
+ responseHeaders,
51
+ value: responseBody,
52
+ rawValue,
53
+ } = await postJsonToApi({
54
+ url: this.getUrl(),
55
+ headers: combineHeaders(
56
+ resolvedHeaders,
57
+ headers ?? {},
58
+ this.getModelConfigHeaders(),
59
+ await resolve(this.config.o11yHeaders),
60
+ ),
61
+ body: {
62
+ documents,
63
+ query,
64
+ ...(topN != null ? { topN } : {}),
65
+ ...(providerOptions ? { providerOptions } : {}),
66
+ },
67
+ successfulResponseHandler: createJsonResponseHandler(
68
+ gatewayRerankingResponseSchema,
69
+ ),
70
+ failedResponseHandler: createJsonErrorResponseHandler({
71
+ errorSchema: z.any(),
72
+ errorToMessage: data => data,
73
+ }),
74
+ ...(abortSignal && { abortSignal }),
75
+ fetch: this.config.fetch,
76
+ });
77
+
78
+ return {
79
+ ranking: responseBody.ranking,
80
+ providerMetadata:
81
+ responseBody.providerMetadata as unknown as SharedV4ProviderMetadata,
82
+ response: { headers: responseHeaders, body: rawValue },
83
+ warnings: responseBody.warnings ?? [],
84
+ };
85
+ } catch (error) {
86
+ throw await asGatewayError(
87
+ error,
88
+ await parseAuthMethod(resolvedHeaders ?? {}),
89
+ );
90
+ }
91
+ }
92
+
93
+ private getUrl() {
94
+ return `${this.config.baseURL}/reranking-model`;
95
+ }
96
+
97
+ private getModelConfigHeaders() {
98
+ return {
99
+ 'ai-reranking-model-specification-version': '4',
100
+ 'ai-model-id': this.modelId,
101
+ };
102
+ }
103
+ }
104
+
105
+ const gatewayRerankingWarningSchema = z.discriminatedUnion('type', [
106
+ z.object({
107
+ type: z.literal('unsupported'),
108
+ feature: z.string(),
109
+ details: z.string().optional(),
110
+ }),
111
+ z.object({
112
+ type: z.literal('compatibility'),
113
+ feature: z.string(),
114
+ details: z.string().optional(),
115
+ }),
116
+ z.object({
117
+ type: z.literal('deprecated'),
118
+ setting: z.string(),
119
+ message: z.string(),
120
+ }),
121
+ z.object({
122
+ type: z.literal('other'),
123
+ message: z.string(),
124
+ }),
125
+ ]);
126
+
127
+ const gatewayRerankingResponseSchema = lazySchema(() =>
128
+ zodSchema(
129
+ z.object({
130
+ ranking: z.array(
131
+ z.object({
132
+ index: z.number(),
133
+ relevanceScore: z.number(),
134
+ }),
135
+ ),
136
+ warnings: z.array(gatewayRerankingWarningSchema).optional(),
137
+ providerMetadata: z
138
+ .record(z.string(), z.record(z.string(), z.unknown()))
139
+ .optional(),
140
+ }),
141
+ ),
142
+ );
@@ -0,0 +1 @@
1
+ export type GatewaySpeechModelId = string & {};
@@ -0,0 +1,145 @@
1
+ import type {
2
+ SharedV4ProviderMetadata,
3
+ SharedV4Warning,
4
+ SpeechModelV4,
5
+ } from '@ai-sdk/provider';
6
+ import {
7
+ combineHeaders,
8
+ createJsonErrorResponseHandler,
9
+ createJsonResponseHandler,
10
+ postJsonToApi,
11
+ resolve,
12
+ type Resolvable,
13
+ } from '@ai-sdk/provider-utils';
14
+ import { z } from 'zod/v4';
15
+ import { asGatewayError } from './errors';
16
+ import { parseAuthMethod } from './errors/parse-auth-method';
17
+ import type { GatewayConfig } from './gateway-config';
18
+
19
+ export class GatewaySpeechModel implements SpeechModelV4 {
20
+ readonly specificationVersion = 'v4' as const;
21
+
22
+ constructor(
23
+ readonly modelId: string,
24
+ private readonly config: GatewayConfig & {
25
+ provider: string;
26
+ o11yHeaders: Resolvable<Record<string, string>>;
27
+ },
28
+ ) {}
29
+
30
+ get provider(): string {
31
+ return this.config.provider;
32
+ }
33
+
34
+ async doGenerate({
35
+ text,
36
+ voice,
37
+ outputFormat,
38
+ instructions,
39
+ speed,
40
+ language,
41
+ providerOptions,
42
+ headers,
43
+ abortSignal,
44
+ }: Parameters<SpeechModelV4['doGenerate']>[0]): Promise<
45
+ Awaited<ReturnType<SpeechModelV4['doGenerate']>>
46
+ > {
47
+ const resolvedHeaders = this.config.headers
48
+ ? await resolve(this.config.headers)
49
+ : undefined;
50
+ try {
51
+ const {
52
+ responseHeaders,
53
+ value: responseBody,
54
+ rawValue,
55
+ } = await postJsonToApi({
56
+ url: this.getUrl(),
57
+ headers: combineHeaders(
58
+ resolvedHeaders,
59
+ headers ?? {},
60
+ this.getModelConfigHeaders(),
61
+ await resolve(this.config.o11yHeaders),
62
+ ),
63
+ body: {
64
+ text,
65
+ ...(voice && { voice }),
66
+ ...(outputFormat && { outputFormat }),
67
+ ...(instructions && { instructions }),
68
+ ...(speed != null && { speed }),
69
+ ...(language && { language }),
70
+ ...(providerOptions && { providerOptions }),
71
+ },
72
+ successfulResponseHandler: createJsonResponseHandler(
73
+ gatewaySpeechResponseSchema,
74
+ ),
75
+ failedResponseHandler: createJsonErrorResponseHandler({
76
+ errorSchema: z.any(),
77
+ errorToMessage: data => data,
78
+ }),
79
+ ...(abortSignal && { abortSignal }),
80
+ fetch: this.config.fetch,
81
+ });
82
+
83
+ return {
84
+ audio: responseBody.audio,
85
+ warnings: (responseBody.warnings ?? []) as Array<SharedV4Warning>,
86
+ providerMetadata:
87
+ responseBody.providerMetadata as SharedV4ProviderMetadata,
88
+ response: {
89
+ timestamp: new Date(),
90
+ modelId: this.modelId,
91
+ headers: responseHeaders,
92
+ body: rawValue,
93
+ },
94
+ };
95
+ } catch (error) {
96
+ throw await asGatewayError(
97
+ error,
98
+ await parseAuthMethod(resolvedHeaders ?? {}),
99
+ );
100
+ }
101
+ }
102
+
103
+ private getUrl() {
104
+ return `${this.config.baseURL}/speech-model`;
105
+ }
106
+
107
+ private getModelConfigHeaders() {
108
+ return {
109
+ 'ai-speech-model-specification-version': '4',
110
+ 'ai-model-id': this.modelId,
111
+ };
112
+ }
113
+ }
114
+
115
+ const providerMetadataEntrySchema = z.object({}).catchall(z.unknown());
116
+
117
+ const gatewaySpeechWarningSchema = z.discriminatedUnion('type', [
118
+ z.object({
119
+ type: z.literal('unsupported'),
120
+ feature: z.string(),
121
+ details: z.string().optional(),
122
+ }),
123
+ z.object({
124
+ type: z.literal('compatibility'),
125
+ feature: z.string(),
126
+ details: z.string().optional(),
127
+ }),
128
+ z.object({
129
+ type: z.literal('deprecated'),
130
+ setting: z.string(),
131
+ message: z.string(),
132
+ }),
133
+ z.object({
134
+ type: z.literal('other'),
135
+ message: z.string(),
136
+ }),
137
+ ]);
138
+
139
+ const gatewaySpeechResponseSchema = z.object({
140
+ audio: z.string(),
141
+ warnings: z.array(gatewaySpeechWarningSchema).optional(),
142
+ providerMetadata: z
143
+ .record(z.string(), providerMetadataEntrySchema)
144
+ .optional(),
145
+ });