@ai-sdk/xai 4.0.4 → 4.0.6

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 CHANGED
@@ -1,5 +1,23 @@
1
1
  # @ai-sdk/xai
2
2
 
3
+ ## 4.0.6
4
+
5
+ ### Patch Changes
6
+
7
+ - 5c5c0f5: Add experimental streaming transcription support for transcription models, including OpenAI `gpt-realtime-whisper` and xAI WebSocket STT.
8
+ - Updated dependencies [5c5c0f5]
9
+ - @ai-sdk/provider@4.0.2
10
+ - @ai-sdk/provider-utils@5.0.5
11
+ - @ai-sdk/openai-compatible@3.0.5
12
+
13
+ ## 4.0.5
14
+
15
+ ### Patch Changes
16
+
17
+ - Updated dependencies [c6f5e62]
18
+ - @ai-sdk/provider-utils@5.0.4
19
+ - @ai-sdk/openai-compatible@3.0.4
20
+
3
21
  ## 4.0.4
4
22
 
5
23
  ### Patch Changes
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { z } from 'zod/v4';
2
2
  import * as _ai_sdk_provider_utils from '@ai-sdk/provider-utils';
3
- import { InferSchema, FetchFunction } from '@ai-sdk/provider-utils';
3
+ import { InferSchema, FetchFunction, WebSocketConstructor } from '@ai-sdk/provider-utils';
4
4
  import { ProviderV4, LanguageModelV4, ImageModelV4, Experimental_VideoModelV4, Experimental_RealtimeFactoryV4, SpeechModelV4, TranscriptionModelV4, FilesV4, Experimental_RealtimeModelV4, Experimental_RealtimeModelV4ClientSecretOptions, Experimental_RealtimeModelV4ClientSecretResult, Experimental_RealtimeModelV4ServerEvent, Experimental_RealtimeModelV4ClientEvent, Experimental_RealtimeModelV4SessionConfig } from '@ai-sdk/provider';
5
5
 
6
6
  type XaiChatModelId = 'grok-4.20-non-reasoning' | 'grok-4.20-reasoning' | 'grok-4.3' | 'grok-latest' | (string & {});
@@ -197,6 +197,12 @@ declare const xaiTranscriptionModelOptionsSchema: _ai_sdk_provider_utils.LazySch
197
197
  diarize?: boolean | null | undefined;
198
198
  keyterm?: string | string[] | null | undefined;
199
199
  fillerWords?: boolean | null | undefined;
200
+ streaming?: {
201
+ interimResults?: boolean | undefined;
202
+ endpointing?: number | undefined;
203
+ smartTurn?: number | undefined;
204
+ smartTurnTimeout?: number | undefined;
205
+ } | undefined;
200
206
  }>;
201
207
  type XaiTranscriptionModelOptions = InferSchema<typeof xaiTranscriptionModelOptionsSchema>;
202
208
 
@@ -488,6 +494,11 @@ interface XaiProviderSettings {
488
494
  * or to provide a custom fetch implementation for e.g. testing.
489
495
  */
490
496
  fetch?: FetchFunction;
497
+ /**
498
+ * Custom WebSocket implementation. Required in runtimes whose native
499
+ * WebSocket constructor does not support headers for xAI streaming STT.
500
+ */
501
+ webSocket?: WebSocketConstructor;
491
502
  }
492
503
  declare function createXai(options?: XaiProviderSettings): XaiProvider;
493
504
  declare const xai: XaiProvider;
package/dist/index.js CHANGED
@@ -3393,7 +3393,7 @@ var xaiTools = {
3393
3393
  };
3394
3394
 
3395
3395
  // src/version.ts
3396
- var VERSION = true ? "4.0.4" : "0.0.0-test";
3396
+ var VERSION = true ? "4.0.6" : "0.0.0-test";
3397
3397
 
3398
3398
  // src/files/xai-files.ts
