@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/src/gateway-provider.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
|
+
createJsonErrorResponseHandler,
|
|
3
|
+
createJsonResponseHandler,
|
|
2
4
|
loadOptionalSetting,
|
|
5
|
+
postJsonToApi,
|
|
3
6
|
withoutTrailingSlash,
|
|
4
7
|
withUserAgentSuffix,
|
|
5
8
|
type FetchFunction,
|
|
6
9
|
} from '@ai-sdk/provider-utils';
|
|
10
|
+
import { z } from 'zod/v4';
|
|
7
11
|
import { asGatewayError, GatewayAuthenticationError } from './errors';
|
|
8
12
|
import {
|
|
9
13
|
GATEWAY_AUTH_METHOD_HEADER,
|
|
@@ -32,11 +36,13 @@ import { GatewayVideoModel } from './gateway-video-model';
|
|
|
32
36
|
import { GatewayRerankingModel } from './gateway-reranking-model';
|
|
33
37
|
import { GatewaySpeechModel } from './gateway-speech-model';
|
|
34
38
|
import { GatewayTranscriptionModel } from './gateway-transcription-model';
|
|
39
|
+
import { GatewayRealtimeModel } from './gateway-realtime-model';
|
|
35
40
|
import type { GatewayEmbeddingModelId } from './gateway-embedding-model-settings';
|
|
36
41
|
import type { GatewayImageModelId } from './gateway-image-model-settings';
|
|
37
42
|
import type { GatewayRerankingModelId } from './gateway-reranking-model-settings';
|
|
38
43
|
import type { GatewaySpeechModelId } from './gateway-speech-model-settings';
|
|
39
44
|
import type { GatewayTranscriptionModelId } from './gateway-transcription-model-settings';
|
|
45
|
+
import type { GatewayRealtimeModelId } from './gateway-realtime-model-settings';
|
|
40
46
|
import type { GatewayVideoModelId } from './gateway-video-model-settings';
|
|
41
47
|
import { gatewayTools } from './gateway-tools';
|
|
42
48
|
import { getVercelOidcToken, getVercelRequestId } from './vercel-environment';
|
|
@@ -49,6 +55,8 @@ import type {
|
|
|
49
55
|
SpeechModelV4,
|
|
50
56
|
TranscriptionModelV4,
|
|
51
57
|
Experimental_VideoModelV4,
|
|
58
|
+
Experimental_RealtimeFactoryV4 as RealtimeFactoryV4,
|
|
59
|
+
Experimental_RealtimeFactoryV4GetTokenOptions as RealtimeFactoryV4GetTokenOptions,
|
|
52
60
|
ProviderV4,
|
|
53
61
|
} from '@ai-sdk/provider';
|
|
54
62
|
import { VERSION } from './version';
|
|
@@ -159,6 +167,12 @@ export interface GatewayProvider extends ProviderV4 {
|
|
|
159
167
|
modelId: GatewayTranscriptionModelId,
|
|
160
168
|
): TranscriptionModelV4;
|
|
161
169
|
|
|
170
|
+
/**
|
|
171
|
+
* Creates an experimental realtime model for bidirectional audio/text
|
|
172
|
+
* communication over WebSocket, normalized through the AI Gateway.
|
|
173
|
+
*/
|
|
174
|
+
experimental_realtime: RealtimeFactoryV4;
|
|
175
|
+
|
|
162
176
|
/**
|
|
163
177
|
* Gateway-specific tools executed server-side.
|
|
164
178
|
*/
|
|
@@ -209,6 +223,12 @@ export interface GatewayProviderSettings {
|
|
|
209
223
|
|
|
210
224
|
const AI_GATEWAY_PROTOCOL_VERSION = '0.0.1';
|
|
211
225
|
|
|
226
|
+
/** Response shape of `POST /v1/realtime/client-secrets`. `expiresAt` is epoch seconds. */
|
|
227
|
+
const gatewayClientSecretResponseSchema = z.object({
|
|
228
|
+
token: z.string(),
|
|
229
|
+
expiresAt: z.number().nullish(),
|
|
230
|
+
});
|
|
231
|
+
|
|
212
232
|
/**
|
|
213
233
|
* Create a remote provider instance.
|
|
214
234
|
*/
|
|
@@ -255,6 +275,62 @@ export function createGateway(
|
|
|
255
275
|
}
|
|
256
276
|
};
|
|
257
277
|
|
|
278
|
+
const getRealtimeAuthToken = async () => {
|
|
279
|
+
try {
|
|
280
|
+
return await getGatewayAuthToken(options);
|
|
281
|
+
} catch (error) {
|
|
282
|
+
throw GatewayAuthenticationError.createContextualError({
|
|
283
|
+
apiKeyProvided: false,
|
|
284
|
+
oidcTokenProvided: false,
|
|
285
|
+
statusCode: 401,
|
|
286
|
+
cause: error,
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
// Mints a short-lived realtime client secret (`vcst_`) via the Gateway's
|
|
292
|
+
// `/v1/realtime/client-secrets` route, authenticated with the long-lived
|
|
293
|
+
// Gateway credential. Server-side only (asserted) — the credential never
|
|
294
|
+
// belongs in a browser; the browser receives only the minted token. The
|
|
295
|
+
// mint route lives at the gateway origin's `/v1/realtime/client-secrets`,
|
|
296
|
+
// not under the realtime `baseURL` path (which targets `/v4/ai`), so the
|
|
297
|
+
// URL is resolved against the origin.
|
|
298
|
+
const mintRealtimeClientSecret = async (params: {
|
|
299
|
+
modelId: string;
|
|
300
|
+
expiresAfterSeconds?: number;
|
|
301
|
+
}): Promise<{ token: string; expiresAt?: number }> => {
|
|
302
|
+
assertGatewayRealtimeServerEnvironment();
|
|
303
|
+
const auth = await getRealtimeAuthToken();
|
|
304
|
+
const headers = createAuthHeaders(auth);
|
|
305
|
+
const url = new URL('/v1/realtime/client-secrets', baseURL).toString();
|
|
306
|
+
try {
|
|
307
|
+
const { value } = await postJsonToApi({
|
|
308
|
+
url,
|
|
309
|
+
headers,
|
|
310
|
+
body: {
|
|
311
|
+
model: params.modelId,
|
|
312
|
+
...(params.expiresAfterSeconds != null && {
|
|
313
|
+
expiresIn: params.expiresAfterSeconds,
|
|
314
|
+
}),
|
|
315
|
+
},
|
|
316
|
+
successfulResponseHandler: createJsonResponseHandler(
|
|
317
|
+
gatewayClientSecretResponseSchema,
|
|
318
|
+
),
|
|
319
|
+
failedResponseHandler: createJsonErrorResponseHandler({
|
|
320
|
+
errorSchema: z.any(),
|
|
321
|
+
errorToMessage: data => data,
|
|
322
|
+
}),
|
|
323
|
+
fetch: options.fetch,
|
|
324
|
+
});
|
|
325
|
+
return {
|
|
326
|
+
token: value.token,
|
|
327
|
+
...(value.expiresAt != null && { expiresAt: value.expiresAt }),
|
|
328
|
+
};
|
|
329
|
+
} catch (error) {
|
|
330
|
+
throw await asGatewayError(error, await parseAuthMethod(headers));
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
|
|
258
334
|
const createO11yHeaders = () => {
|
|
259
335
|
const deploymentId = loadOptionalSetting({
|
|
260
336
|
settingValue: undefined,
|
|
@@ -444,6 +520,35 @@ export function createGateway(
|
|
|
444
520
|
};
|
|
445
521
|
provider.transcriptionModel = createTranscriptionModel;
|
|
446
522
|
provider.transcription = createTranscriptionModel;
|
|
523
|
+
|
|
524
|
+
// No server-environment guard here: building the realtime model is just the
|
|
525
|
+
// event codec + WebSocket-config helper, which the browser legitimately
|
|
526
|
+
// needs to drive the transport with a server-minted client secret. The
|
|
527
|
+
// server-only boundary is enforced on minting itself
|
|
528
|
+
// (`mintRealtimeClientSecret`), which requires the Gateway credential.
|
|
529
|
+
const createRealtimeModel = (modelId: GatewayRealtimeModelId) =>
|
|
530
|
+
new GatewayRealtimeModel(modelId, {
|
|
531
|
+
provider: 'gateway.realtime',
|
|
532
|
+
baseURL,
|
|
533
|
+
teamIdOrSlug: options.teamIdOrSlug,
|
|
534
|
+
createClientSecret: mintRealtimeClientSecret,
|
|
535
|
+
});
|
|
536
|
+
provider.experimental_realtime = Object.assign(
|
|
537
|
+
(modelId: GatewayRealtimeModelId) => createRealtimeModel(modelId),
|
|
538
|
+
{
|
|
539
|
+
getToken: async (tokenOptions: RealtimeFactoryV4GetTokenOptions) => {
|
|
540
|
+
const { model: modelId, ...secretOptions } = tokenOptions;
|
|
541
|
+
const model = createRealtimeModel(modelId);
|
|
542
|
+
const secret = await model.doCreateClientSecret(secretOptions);
|
|
543
|
+
return {
|
|
544
|
+
token: secret.token,
|
|
545
|
+
url: secret.url,
|
|
546
|
+
...(secret.expiresAt != null && { expiresAt: secret.expiresAt }),
|
|
547
|
+
};
|
|
548
|
+
},
|
|
549
|
+
},
|
|
550
|
+
) as RealtimeFactoryV4;
|
|
551
|
+
|
|
447
552
|
provider.chat = provider.languageModel;
|
|
448
553
|
provider.embedding = provider.embeddingModel;
|
|
449
554
|
provider.image = provider.imageModel;
|
|
@@ -475,3 +580,11 @@ export async function getGatewayAuthToken(
|
|
|
475
580
|
authMethod: 'oidc',
|
|
476
581
|
};
|
|
477
582
|
}
|
|
583
|
+
|
|
584
|
+
function assertGatewayRealtimeServerEnvironment(): void {
|
|
585
|
+
if (typeof globalThis.window !== 'undefined') {
|
|
586
|
+
throw new Error(
|
|
587
|
+
'AI Gateway realtime client secrets must be minted server-side: minting needs your Gateway credential, which must never reach the browser. Call gateway.experimental_realtime.getToken() from your server and pass the returned token to the client.',
|
|
588
|
+
);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
@@ -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,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
|
+
}
|
|
@@ -80,7 +80,7 @@ export class GatewayRerankingModel implements RerankingModelV4 {
|
|
|
80
80
|
providerMetadata:
|
|
81
81
|
responseBody.providerMetadata as unknown as SharedV4ProviderMetadata,
|
|
82
82
|
response: { headers: responseHeaders, body: rawValue },
|
|
83
|
-
warnings: [],
|
|
83
|
+
warnings: responseBody.warnings ?? [],
|
|
84
84
|
};
|
|
85
85
|
} catch (error) {
|
|
86
86
|
throw await asGatewayError(
|
|
@@ -102,6 +102,28 @@ export class GatewayRerankingModel implements RerankingModelV4 {
|
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
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
|
+
|
|
105
127
|
const gatewayRerankingResponseSchema = lazySchema(() =>
|
|
106
128
|
zodSchema(
|
|
107
129
|
z.object({
|
|
@@ -111,6 +133,7 @@ const gatewayRerankingResponseSchema = lazySchema(() =>
|
|
|
111
133
|
relevanceScore: z.number(),
|
|
112
134
|
}),
|
|
113
135
|
),
|
|
136
|
+
warnings: z.array(gatewayRerankingWarningSchema).optional(),
|
|
114
137
|
providerMetadata: z
|
|
115
138
|
.record(z.string(), z.record(z.string(), z.unknown()))
|
|
116
139
|
.optional(),
|
|
@@ -125,6 +125,11 @@ const gatewaySpeechWarningSchema = z.discriminatedUnion('type', [
|
|
|
125
125
|
feature: z.string(),
|
|
126
126
|
details: z.string().optional(),
|
|
127
127
|
}),
|
|
128
|
+
z.object({
|
|
129
|
+
type: z.literal('deprecated'),
|
|
130
|
+
setting: z.string(),
|
|
131
|
+
message: z.string(),
|
|
132
|
+
}),
|
|
128
133
|
z.object({
|
|
129
134
|
type: z.literal('other'),
|
|
130
135
|
message: z.string(),
|
package/src/gateway-tools.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { exaSearch } from './tool/exa-search';
|
|
1
2
|
import { parallelSearch } from './tool/parallel-search';
|
|
2
3
|
import { perplexitySearch } from './tool/perplexity-search';
|
|
3
4
|
|
|
@@ -5,6 +6,15 @@ import { perplexitySearch } from './tool/perplexity-search';
|
|
|
5
6
|
* Gateway-specific provider-defined tools.
|
|
6
7
|
*/
|
|
7
8
|
export const gatewayTools = {
|
|
9
|
+
/**
|
|
10
|
+
* Search the web using Exa for current information and token-efficient
|
|
11
|
+
* excerpts optimized for agent workflows.
|
|
12
|
+
*
|
|
13
|
+
* Supports search type, category, domain, date, location, and content
|
|
14
|
+
* extraction controls.
|
|
15
|
+
*/
|
|
16
|
+
exaSearch,
|
|
17
|
+
|
|
8
18
|
/**
|
|
9
19
|
* Search the web using Parallel AI's Search API for LLM-optimized excerpts.
|
|
10
20
|
*
|
|
@@ -124,6 +124,11 @@ const gatewayTranscriptionWarningSchema = z.discriminatedUnion('type', [
|
|
|
124
124
|
feature: z.string(),
|
|
125
125
|
details: z.string().optional(),
|
|
126
126
|
}),
|
|
127
|
+
z.object({
|
|
128
|
+
type: z.literal('deprecated'),
|
|
129
|
+
setting: z.string(),
|
|
130
|
+
message: z.string(),
|
|
131
|
+
}),
|
|
127
132
|
z.object({
|
|
128
133
|
type: z.literal('other'),
|
|
129
134
|
message: z.string(),
|
|
@@ -7,8 +7,6 @@ export type GatewayVideoModelId =
|
|
|
7
7
|
| 'alibaba/wan-v2.6-t2v'
|
|
8
8
|
| 'bytedance/seedance-2.0'
|
|
9
9
|
| 'bytedance/seedance-2.0-fast'
|
|
10
|
-
| 'bytedance/seedance-v1.0-lite-i2v'
|
|
11
|
-
| 'bytedance/seedance-v1.0-lite-t2v'
|
|
12
10
|
| 'bytedance/seedance-v1.0-pro'
|
|
13
11
|
| 'bytedance/seedance-v1.0-pro-fast'
|
|
14
12
|
| 'bytedance/seedance-v1.5-pro'
|
|
@@ -46,6 +46,7 @@ export class GatewayVideoModel implements Experimental_VideoModelV4 {
|
|
|
46
46
|
duration,
|
|
47
47
|
fps,
|
|
48
48
|
seed,
|
|
49
|
+
generateAudio,
|
|
49
50
|
image,
|
|
50
51
|
providerOptions,
|
|
51
52
|
headers,
|
|
@@ -81,6 +82,7 @@ export class GatewayVideoModel implements Experimental_VideoModelV4 {
|
|
|
81
82
|
...(duration && { duration }),
|
|
82
83
|
...(fps && { fps }),
|
|
83
84
|
...(seed && { seed }),
|
|
85
|
+
...(generateAudio !== undefined && { generateAudio }),
|
|
84
86
|
...(providerOptions && { providerOptions }),
|
|
85
87
|
...(image && { image: maybeEncodeVideoFile(image) }),
|
|
86
88
|
},
|
|
@@ -239,6 +241,11 @@ const gatewayVideoWarningSchema = z.discriminatedUnion('type', [
|
|
|
239
241
|
feature: z.string(),
|
|
240
242
|
details: z.string().optional(),
|
|
241
243
|
}),
|
|
244
|
+
z.object({
|
|
245
|
+
type: z.literal('deprecated'),
|
|
246
|
+
setting: z.string(),
|
|
247
|
+
message: z.string(),
|
|
248
|
+
}),
|
|
242
249
|
z.object({
|
|
243
250
|
type: z.literal('other'),
|
|
244
251
|
message: z.string(),
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
export type { GatewayModelId } from './gateway-language-model-settings';
|
|
2
|
+
export {
|
|
3
|
+
GATEWAY_AUTH_SUBPROTOCOL_PREFIX,
|
|
4
|
+
GATEWAY_REALTIME_SUBPROTOCOL,
|
|
5
|
+
GATEWAY_TEAM_SUBPROTOCOL_PREFIX,
|
|
6
|
+
getGatewayRealtimeAuthToken,
|
|
7
|
+
getGatewayRealtimeProtocols,
|
|
8
|
+
getGatewayRealtimeTeamIdOrSlug,
|
|
9
|
+
} from './gateway-realtime-auth';
|
|
10
|
+
export type { GatewayRealtimeModelId } from './gateway-realtime-model-settings';
|
|
2
11
|
export type { GatewayRerankingModelId } from './gateway-reranking-model-settings';
|
|
3
12
|
export type { GatewaySpeechModelId } from './gateway-speech-model-settings';
|
|
4
13
|
export type { GatewayTranscriptionModelId } from './gateway-transcription-model-settings';
|
|
@@ -36,6 +45,8 @@ export type {
|
|
|
36
45
|
export {
|
|
37
46
|
GatewayError,
|
|
38
47
|
GatewayAuthenticationError,
|
|
48
|
+
GatewayFailedDependencyError,
|
|
49
|
+
GatewayForbiddenError,
|
|
39
50
|
GatewayInvalidRequestError,
|
|
40
51
|
GatewayRateLimitError,
|
|
41
52
|
GatewayModelNotFoundError,
|