@ai-sdk/gateway 4.0.121 → 4.0.123

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/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
- import { EXPERIMENTAL_TRANSCRIPTION_STREAM_AUDIO_DONE_FRAME_TYPE, EXPERIMENTAL_TRANSCRIPTION_STREAM_START_FRAME_TYPE, WORKFLOW_DESERIALIZE, WORKFLOW_SERIALIZE, combineHeaders, connectToWebSocket, convertAsyncIteratorToReadableStream, convertBase64ToUint8Array, convertUint8ArrayToBase64, createEventSourceResponseHandler, createJsonErrorResponseHandler, createJsonLinesResponseHandler, createJsonResponseHandler, createProviderExecutedToolFactory, experimental_parseTranscriptionStreamPart, getErrorMessage, getFromApi, lazySchema, loadOptionalSetting, normalizeBatchRequestCounts, normalizeHeaders, parseJsonEventStream, parseProviderOptions, postJsonToApi, resolve, safeValidateTypes, secureJsonParse, serializeModelOptions, waitForWebSocketBufferDrain, withUserAgentSuffix, withoutTrailingSlash, zodSchema } from "@ai-sdk/provider-utils";
1
+ import { EXPERIMENTAL_SPEECH_STREAM_START_FRAME_TYPE, EXPERIMENTAL_SPEECH_STREAM_TEXT_DELTA_FRAME_TYPE, EXPERIMENTAL_SPEECH_STREAM_TEXT_DONE_FRAME_TYPE, EXPERIMENTAL_TRANSCRIPTION_STREAM_AUDIO_DONE_FRAME_TYPE, EXPERIMENTAL_TRANSCRIPTION_STREAM_START_FRAME_TYPE, WORKFLOW_DESERIALIZE, WORKFLOW_SERIALIZE, combineHeaders, connectToWebSocket, convertAsyncIteratorToReadableStream, convertBase64ToUint8Array, convertUint8ArrayToBase64, createEventSourceResponseHandler, createJsonErrorResponseHandler, createJsonLinesResponseHandler, createJsonResponseHandler, createProviderExecutedToolFactory, experimental_parseSpeechStreamPart, experimental_parseTranscriptionStreamPart, getErrorMessage, getFromApi, lazySchema, loadOptionalSetting, normalizeBatchRequestCounts, normalizeHeaders, parseJsonEventStream, parseProviderOptions, postJsonToApi, resolve, safeValidateTypes, secureJsonParse, serializeModelOptions, waitForWebSocketBufferDrain, withUserAgentSuffix, withoutTrailingSlash, zodSchema } from "@ai-sdk/provider-utils";
2
2
  import { any, array, boolean, discriminatedUnion, enum as enum$1, json, literal, number, object, record, string, union, unknown } from "zod/v4";
3
3
  import { APICallError, InvalidArgumentError, UnsupportedFunctionalityError, getErrorMessage as getErrorMessage$1 } from "@ai-sdk/provider";
4
4
  import { getContext, getVercelOidcToken } from "@vercel/oidc";
5
5
  //#region src/gateway-realtime-auth.ts
6
6
  /**
7
- * Shared WebSocket subprotocol contract for AI Gateway realtime and streaming
8
- * transcription auth.
7
+ * Shared WebSocket subprotocol contract for AI Gateway realtime, streaming
8
+ * transcription, and streaming speech auth.
9
9
  *
10
10
  * The browser `WebSocket` API cannot set request headers, so the Gateway auth
11
11
  * (bearer) token is carried through the `Sec-WebSocket-Protocol` handshake
@@ -14,7 +14,8 @@ import { getContext, getVercelOidcToken } from "@vercel/oidc";
14
14
  *
15
15
  * This module is the single source of truth for that contract so the client and
16
16
  * the Gateway server can't drift: the client encodes values with
17
- * `getGatewayRealtimeProtocols` / `getGatewayTranscriptionProtocols`, and the
17
+ * `getGatewayRealtimeProtocols` / `getGatewayTranscriptionProtocols` /
18
+ * `getGatewaySpeechProtocols`, and the
18
19
  * Gateway server decodes them with `getGatewayRealtimeAuthToken` /
19
20
  * `getGatewayRealtimeTeamIdOrSlug`.
20
21
  *
@@ -35,6 +36,11 @@ const GATEWAY_REALTIME_SUBPROTOCOL = "ai-gateway-realtime.v1";
35
36
  * purpose as `GATEWAY_REALTIME_SUBPROTOCOL`).
36
37
  */
37
38
  const GATEWAY_TRANSCRIPTION_SUBPROTOCOL = "ai-gateway-transcription.v1";
39
+ /**
40
+ * Marker subprotocol for streaming speech handshakes (same negotiation
41
+ * purpose as `GATEWAY_REALTIME_SUBPROTOCOL`).
42
+ */
43
+ const GATEWAY_SPEECH_SUBPROTOCOL = "ai-gateway-speech.v1";
38
44
  /** Subprotocol prefix that carries the Gateway auth (bearer) token. */