3399
3399
  import {
@@ -4058,14 +4058,21 @@ var XaiSpeechModel = class _XaiSpeechModel {
4058
4058
  };
4059
4059
 
4060
4060
  // src/xai-transcription-model.ts
4061
+ import {
4062
+ InvalidArgumentError
4063
+ } from "@ai-sdk/provider";
4061
4064
  import {
4062
4065
  combineHeaders as combineHeaders7,
4063
4066
  convertBase64ToUint8Array,
4064
4067
  createJsonResponseHandler as createJsonResponseHandler6,
4068
+ getWebSocketConstructor,
4065
4069
  mediaTypeToExtension,
4066
4070
  parseProviderOptions as parseProviderOptions7,
4067
4071
  postFormDataToApi as postFormDataToApi2,
4072
+ readWebSocketMessageText,
4073
+ safeParseJSON as safeParseJSON2,
4068
4074
  serializeModelOptions as serializeModelOptions5,
4075
+ toWebSocketUrl,
4069
4076
  WORKFLOW_DESERIALIZE as WORKFLOW_DESERIALIZE5,
4070
4077
  WORKFLOW_SERIALIZE as WORKFLOW_SERIALIZE5
4071
4078
  } from "@ai-sdk/provider-utils";
@@ -4122,7 +4129,28 @@ var xaiTranscriptionModelOptionsSchema = lazySchema9(
4122
4129
  /**
4123
4130
  * Include filler words such as "uh" and "um" in the transcript.
4124
4131
  */
4125
- fillerWords: z20.boolean().nullish()
4132
+ fillerWords: z20.boolean().nullish(),
4133
+ /**
4134
+ * Options for streaming speech-to-text over WebSocket.
4135
+ */
4136
+ streaming: z20.object({
4137
+ /**
4138
+ * Emit interim transcript results while speech is being processed.
4139
+ */
4140
+ interimResults: z20.boolean().optional(),
4141
+ /**
4142
+ * Silence duration in milliseconds before an utterance-final event.
4143
+ */
4144
+ endpointing: z20.number().int().min(0).max(5e3).optional(),
4145
+ /**
4146
+ * End-of-turn detection threshold. When set, enables Smart Turn.
4147
+ */
4148
+ smartTurn: z20.number().min(0).max(1).optional(),
4149
+ /**
4150
+ * Maximum silence duration in milliseconds before forcing speech_final.
4151
+ */
4152
+ smartTurnTimeout: z20.number().int().min(1).max(5e3).optional()
4153
+ }).optional()
4126
4154
  })
4127
4155
  )
4128
4156
  );
@@ -4225,7 +4253,282 @@ var XaiTranscriptionModel = class _XaiTranscriptionModel {
4225
4253
  }
4226
4254
  };
4227
4255
  }
