@ai-sdk/gateway 4.0.122 → 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
  /**
@@ -3078,7 +3271,7 @@ async function getVercelRequestId() {
3078
3271
  }
3079
3272
  //#endregion
3080
3273
  //#region src/version.ts
3081
- const VERSION = "4.0.122";
3274
+ const VERSION = "4.0.123";
3082
3275
  //#endregion
3083
3276
  //#region src/gateway-provider.ts
3084
3277
  const AI_GATEWAY_PROTOCOL_VERSION = "0.0.1";
@@ -3315,11 +3508,24 @@ function createGateway(options = {}) {
3315
3508
  baseURL,
3316
3509
  headers: getHeaders,
3317
3510
  fetch: options.fetch,
3318
- o11yHeaders: createO11yHeaders()
3511
+ o11yHeaders: createO11yHeaders(),
3512
+ webSocket: options.webSocket
3319
3513
  });
3320
3514
  };
3321
3515
  provider.speechModel = createSpeechModel;
3322
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
+ } });
3323
3529
  const createTranscriptionModel = (modelId) => {
3324
3530
  return new GatewayTranscriptionModel(modelId, {
3325
3531
  provider: "gateway",
@@ -3382,9 +3588,9 @@ async function getGatewayAuthToken(options) {
3382
3588
  };
3383
3589
  }
3384
3590
  function assertGatewayClientSecretServerEnvironment() {
3385
- 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.");
3386
3592
  }
3387
3593
  //#endregion
3388
- 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 };
3389
3595
 
3390
3596
  //# sourceMappingURL=index.js.map