39
45
  const GATEWAY_AUTH_SUBPROTOCOL_PREFIX = "ai-gateway-auth.";
40
46
  /** Subprotocol prefix that carries optional Vercel team scoping. */
@@ -53,6 +59,13 @@ function getGatewayRealtimeProtocols(token, options) {
53
59
  function getGatewayTranscriptionProtocols(token, options) {
54
60
  return buildGatewayProtocols(GATEWAY_TRANSCRIPTION_SUBPROTOCOL, token, options);
55
61
  }
62
+ /**
63
+ * Client-side: `getGatewayRealtimeProtocols`, but with the streaming speech
64
+ * marker subprotocol.
65
+ */
66
+ function getGatewaySpeechProtocols(token, options) {
67
+ return buildGatewayProtocols(GATEWAY_SPEECH_SUBPROTOCOL, token, options);
68
+ }
56
69
  function buildGatewayProtocols(marker, token, options) {
57
70
  const protocols = [marker, `${GATEWAY_AUTH_SUBPROTOCOL_PREFIX}${token}`];
58
71
  if (options?.teamIdOrSlug) protocols.push(`${GATEWAY_TEAM_SUBPROTOCOL_PREFIX}${encodeSubprotocolValue(options.teamIdOrSlug)}`);
@@ -2044,93 +2057,47 @@ const gatewayRerankingResponseSchema = lazySchema(() => zodSchema(z.object({
2044
2057
  providerMetadata: z.record(z.string(), z.record(z.string(), z.unknown())).optional()
2045
2058
  })));
2046
2059
  //#endregion
2047
- //#region src/gateway-speech-model.ts
2048
- var GatewaySpeechModel = class {
2049
- constructor(modelId, config) {
2050
- this.modelId = modelId;
2051
- this.config = config;
2052
- this.specificationVersion = "v4";
2053
- }
2054
- get provider() {
2055
- return this.config.provider;
2056
- }
2057
- async doGenerate({ text, voice, outputFormat, instructions, speed, language, providerOptions, headers, abortSignal }) {
2058
- const resolvedHeaders = this.config.headers ? await resolve(this.config.headers) : void 0;
2059
- try {
2060
- const { responseHeaders, value: responseBody, rawValue } = await postJsonToApi({
2061
- url: this.getUrl(),
2062
- headers: combineHeaders(resolvedHeaders, headers ?? {}, this.getModelConfigHeaders(), await resolve(this.config.o11yHeaders)),
2063
- body: {
2064
- text,
2065
- ...voice && { voice },
2066
- ...outputFormat && { outputFormat },
2067
- ...instructions && { instructions },
2068
- ...speed != null && { speed },
2069
- ...language && { language },
2070
- ...providerOptions && { providerOptions }
2071
- },
2072
- successfulResponseHandler: createJsonResponseHandler(gatewaySpeechResponseSchema),
2073
- failedResponseHandler: createJsonErrorResponseHandler({
2074
- errorSchema: z.any(),
2075
- errorToMessage: (data) => getErrorMessage(data) ?? "unknown error"
2076
- }),
2077
- ...abortSignal && { abortSignal },
2078
- fetch: this.config.fetch
2079
- });
2080
- return {
2081
- audio: responseBody.audio,
2082
- warnings: responseBody.warnings ?? [],
2083
- ...responseBody.usage != null && { usage: responseBody.usage },
2084
- providerMetadata: responseBody.providerMetadata,
2085
- response: {
2086
- timestamp: /* @__PURE__ */ new Date(),
2087
- modelId: this.modelId,
2088
- headers: responseHeaders,
2089
- body: rawValue
2090
- }
2091
- };
2092
- } catch (error) {
2093
- throw await asGatewayError(error, await parseAuthMethod(resolvedHeaders ?? {}));
2094
- }
2095
- }
2096
- getUrl() {
2097
- return `${this.config.baseURL}/speech-model`;
2098
- }
2099
- getModelConfigHeaders() {
2100
- return {
2101
- "ai-speech-model-specification-version": "4",
2102
- "ai-model-id": this.modelId
2103
- };
2104
- }
2060
+ //#region src/gateway-stream-errors.ts
2061
+ /**
2062
+ * `asGatewayError` is async while the WebSocket event handlers are
2063
+ * synchronous, hence this helper.
2064
+ */
2065
+ async function errorControllerWithGatewayError(controller, error, authMethod) {
2066
+ controller.error(await asGatewayError(error, authMethod));
2067
+ }
2068
+ /**
2069
+ * Extract a human-readable message from an `error` stream part's payload for
2070
+ * the terminal close error.
2071
+ */
2072
+ function getServerErrorMessage(error) {
2073
+ if (error != null && typeof error === "object" && "message" in error && typeof error.message === "string") return error.message;
2074
+ return getErrorMessage$1(error);
2075
+ }
2076
+ /** Canonical status codes for server error-part types (no HTTP status exists on the WebSocket). */
2077
+ const SERVER_ERROR_STATUS_CODES = {
2078
+ authentication_error: 401,
2079
+ failed_dependency: 424,
2080
+ forbidden: 403,
2081
+ internal_server_error: 500,
2082
+ invalid_request_error: 400,
2083
+ model_not_found: 404,
2084
+ rate_limit_exceeded: 429
2105
2085
  };
2106
- const providerMetadataEntrySchema$1 = () => z.object({}).catchall(z.unknown());
2107
- const gatewaySpeechWarningSchema = () => z.discriminatedUnion("type", [
2108
- z.object({
2109
- type: z.literal("unsupported"),
2110
- feature: z.string(),
2111
- details: z.string().optional()
2112
- }),
2113
- z.object({
2114
- type: z.literal("compatibility"),
2115
- feature: z.string(),
2116
- details: z.string().optional()
2117
- }),
2118
- z.object({
2119
- type: z.literal("deprecated"),
2120
- setting: z.string(),
2121
- message: z.string()
2122
- }),
2123
- z.object({
2124
- type: z.literal("other"),
2125
- message: z.string()
2126
- })
2127
- ]);
2128
- const gatewaySpeechResponseSchema = lazySchema(() => zodSchema(z.object({
2129
- audio: z.string(),
2130
- warnings: z.array(gatewaySpeechWarningSchema()).optional(),
2131
- usage: z.record(z.string(), z.json()).optional(),
2132
- providerMetadata: z.record(z.string(), providerMetadataEntrySchema$1()).optional()
2133
- })));
2086
+ /**
2087
+ * Maps a server error-part payload (`{ message, type }`) to the public
2088
+ * Gateway error class for its type; unknown shapes keep the generic message.
2089
+ */
2090
+ async function createErrorFromServerErrorPart(error, authMethod, streamLabel = "transcription") {
2091
+ if (typeof error === "object" && error != null && "message" in error && typeof error.message === "string" && "type" in error && typeof error.type === "string" && error.type in SERVER_ERROR_STATUS_CODES) return createGatewayErrorFromResponse({
2092
+ response: { error: {
2093
+ message: error.message,
2094
+ type: error.type
2095
+ } },
2096
+ statusCode: SERVER_ERROR_STATUS_CODES[error.type],
2097
+ authMethod
2098
+ });
2099
+ return /* @__PURE__ */ new Error(`AI Gateway ${streamLabel} stream failed: ${getServerErrorMessage(error)}`);
2100
+ }
2134
2101
  //#endregion
2135
2102
  //#region src/gateway-transcription-model.ts
2136
2103
  var GatewayTranscriptionModel = class {
@@ -2252,11 +2219,11 @@ function toGatewayTranscriptionUrl(baseURL, modelId) {
2252
2219
  * case-insensitive because `combineHeaders` does not normalize casing and
2253
2220
  * callers can pass arbitrary casing via the `headers` option.
2254
2221
  */
2255
- function getProtocolsFromHeaders(headers) {
2222
+ function getProtocolsFromHeaders(headers, { marker = GATEWAY_TRANSCRIPTION_SUBPROTOCOL, getProtocols = getGatewayTranscriptionProtocols } = {}) {
2256
2223
  const normalizedHeaders = normalizeHeaders(headers);
2257
2224
  const authorization = normalizedHeaders.authorization;
2258
2225
  const token = authorization?.startsWith("Bearer ") ? authorization.slice(7) : void 0;
2259
- return token == null ? [GATEWAY_TRANSCRIPTION_SUBPROTOCOL] : getGatewayTranscriptionProtocols(token, { teamIdOrSlug: normalizedHeaders[VERCEL_AI_GATEWAY_TEAM_HEADER] });
2226
+ return token == null ? [marker] : getProtocols(token, { teamIdOrSlug: normalizedHeaders[VERCEL_AI_GATEWAY_TEAM_HEADER] });
2260
2227
  }
2261
2228
  /** Audio frames stay under server frame-size limits (envelope rule 7). */
2262
2229
  const MAX_AUDIO_FRAME_BYTES = 65536;
@@ -2363,7 +2330,7 @@ function createGatewayTranscriptionStream({ webSocket, url, protocols, headers,
2363
2330
  }
2364
2331
  });
2365
2332
  }
2366
- const providerMetadataEntrySchema = () => z.object({}).catchall(z.unknown());
2333
+ const providerMetadataEntrySchema$1 = () => z.object({}).catchall(z.unknown());
2367
2334
  const gatewayTranscriptionWarningSchema = () => z.discriminatedUnion("type", [
2368
2335
  z.object({
2369
2336
  type: z.literal("unsupported"),
@@ -2396,48 +2363,274 @@ const gatewayTranscriptionResponseSchema = lazySchema(() => zodSchema(z.object({
2396
2363
  durationInSeconds: z.number().nullish(),
2397
2364
  warnings: z.array(gatewayTranscriptionWarningSchema()).optional(),
2398
2365
  usage: z.record(z.string(), z.json()).optional(),
2399
- providerMetadata: z.record(z.string(), providerMetadataEntrySchema()).optional()
2366
+ providerMetadata: z.record(z.string(), providerMetadataEntrySchema$1()).optional()
2400
2367
  })));
2368
+ //#endregion
2369
+ //#region src/gateway-speech-model.ts
2370
+ var GatewaySpeechModel = class {
2371
+ constructor(modelId, config) {
2372
+ this.modelId = modelId;
2373
+ this.config = config;
2374
+ this.specificationVersion = "v4";
2375
+ }
2376
+ get provider() {
2377
+ return this.config.provider;
2378
+ }
2379
+ async doGenerate({ text, voice, outputFormat, instructions, speed, language, providerOptions, headers, abortSignal }) {
2380
+ const resolvedHeaders = this.config.headers ? await resolve(this.config.headers) : void 0;
2381
+ try {
2382
+ const { responseHeaders, value: responseBody, rawValue } = await postJsonToApi({
2383
+ url: this.getUrl(),
2384
+ headers: combineHeaders(resolvedHeaders, headers ?? {}, this.getModelConfigHeaders(), await resolve(this.config.o11yHeaders)),
2385
+ body: {
2386
+ text,
2387
+ ...voice && { voice },
2388
+ ...outputFormat && { outputFormat },
2389
+ ...instructions && { instructions },
2390
+ ...speed != null && { speed },
2391
+ ...language && { language },
2392
+ ...providerOptions && { providerOptions }
2393
+ },
2394
+ successfulResponseHandler: createJsonResponseHandler(gatewaySpeechResponseSchema),
2395
+ failedResponseHandler: createJsonErrorResponseHandler({
2396
+ errorSchema: z.any(),
2397
+ errorToMessage: (data) => getErrorMessage(data) ?? "unknown error"
2398
+ }),
2399
+ ...abortSignal && { abortSignal },
2400
+ fetch: this.config.fetch
2401
+ });
2402
+ return {
2403
+ audio: responseBody.audio,
2404
+ warnings: responseBody.warnings ?? [],
2405
+ ...responseBody.usage != null && { usage: responseBody.usage },
2406
+ providerMetadata: responseBody.providerMetadata,
2407
+ response: {
2408
+ timestamp: /* @__PURE__ */ new Date(),
2409
+ modelId: this.modelId,
2410
+ headers: responseHeaders,
2411
+ body: rawValue
2412
+ }
2413
+ };
2414
+ } catch (error) {
2415
+ throw await asGatewayError(error, await parseAuthMethod(resolvedHeaders ?? {}));
2416
+ }
2417
+ }
2418
+ async doStream(options) {
2419
+ const currentDate = this.config._internal?.currentDate?.() ?? /* @__PURE__ */ new Date();
2420
+ const headers = combineHeaders(await resolve(this.config.headers ?? {}), options.headers ?? {}, this.getModelConfigHeaders(), await resolve(this.config.o11yHeaders));
2421
+ const authMethod = await parseAuthMethod(headers);
2422
+ const startFrame = {
2423
+ type: EXPERIMENTAL_SPEECH_STREAM_START_FRAME_TYPE,
2424
+ ...options.voice != null && { voice: options.voice },
2425
+ ...options.outputFormat != null && { outputFormat: options.outputFormat },
2426
+ ...options.instructions != null && { instructions: options.instructions },
2427
+ ...options.speed != null && { speed: options.speed },
2428
+ ...options.language != null && { language: options.language },
2429
+ ...options.providerOptions != null && { providerOptions: options.providerOptions },
2430
+ ...options.includeRawChunks != null && { includeRawChunks: options.includeRawChunks }
2431
+ };
2432
+ return {
2433
+ stream: createGatewaySpeechStream({
2434
+ webSocket: this.config.webSocket,
2435
+ url: toGatewaySpeechUrl(this.config.baseURL, this.modelId),
2436
+ protocols: getProtocolsFromHeaders(headers, {
2437
+ marker: GATEWAY_SPEECH_SUBPROTOCOL,
2438
+ getProtocols: getGatewaySpeechProtocols
2439
+ }),
2440
+ headers,
2441
+ startFrame,
2442
+ text: options.text,
2443
+ abortSignal: options.abortSignal,
2444
+ authMethod
2445
+ }),
2446
+ request: { body: startFrame },
2447
+ response: {
2448
+ timestamp: currentDate,
2449
+ modelId: this.modelId
2450
+ }
2451
+ };
2452
+ }
2453
+ getUrl() {
2454
+ return `${this.config.baseURL}/speech-model`;
2455
+ }
2456
+ getModelConfigHeaders() {
2457
+ return {
2458
+ "ai-speech-model-specification-version": "4",
2459
+ "ai-model-id": this.modelId
2460
+ };
2461
+ }
2462
+ };
2401
2463
  /**
2402
- * `asGatewayError` is async while the WebSocket event handlers are
2403
- * synchronous, hence this helper.
2464
+ * Gateway streaming speech WebSocket URL: HTTP(S) base upgraded to WS(S),
2465
+ * model id in `?ai-model-id=` (browser `WebSocket` cannot set headers).
2404
2466
  */
2405
- async function errorControllerWithGatewayError(controller, error, authMethod) {
2406
- controller.error(await asGatewayError(error, authMethod));
2467
+ function toGatewaySpeechUrl(baseURL, modelId) {
2468
+ const url = new URL(`${baseURL.replace(/^http/, "ws")}/speech-model`);
2469
+ url.searchParams.set("ai-model-id", modelId);
2470
+ return url.toString();
2407
2471
  }
2408
2472
  /**
2409
- * Extract a human-readable message from an `error` stream part's payload for
2410
- * the terminal close error.
2473
+ * Keeps text-delta frames under 64 KiB (envelope rule 7) even when JSON
2474
+ * escapes every code unit as `\uXXXX`.
2411
2475
  */
2412
- function getServerErrorMessage(error) {
2413
- if (error != null && typeof error === "object" && "message" in error && typeof error.message === "string") return error.message;
2414
- return getErrorMessage$1(error);
2415
- }
2416
- /** Canonical status codes for server error-part types (no HTTP status exists on the WebSocket). */
2417
- const SERVER_ERROR_STATUS_CODES = {
2418
- authentication_error: 401,
2419
- failed_dependency: 424,
2420
- forbidden: 403,
2421
- internal_server_error: 500,
2422
- invalid_request_error: 400,
2423
- model_not_found: 404,
2424
- rate_limit_exceeded: 429
2425
- };
2476
+ const MAX_TEXT_DELTA_CODE_UNITS = 8192;
2426
2477
  /**
2427
- * Maps a server error-part payload (`{ message, type }`) to the public
2428
- * Gateway error class for its type; unknown shapes keep the generic message.
2478
+ * Splits a long text delta into pieces of at most `MAX_TEXT_DELTA_CODE_UNITS`
2479
+ * UTF-16 code units without separating a surrogate pair.
2429
2480
  */
2430
- async function createErrorFromServerErrorPart(error, authMethod) {
2431
- if (typeof error === "object" && error != null && "message" in error && typeof error.message === "string" && "type" in error && typeof error.type === "string" && error.type in SERVER_ERROR_STATUS_CODES) return createGatewayErrorFromResponse({
2432
- response: { error: {
2433
- message: error.message,
2434
- type: error.type
2435
- } },
2436
- statusCode: SERVER_ERROR_STATUS_CODES[error.type],
2437
- authMethod
2481
+ function splitTextDelta(delta) {
2482
+ const pieces = [];
2483
+ let offset = 0;
2484
+ while (offset < delta.length) {
2485
+ let end = Math.min(offset + MAX_TEXT_DELTA_CODE_UNITS, delta.length);
2486
+ const lastCodeUnit = delta.charCodeAt(end - 1);
2487
+ if (end < delta.length && lastCodeUnit >= 55296 && lastCodeUnit <= 56319) end--;
2488
+ pieces.push(delta.slice(offset, end));
2489
+ offset = end;
2490
+ }
2491
+ return pieces;
2492
+ }
2493
+ function createGatewaySpeechStream({ webSocket, url, protocols, headers, startFrame, text, abortSignal, authMethod }) {
2494
+ let finished = false;
2495
+ let cleanup = () => {};
2496
+ return new ReadableStream({
2497
+ start: (controller) => {
2498
+ let textReader;
2499
+ let hasServerErrorPart = false;
2500
+ let lastServerError;
2501
+ let textStopped = false;
2502
+ let connection;
2503
+ const cancelText = () => {
2504
+ if (textReader != null) {
2505
+ textReader.cancel().catch(() => {});
2506
+ textReader = void 0;
2507
+ } else text.cancel().catch(() => {});
2508
+ };
2509
+ cleanup = (closeCode) => {
2510
+ cancelText();
2511
+ connection?.close(closeCode);
2512
+ };
2513
+ const finishWithError = (error) => {
2514
+ if (finished) return;
2515
+ finished = true;
2516
+ cleanup();
2517
+ errorControllerWithGatewayError(controller, error, authMethod);
2518
+ };
2519
+ const sendText = async (socket) => {
2520
+ const reader = text.getReader();
2521
+ textReader = reader;
2522
+ try {
2523
+ while (true) {
2524
+ const { done, value } = await reader.read();
2525
+ if (done || finished || textStopped) break;
2526
+ let first = true;
2527
+ const pieces = splitTextDelta(value.text);
2528
+ if (pieces.length === 0 && (value.voice != null || value.providerOptions != null)) pieces.push("");
2529
+ for (const delta of pieces) {
2530
+ if (finished || textStopped) break;
2531
+ const frame = {
2532
+ type: EXPERIMENTAL_SPEECH_STREAM_TEXT_DELTA_FRAME_TYPE,
2533
+ delta,
2534
+ ...value.voice != null && { voice: value.voice },
2535
+ ...first && value.providerOptions != null && { providerOptions: value.providerOptions }
2536
+ };
2537
+ first = false;
2538
+ socket.send(JSON.stringify(frame));
2539
+ await waitForWebSocketBufferDrain(socket);
2540
+ }
2541
+ }
2542
+ } finally {
2543
+ reader.releaseLock();
2544
+ if (textReader === reader) textReader = void 0;
2545
+ }
2546
+ if (!finished && !textStopped) socket.send(JSON.stringify({ type: EXPERIMENTAL_SPEECH_STREAM_TEXT_DONE_FRAME_TYPE }));
2547
+ };
2548
+ connection = connectToWebSocket({
2549
+ url,
2550
+ protocols,
2551
+ headers,
2552
+ webSocket,
2553
+ abortSignal,
2554
+ onAbort: (reason) => {
2555
+ if (finished) return;
2556
+ finished = true;
2557
+ cleanup();
2558
+ controller.error(reason);
2559
+ },
2560
+ onProcessingError: finishWithError,
2561
+ onOpen: (socket) => {
2562
+ socket.send(JSON.stringify(startFrame));
2563
+ sendText(socket).catch(finishWithError);
2564
+ },
2565
+ onMessageText: async (messageText) => {
2566
+ if (finished) return;
2567
+ const part = experimental_parseSpeechStreamPart(messageText);
2568
+ if (part == null) return;
2569
+ if (part.type === "finish") {
2570
+ finished = true;
2571
+ controller.enqueue(part);
2572
+ controller.close();
2573
+ cleanup(1e3);
2574
+ return;
2575
+ }
2576
+ if (part.type === "error") {
2577
+ hasServerErrorPart = true;
2578
+ lastServerError = part.error;
2579
+ textStopped = true;
2580
+ cancelText();
2581
+ part.error = await createErrorFromServerErrorPart(part.error, authMethod, "speech");
2582
+ if (finished) return;
2583
+ }
2584
+ controller.enqueue(part);
2585
+ },
2586
+ onSocketError: () => {
2587
+ finishWithError(/* @__PURE__ */ new Error("Connection error on AI Gateway speech stream"));
2588
+ },
2589
+ onClose: ({ code, reason }) => {
2590
+ if (hasServerErrorPart) {
2591
+ if (finished) return;
2592
+ createErrorFromServerErrorPart(lastServerError, authMethod, "speech").then(finishWithError);
2593
+ return;
2594
+ }
2595
+ finishWithError(/* @__PURE__ */ new Error("AI Gateway speech stream closed before a finish part was received" + (code != null ? ` (code ${code}${reason ? `: ${reason}` : ""})` : "") + "."));
2596
+ }
2597
+ });
2598
+ },
2599
+ cancel: () => {
2600
+ if (finished) return;
2601
+ finished = true;
2602
+ cleanup();
2603
+ }
2438
2604
  });
2439
- return /* @__PURE__ */ new Error(`AI Gateway transcription stream failed: ${getServerErrorMessage(error)}`);
2440
2605
  }
2606
+ const providerMetadataEntrySchema = () => z.object({}).catchall(z.unknown());
2607
+ const gatewaySpeechWarningSchema = () => z.discriminatedUnion("type", [
2608
+ z.object({
2609
+ type: z.literal("unsupported"),
2610
+ feature: z.string(),
2611
+ details: z.string().optional()
2612
+ }),
2613
+ z.object({
2614
+ type: z.literal("compatibility"),
2615
+ feature: z.string(),
2616
+ details: z.string().optional()
2617
+ }),
2618
+ z.object({
2619
+ type: z.literal("deprecated"),
2620
+ setting: z.string(),
2621
+ message: z.string()
2622
+ }),
2623
+ z.object({
2624
+ type: z.literal("other"),
2625
+ message: z.string()
2626
+ })
2627
+ ]);
2628
+ const gatewaySpeechResponseSchema = lazySchema(() => zodSchema(z.object({
2629
+ audio: z.string(),
2630
+ warnings: z.array(gatewaySpeechWarningSchema()).optional(),
2631
+ usage: z.record(z.string(), z.json()).optional(),
2632
+ providerMetadata: z.record(z.string(), providerMetadataEntrySchema()).optional()
2633
+ })));
2441
2634
  //#endregion
2442
2635
  //#region src/gateway-realtime-model.ts
2443
2636
  /**
@@ -2697,39 +2890,65 @@ const exaSearchToolFactory = createProviderExecutedToolFactory({
2697
2890
  const exaSearch = (config = {}) => exaSearchToolFactory(config);
2698
2891
  //#endregion
2699
2892
  //#region src/tool/parallel-search.ts
2893
+ const parallelSearchModes = [
2894
+ "turbo",
2895
+ "fast",
2896
+ "basic",
2897
+ "advanced",
2898
+ "one-shot",
2899
+ "agentic"
2900
+ ];
2700
2901
  const parallelSearchInputSchema = lazySchema(() => zodSchema(z.object({
2701
2902
  objective: z.string().describe("Natural-language description of the web research goal, including source or freshness guidance and broader context from the task. Maximum 5000 characters."),
2702
- search_queries: z.array(z.string()).optional().describe("Optional search queries to supplement the objective. Maximum 200 characters per query."),
2703
- mode: z.enum(["one-shot", "agentic"]).optional().describe("Mode preset: \"one-shot\" for comprehensive results with longer excerpts (default), \"agentic\" for concise, token-efficient results for multi-step workflows."),
2903
+ search_queries: z.array(z.string()).optional().describe("Provide 2-3 concise keyword queries, 3-6 words each (max 5, 200 chars each). Strongly recommended; the search runs on these queries."),
2904
+ mode: z.enum(parallelSearchModes).optional().describe("Search mode: \"turbo\" and \"fast\" return quickly with shorter excerpts, \"basic\" is balanced (default), \"advanced\" is the most thorough. \"turbo\" supports English and Japanese only. \"one-shot\" and \"agentic\" are deprecated aliases of \"basic\" and \"advanced\"."),
2704
2905
  max_results: z.number().optional().describe("Maximum number of results to return (1-20). Defaults to 10 if not specified."),
2705
2906
  source_policy: z.object({
2706
- include_domains: z.array(z.string()).optional().describe("Limit results to these domains. Use plain domain names only — e.g. example.com or sub.example.gov, or a bare extension like .edu. Do not include a scheme, path, or port (e.g. not https://example.com/page)."),
2707
- exclude_domains: z.array(z.string()).optional().describe("Exclude results from these domains. Use plain domain names only — e.g. example.com or sub.example.gov, or a bare extension like .edu. Do not include a scheme, path, or port (e.g. not https://example.com/page)."),
2708
- after_date: z.string().optional().describe("Only include results published after this date. Use an ISO 8601 calendar date formatted YYYY-MM-DD (e.g. 2025-01-01); do not include a time.")
2907
+ include_domains: z.array(z.string()).optional().describe("Domains or domain/path prefixes to restrict results to, without scheme or port. Example: ['wikipedia.org', 'docs.python.org/3']."),
2908
+ exclude_domains: z.array(z.string()).optional().describe("Domains or domain/path prefixes to exclude, without scheme or port. Example: ['reddit.com']."),
2909
+ after_date: z.string().optional().describe("Only include results published on or after this date. Use an ISO 8601 calendar date formatted YYYY-MM-DD (e.g. 2025-01-01); do not include a time.")
2709
2910
  }).optional().describe("Source policy for controlling which domains to include/exclude and freshness."),
2710
2911
  excerpts: z.object({
2711
2912
  max_chars_per_result: z.number().optional().describe("Maximum characters per result."),
2712
- max_chars_total: z.number().optional().describe("Maximum total characters across all results.")
2913
+ max_chars_total: z.number().optional().describe("Deprecated: use the top-level max_chars_total.")
2713
2914
  }).optional().describe("Excerpt configuration for controlling result length."),
2714
- fetch_policy: z.object({ max_age_seconds: z.number().optional().describe("Maximum age in seconds for cached content. Set to 0 to always fetch fresh content.") }).optional().describe("Fetch policy for controlling content freshness.")
2915
+ max_chars_total: z.number().optional().describe("Maximum total characters across all result excerpts."),
2916
+ fetch_policy: z.object({
2917
+ max_age_seconds: z.number().optional().describe("Maximum age of cached content in seconds before a live fetch, minimum 600."),
2918
+ timeout_seconds: z.number().optional().describe("Timeout in seconds for a live fetch when content is not cached. Capped by the gateway tool timeout."),
2919
+ disable_cache_fallback: z.boolean().optional().describe("Return an error instead of older cached content when a live fetch fails.")
2920
+ }).optional().describe("Fetch policy for controlling content freshness."),
2921
+ location: z.string().optional().describe("ISO 3166-1 alpha-2 country code to localize results, e.g. \"us\".")
2715
2922
  })));
2716
2923
  const parallelSearchOutputSchema = lazySchema(() => zodSchema(z.union([z.object({
2717
- searchId: z.string(),
2924
+ search_id: z.string(),
2718
2925
  results: z.array(z.object({
2719
2926
  url: z.string(),
2720
- title: z.string(),
2721
- excerpt: z.string(),
2722
- publishDate: z.string().nullable().optional(),
2723
- relevanceScore: z.number().optional()
2724
- }))
2927
+ title: z.string().nullable().optional(),
2928
+ publish_date: z.string().nullable().optional(),
2929
+ excerpts: z.array(z.string())
2930
+ })),
2931
+ warnings: z.array(z.object({
2932
+ type: z.string(),
2933
+ message: z.string(),
2934
+ detail: z.record(z.string(), z.unknown()).nullable().optional()
2935
+ })).nullable().optional(),
2936
+ usage: z.array(z.object({
2937
+ name: z.string(),
2938
+ count: z.number().optional()
2939
+ })).nullable().optional(),
2940
+ session_id: z.string().optional()
2725
2941
  }), z.object({
2726
2942
  error: z.enum([
2727
2943
  "api_error",
2944
+ "configuration_error",
2945
+ "execution_error",
2946
+ "gateway_rate_limit",
2947
+ "invalid_input",
2728
2948
  "rate_limit",
2729
2949
  "timeout",
2730
- "invalid_input",
2731
- "configuration_error",
2732
- "unknown"
2950
+ "unknown",
2951
+ "unknown_tool"
2733
2952
  ]),
2734
2953
  statusCode: z.number().optional(),
2735
2954
  message: z.string()
@@ -3052,7 +3271,7 @@ async function getVercelRequestId() {
3052
3271
  }
3053
3272
  //#endregion
3054
3273
  //#region src/version.ts
3055
- const VERSION = "4.0.121";
3274
+ const VERSION = "4.0.123";
3056
3275
  //#endregion
3057
3276
  //#region src/gateway-provider.ts
3058
3277
  const AI_GATEWAY_PROTOCOL_VERSION = "0.0.1";
@@ -3289,11 +3508,24 @@ function createGateway(options = {}) {
3289
3508
  baseURL,
3290
3509
  headers: getHeaders,
3291
3510
  fetch: options.fetch,
3292
- o11yHeaders: createO11yHeaders()
3511
+ o11yHeaders: createO11yHeaders(),
3512
+ webSocket: options.webSocket
3293
3513
  });
3294
3514
  };
3295
3515
  provider.speechModel = createSpeechModel;
3296
3516
  provider.speech = createSpeechModel;
3517
+ provider.experimental_speech = Object.assign((modelId) => createSpeechModel(modelId), { getToken: async (tokenOptions) => {
3518
+ const secret = await mintClientSecret({
3519
+ modelId: tokenOptions.model,
3520
+ routeKind: "speech",
3521
+ ...tokenOptions.expiresAfterSeconds != null && { expiresAfterSeconds: tokenOptions.expiresAfterSeconds }
3522
+ });
3523
+ return {
3524
+ token: secret.token,
3525
+ url: toGatewaySpeechUrl(baseURL, tokenOptions.model),
3526
+ ...secret.expiresAt != null && { expiresAt: secret.expiresAt }
3527
+ };
3528
+ } });
3297
3529
  const createTranscriptionModel = (modelId) => {
3298
3530
  return new GatewayTranscriptionModel(modelId, {
3299
3531
  provider: "gateway",
@@ -3356,9 +3588,9 @@ async function getGatewayAuthToken(options) {
3356
3588
  };
3357
3589
  }
3358
3590
  function assertGatewayClientSecretServerEnvironment() {
3359
- if (typeof globalThis.window !== "undefined") throw new Error("AI Gateway client secrets must be minted server-side: minting needs your Gateway credential, which must never reach the browser. Call gateway.experimental_realtime.getToken() or gateway.experimental_transcription.getToken() from your server and pass the returned token to the client.");
3591
+ if (typeof globalThis.window !== "undefined") throw new Error("AI Gateway client secrets must be minted server-side: minting needs your Gateway credential, which must never reach the browser. Call gateway.experimental_realtime.getToken(), gateway.experimental_transcription.getToken(), or gateway.experimental_speech.getToken() from your server and pass the returned token to the client.");
3360
3592
  }
3361
3593
  //#endregion
3362
- export { GATEWAY_AUTH_SUBPROTOCOL_PREFIX, GATEWAY_REALTIME_SUBPROTOCOL, GATEWAY_TEAM_SUBPROTOCOL_PREFIX, GATEWAY_TRANSCRIPTION_SUBPROTOCOL, GatewayAuthenticationError, GatewayError, GatewayFailedDependencyError, GatewayForbiddenError, GatewayInternalServerError, GatewayInvalidRequestError, GatewayModelNotFoundError, GatewayNotFoundError, GatewayRateLimitError, GatewayResponseError, VERSION, createGateway, createGateway as createGatewayProvider, gateway, getGatewayRealtimeAuthToken, getGatewayRealtimeProtocols, getGatewayRealtimeTeamIdOrSlug, getGatewayTranscriptionProtocols };
3594
+ export { GATEWAY_AUTH_SUBPROTOCOL_PREFIX, GATEWAY_REALTIME_SUBPROTOCOL, GATEWAY_SPEECH_SUBPROTOCOL, GATEWAY_TEAM_SUBPROTOCOL_PREFIX, GATEWAY_TRANSCRIPTION_SUBPROTOCOL, GatewayAuthenticationError, GatewayError, GatewayFailedDependencyError, GatewayForbiddenError, GatewayInternalServerError, GatewayInvalidRequestError, GatewayModelNotFoundError, GatewayNotFoundError, GatewayRateLimitError, GatewayResponseError, VERSION, createGateway, createGateway as createGatewayProvider, gateway, getGatewayRealtimeAuthToken, getGatewayRealtimeProtocols, getGatewayRealtimeTeamIdOrSlug, getGatewaySpeechProtocols, getGatewayTranscriptionProtocols };
3363
3595
 
3364
3596
  //# sourceMappingURL=index.js.map