4256
+ async doStream(options) {
4257
+ var _a, _b, _c, _d, _e, _f, _g;
4258
+ const currentDate = (_c = (_b = (_a = this.config._internal) == null ? void 0 : _a.currentDate) == null ? void 0 : _b.call(_a)) != null ? _c : /* @__PURE__ */ new Date();
4259
+ const warnings = [];
4260
+ const xaiOptions = await parseProviderOptions7({
4261
+ provider: "xai",
4262
+ providerOptions: options.providerOptions,
4263
+ schema: xaiTranscriptionModelOptionsSchema
4264
+ });
4265
+ if ((xaiOptions == null ? void 0 : xaiOptions.multichannel) === true && xaiOptions.channels == null) {
4266
+ throw new InvalidArgumentError({
4267
+ argument: "providerOptions",
4268
+ message: "providerOptions.xai.channels is required when providerOptions.xai.multichannel is true"
4269
+ });
4270
+ }
4271
+ if ((xaiOptions == null ? void 0 : xaiOptions.format) != null) {
4272
+ warnings.push({
4273
+ type: "unsupported",
4274
+ feature: "providerOptions.xai.format",
4275
+ details: "xAI streaming transcription does not support format."
4276
+ });
4277
+ }
4278
+ if ((xaiOptions == null ? void 0 : xaiOptions.audioFormat) == null && !isKnownInputAudioFormat(options.inputAudioFormat.type)) {
4279
+ warnings.push({
4280
+ type: "other",
4281
+ message: `Unrecognized inputAudioFormat.type "${options.inputAudioFormat.type}"; falling back to raw PCM encoding. Use audio/pcm, audio/pcmu, or audio/pcma, or set providerOptions.xai.audioFormat explicitly.`
4282
+ });
4283
+ }
4284
+ const url = buildXaiStreamingTranscriptionUrl({
4285
+ baseURL: (_d = this.config.baseURL) != null ? _d : "https://api.x.ai/v1",
4286
+ inputAudioFormat: options.inputAudioFormat,
4287
+ providerOptions: xaiOptions
4288
+ });
4289
+ const headers = combineHeaders7((_f = (_e = this.config).headers) == null ? void 0 : _f.call(_e), options.headers);
4290
+ return {
4291
+ request: { body: url.toString() },
4292
+ response: {
4293
+ timestamp: currentDate,
4294
+ modelId: this.modelId
4295
+ },
4296
+ stream: createXaiStreamingTranscriptionStream({
4297
+ webSocket: this.config.webSocket,
4298
+ url,
4299
+ headers,
4300
+ warnings,
4301
+ language: (_g = xaiOptions == null ? void 0 : xaiOptions.language) != null ? _g : void 0,
4302
+ expectedDoneCount: (xaiOptions == null ? void 0 : xaiOptions.multichannel) === true ? xaiOptions.channels : 1,
4303
+ audio: options.audio,
4304
+ abortSignal: options.abortSignal,
4305
+ includeRawChunks: options.includeRawChunks
4306
+ })
4307
+ };
4308
+ }
4228
4309
  };
4310
+ function createXaiStreamingTranscriptionStream({
4311
+ webSocket,
4312
+ url,
4313
+ headers,
4314
+ warnings,
4315
+ language,
4316
+ expectedDoneCount,
4317
+ audio,
4318
+ abortSignal,
4319
+ includeRawChunks
4320
+ }) {
4321
+ let finished = false;
4322
+ let cleanup = () => {
4323
+ };
4324
+ return new ReadableStream({
4325
+ start: (controller) => {
4326
+ const WebSocketConstructor = getWebSocketConstructor(webSocket);
4327
+ const ws = new WebSocketConstructor(url, void 0, { headers });
4328
+ const doneTexts = /* @__PURE__ */ new Map();
4329
+ let doneDuration;
4330
+ let audioReader;
4331
+ cleanup = (closeCode) => {
4332
+ abortSignal == null ? void 0 : abortSignal.removeEventListener("abort", abort);
4333
+ void (audioReader == null ? void 0 : audioReader.cancel().catch(() => {
4334
+ }));
4335
+ try {
4336
+ ws.close(closeCode);
4337
+ } catch (e) {
4338
+ }
4339
+ };
4340
+ const finishWithError = (error) => {
4341
+ if (finished) return;
4342
+ finished = true;
4343
+ cleanup();
4344
+ controller.error(error);
4345
+ };
4346
+ const maybeFinish = () => {
4347
+ if (finished || doneTexts.size < expectedDoneCount) return;
4348
+ finished = true;
4349
+ const text = [...doneTexts.entries()].sort(([a], [b]) => a - b).map(([, value]) => value).join("\n");
4350
+ controller.enqueue({
4351
+ type: "finish",
4352
+ text,
4353
+ segments: [],
4354
+ language,
4355
+ durationInSeconds: doneDuration
4356
+ });
4357
+ controller.close();
4358
+ cleanup(1e3);
4359
+ };
4360
+ const abort = () => {
4361
+ var _a;
4362
+ finishWithError((_a = abortSignal == null ? void 0 : abortSignal.reason) != null ? _a : new Error("Aborted"));
4363
+ };
4364
+ if (abortSignal == null ? void 0 : abortSignal.aborted) {
4365
+ abort();
4366
+ return;
4367
+ }
4368
+ abortSignal == null ? void 0 : abortSignal.addEventListener("abort", abort, { once: true });
4369
+ const sendAudio = async () => {
4370
+ audioReader = audio.getReader();
4371
+ try {
4372
+ while (true) {
4373
+ const { done, value } = await audioReader.read();
4374
+ if (done || finished) break;
4375
+ ws.send(
4376
+ value instanceof Uint8Array ? value : convertBase64ToUint8Array(value)
4377
+ );
4378
+ }
4379
+ } finally {
4380
+ audioReader.releaseLock();
4381
+ }
4382
+ if (!finished) {
4383
+ ws.send(JSON.stringify({ type: "audio.done" }));
4384
+ }
4385
+ };
4386
+ ws.onmessage = (event) => {
4387
+ void readWebSocketMessageText(event.data).then(async (text) => {
4388
+ var _a, _b, _c, _d, _e, _f;
4389
+ const parsed = await safeParseJSON2({ text });
4390
+ if (!parsed.success) return;
4391
+ const raw = parsed.value;
4392
+ if (includeRawChunks) {
4393
+ controller.enqueue({ type: "raw", rawValue: raw });
4394
+ }
4395
+ switch (raw.type) {
4396
+ case "transcript.created": {
4397
+ controller.enqueue({ type: "stream-start", warnings });
4398
+ void sendAudio().catch(finishWithError);
4399
+ break;
4400
+ }
4401
+ case "transcript.partial": {
4402
+ const id = channelId(raw.channel_index);
4403
+ const timing = timingFromXaiEvent(raw);
4404
+ if (raw.is_final) {
4405
+ controller.enqueue({
4406
+ type: "transcript-final",
4407
+ id,
4408
+ text: (_a = raw.text) != null ? _a : "",
4409
+ ...timing,
4410
+ channelIndex: raw.channel_index
4411
+ });
4412
+ } else {
4413
+ controller.enqueue({
4414
+ type: "transcript-partial",
4415
+ id,
4416
+ text: (_b = raw.text) != null ? _b : "",
4417
+ startSecond: raw.start,
4418
+ durationInSeconds: raw.duration,
4419
+ channelIndex: raw.channel_index
4420
+ });
4421
+ }
4422
+ break;
4423
+ }
4424
+ case "transcript.done": {
4425
+ const channelIndex = (_c = raw.channel_index) != null ? _c : 0;
4426
+ doneTexts.set(channelIndex, (_d = raw.text) != null ? _d : "");
4427
+ doneDuration = (_e = raw.duration) != null ? _e : doneDuration;
4428
+ maybeFinish();
4429
+ break;
4430
+ }
4431
+ case "error": {
4432
+ finishWithError(new Error((_f = raw.message) != null ? _f : "xAI STT error"));
4433
+ break;
4434
+ }
4435
+ }
4436
+ }).catch(finishWithError);
4437
+ };
4438
+ ws.onerror = () => {
4439
+ finishWithError(
4440
+ new Error(
4441
+ "xAI streaming transcription error." + (webSocket == null ? " Note: the native WebSocket implementation in browsers, Node.js, Deno, and Bun cannot send the Authorization header required by xAI. Pass a header-capable WebSocket implementation (e.g. the 'ws' package) via createXai({ webSocket })." : "")
4442
+ )
4443
+ );
4444
+ };
4445
+ ws.onclose = () => {
4446
+ if (finished) return;
4447
+ finished = true;
4448
+ cleanup();
4449
+ controller.close();
4450
+ };
4451
+ },
4452
+ cancel: () => {
4453
+ if (finished) return;
4454
+ finished = true;
4455
+ cleanup();
4456
+ }
4457
+ });
4458
+ }
4459
+ function buildXaiStreamingTranscriptionUrl({
4460
+ baseURL,
4461
+ inputAudioFormat,
4462
+ providerOptions
4463
+ }) {
4464
+ var _a, _b, _c, _d, _e, _f;
4465
+ const url = toWebSocketUrl(`${baseURL}/stt`);
4466
+ appendSearchParam(
4467
+ url,
4468
+ "sample_rate",
4469
+ (_a = providerOptions == null ? void 0 : providerOptions.sampleRate) != null ? _a : inputAudioFormat.rate
4470
+ );
4471
+ appendSearchParam(
4472
+ url,
4473
+ "encoding",
4474
+ (_b = providerOptions == null ? void 0 : providerOptions.audioFormat) != null ? _b : encodingFromInputAudioFormat(inputAudioFormat.type)
4475
+ );
4476
+ appendSearchParam(url, "language", providerOptions == null ? void 0 : providerOptions.language);
4477
+ appendSearchParam(url, "diarize", providerOptions == null ? void 0 : providerOptions.diarize);
4478
+ appendSearchParam(url, "filler_words", providerOptions == null ? void 0 : providerOptions.fillerWords);
4479
+ appendSearchParam(url, "multichannel", providerOptions == null ? void 0 : providerOptions.multichannel);
4480
+ appendSearchParam(url, "channels", providerOptions == null ? void 0 : providerOptions.channels);
4481
+ appendSearchParam(
4482
+ url,
4483
+ "interim_results",
4484
+ (_c = providerOptions == null ? void 0 : providerOptions.streaming) == null ? void 0 : _c.interimResults
4485
+ );
4486
+ appendSearchParam(
4487
+ url,
4488
+ "endpointing",
4489
+ (_d = providerOptions == null ? void 0 : providerOptions.streaming) == null ? void 0 : _d.endpointing
4490
+ );
4491
+ appendSearchParam(url, "smart_turn", (_e = providerOptions == null ? void 0 : providerOptions.streaming) == null ? void 0 : _e.smartTurn);
4492
+ appendSearchParam(
4493
+ url,
4494
+ "smart_turn_timeout",
4495
+ (_f = providerOptions == null ? void 0 : providerOptions.streaming) == null ? void 0 : _f.smartTurnTimeout
4496
+ );
4497
+ if ((providerOptions == null ? void 0 : providerOptions.keyterm) != null) {
4498
+ const keyterms = Array.isArray(providerOptions.keyterm) ? providerOptions.keyterm : [providerOptions.keyterm];
4499
+ for (const keyterm of keyterms) {
4500
+ url.searchParams.append("keyterm", keyterm);
4501
+ }
4502
+ }
4503
+ return url;
4504
+ }
4505
+ function appendSearchParam(url, key, value) {
4506
+ if (value != null) {
4507
+ url.searchParams.set(key, String(value));
4508
+ }
4509
+ }
4510
+ function isKnownInputAudioFormat(type) {
4511
+ return type === "audio/pcm" || type === "audio/pcmu" || type === "audio/pcma";
4512
+ }
4513
+ function encodingFromInputAudioFormat(type) {
4514
+ switch (type) {
4515
+ case "audio/pcmu":
4516
+ return "mulaw";
4517
+ case "audio/pcma":
4518
+ return "alaw";
4519
+ default:
4520
+ return "pcm";
4521
+ }
4522
+ }
4523
+ function channelId(channelIndex) {
4524
+ return channelIndex == null ? void 0 : `channel-${channelIndex}`;
4525
+ }
4526
+ function timingFromXaiEvent(event) {
4527
+ return {
4528
+ ...event.start != null ? { startSecond: event.start } : {},
4529
+ ...event.start != null && event.duration != null ? { endSecond: event.start + event.duration } : {}
4530
+ };
4531
+ }
4229
4532
  var xaiTranscriptionResponseSchema = z21.object({
4230
4533
  text: z21.string(),
4231
4534
  language: z21.string().nullish(),
@@ -4311,7 +4614,8 @@ function createXai(options = {}) {
4311
4614
  provider: "xai.transcription",
4312
4615
  baseURL,
4313
4616
  headers: getHeaders,
4314
- fetch: options.fetch
4617
+ fetch: options.fetch,
4618
+ webSocket: options.webSocket
4315
4619
  });
4316
4620
  };
4317
4621
  const experimentalRealtimeFactory = Object.assign(