@ai-sdk/xai 4.0.5 → 4.0.7

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,21 @@
1
1
  # @ai-sdk/xai
2
2
 
3
+ ## 4.0.7
4
+
5
+ ### Patch Changes
6
+
7
+ - 5520b8a: Emit provider-executed tool results for completed xAI Responses API streaming tool calls.
8
+
9
+ ## 4.0.6
10
+
11
+ ### Patch Changes
12
+
13
+ - 5c5c0f5: Add experimental streaming transcription support for transcription models, including OpenAI `gpt-realtime-whisper` and xAI WebSocket STT.
14
+ - Updated dependencies [5c5c0f5]
15
+ - @ai-sdk/provider@4.0.2
16
+ - @ai-sdk/provider-utils@5.0.5
17
+ - @ai-sdk/openai-compatible@3.0.5
18
+
3
19
  ## 4.0.5
4
20
 
5
21
  ### 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
@@ -2870,6 +2870,14 @@ var XaiResponsesLanguageModel = class _XaiResponsesLanguageModel {
2870
2870
  providerExecuted: true
2871
2871
  });
2872
2872
  }
2873
+ if (event.type === "response.output_item.done") {
2874
+ controller.enqueue({
2875
+ type: "tool-result",
2876
+ toolCallId: part.id,
2877
+ toolName,
2878
+ result: {}
2879
+ });
2880
+ }
2873
2881
  return;
2874
2882
  }
2875
2883
  if (part.type === "message") {
@@ -3393,7 +3401,7 @@ var xaiTools = {
3393
3401
  };
3394
3402
 
3395
3403
  // src/version.ts
3396
- var VERSION = true ? "4.0.5" : "0.0.0-test";
3404
+ var VERSION = true ? "4.0.7" : "0.0.0-test";
3397
3405
 
3398
3406
  // src/files/xai-files.ts
3399
3407
  import {
@@ -4058,14 +4066,21 @@ var XaiSpeechModel = class _XaiSpeechModel {
4058
4066
  };
4059
4067
 
4060
4068
  // src/xai-transcription-model.ts
4069
+ import {
4070
+ InvalidArgumentError
4071
+ } from "@ai-sdk/provider";
4061
4072
  import {
4062
4073
  combineHeaders as combineHeaders7,
4063
4074
  convertBase64ToUint8Array,
4064
4075
  createJsonResponseHandler as createJsonResponseHandler6,
4076
+ getWebSocketConstructor,
4065
4077
  mediaTypeToExtension,
4066
4078
  parseProviderOptions as parseProviderOptions7,
4067
4079
  postFormDataToApi as postFormDataToApi2,
4080
+ readWebSocketMessageText,
4081
+ safeParseJSON as safeParseJSON2,
4068
4082
  serializeModelOptions as serializeModelOptions5,
4083
+ toWebSocketUrl,
4069
4084
  WORKFLOW_DESERIALIZE as WORKFLOW_DESERIALIZE5,
4070
4085
  WORKFLOW_SERIALIZE as WORKFLOW_SERIALIZE5
4071
4086
  } from "@ai-sdk/provider-utils";
@@ -4122,7 +4137,28 @@ var xaiTranscriptionModelOptionsSchema = lazySchema9(
4122
4137
  /**
4123
4138
  * Include filler words such as "uh" and "um" in the transcript.
4124
4139
  */
4125
- fillerWords: z20.boolean().nullish()
4140
+ fillerWords: z20.boolean().nullish(),
4141
+ /**
4142
+ * Options for streaming speech-to-text over WebSocket.
4143
+ */
4144
+ streaming: z20.object({
4145
+ /**
4146
+ * Emit interim transcript results while speech is being processed.
4147
+ */
4148
+ interimResults: z20.boolean().optional(),
4149
+ /**
4150
+ * Silence duration in milliseconds before an utterance-final event.
4151
+ */
4152
+ endpointing: z20.number().int().min(0).max(5e3).optional(),
4153
+ /**
4154
+ * End-of-turn detection threshold. When set, enables Smart Turn.
4155
+ */
4156
+ smartTurn: z20.number().min(0).max(1).optional(),
4157
+ /**
4158
+ * Maximum silence duration in milliseconds before forcing speech_final.
4159
+ */
4160
+ smartTurnTimeout: z20.number().int().min(1).max(5e3).optional()
4161
+ }).optional()
4126
4162
  })
4127
4163
  )
4128
4164
  );
@@ -4225,7 +4261,282 @@ var XaiTranscriptionModel = class _XaiTranscriptionModel {
4225
4261
  }
4226
4262
  };
4227
4263
  }
4264
+ async doStream(options) {
4265
+ var _a, _b, _c, _d, _e, _f, _g;
4266
+ const currentDate = (_c = (_b = (_a = this.config._internal) == null ? void 0 : _a.currentDate) == null ? void 0 : _b.call(_a)) != null ? _c : /* @__PURE__ */ new Date();
4267
+ const warnings = [];
4268
+ const xaiOptions = await parseProviderOptions7({
4269
+ provider: "xai",
4270
+ providerOptions: options.providerOptions,
4271
+ schema: xaiTranscriptionModelOptionsSchema
4272
+ });
4273
+ if ((xaiOptions == null ? void 0 : xaiOptions.multichannel) === true && xaiOptions.channels == null) {
4274
+ throw new InvalidArgumentError({
4275
+ argument: "providerOptions",
4276
+ message: "providerOptions.xai.channels is required when providerOptions.xai.multichannel is true"
4277
+ });
4278
+ }
4279
+ if ((xaiOptions == null ? void 0 : xaiOptions.format) != null) {
4280
+ warnings.push({
4281
+ type: "unsupported",
4282
+ feature: "providerOptions.xai.format",
4283
+ details: "xAI streaming transcription does not support format."
4284
+ });
4285
+ }
4286
+ if ((xaiOptions == null ? void 0 : xaiOptions.audioFormat) == null && !isKnownInputAudioFormat(options.inputAudioFormat.type)) {
4287
+ warnings.push({
4288
+ type: "other",
4289
+ 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.`
4290
+ });
4291
+ }
4292
+ const url = buildXaiStreamingTranscriptionUrl({
4293
+ baseURL: (_d = this.config.baseURL) != null ? _d : "https://api.x.ai/v1",
4294
+ inputAudioFormat: options.inputAudioFormat,
4295
+ providerOptions: xaiOptions
4296
+ });
4297
+ const headers = combineHeaders7((_f = (_e = this.config).headers) == null ? void 0 : _f.call(_e), options.headers);
4298
+ return {
4299
+ request: { body: url.toString() },
4300
+ response: {
4301
+ timestamp: currentDate,
4302
+ modelId: this.modelId
4303
+ },
4304
+ stream: createXaiStreamingTranscriptionStream({
4305
+ webSocket: this.config.webSocket,
4306
+ url,
4307
+ headers,
4308
+ warnings,
4309
+ language: (_g = xaiOptions == null ? void 0 : xaiOptions.language) != null ? _g : void 0,
4310
+ expectedDoneCount: (xaiOptions == null ? void 0 : xaiOptions.multichannel) === true ? xaiOptions.channels : 1,
4311
+ audio: options.audio,
4312
+ abortSignal: options.abortSignal,
4313
+ includeRawChunks: options.includeRawChunks
4314
+ })
4315
+ };
4316
+ }
4228
4317
  };
4318
+ function createXaiStreamingTranscriptionStream({
4319
+ webSocket,
4320
+ url,
4321
+ headers,
4322
+ warnings,
4323
+ language,
4324
+ expectedDoneCount,
4325
+ audio,
4326
+ abortSignal,
4327
+ includeRawChunks
4328
+ }) {
4329
+ let finished = false;
4330
+ let cleanup = () => {
4331
+ };
4332
+ return new ReadableStream({
4333
+ start: (controller) => {
4334
+ const WebSocketConstructor = getWebSocketConstructor(webSocket);
4335
+ const ws = new WebSocketConstructor(url, void 0, { headers });
4336
+ const doneTexts = /* @__PURE__ */ new Map();
4337
+ let doneDuration;
4338
+ let audioReader;
4339
+ cleanup = (closeCode) => {
4340
+ abortSignal == null ? void 0 : abortSignal.removeEventListener("abort", abort);
4341
+ void (audioReader == null ? void 0 : audioReader.cancel().catch(() => {
4342
+ }));
4343
+ try {
4344
+ ws.close(closeCode);
4345
+ } catch (e) {
4346
+ }
4347
+ };
4348
+ const finishWithError = (error) => {
4349
+ if (finished) return;
4350
+ finished = true;
4351
+ cleanup();
4352
+ controller.error(error);
4353
+ };
4354
+ const maybeFinish = () => {
4355
+ if (finished || doneTexts.size < expectedDoneCount) return;
4356
+ finished = true;
4357
+ const text = [...doneTexts.entries()].sort(([a], [b]) => a - b).map(([, value]) => value).join("\n");
4358
+ controller.enqueue({
4359
+ type: "finish",
4360
+ text,
4361
+ segments: [],
4362
+ language,
4363
+ durationInSeconds: doneDuration
4364
+ });
4365
+ controller.close();
4366
+ cleanup(1e3);
4367
+ };
4368
+ const abort = () => {
4369
+ var _a;
4370
+ finishWithError((_a = abortSignal == null ? void 0 : abortSignal.reason) != null ? _a : new Error("Aborted"));
4371
+ };
4372
+ if (abortSignal == null ? void 0 : abortSignal.aborted) {
4373
+ abort();
4374
+ return;
4375
+ }
4376
+ abortSignal == null ? void 0 : abortSignal.addEventListener("abort", abort, { once: true });
4377
+ const sendAudio = async () => {
4378
+ audioReader = audio.getReader();
4379
+ try {
4380
+ while (true) {
4381
+ const { done, value } = await audioReader.read();
4382
+ if (done || finished) break;
4383
+ ws.send(
4384
+ value instanceof Uint8Array ? value : convertBase64ToUint8Array(value)
4385
+ );
4386
+ }
4387
+ } finally {
4388
+ audioReader.releaseLock();
4389
+ }
4390
+ if (!finished) {
4391
+ ws.send(JSON.stringify({ type: "audio.done" }));
4392
+ }
4393
+ };
4394
+ ws.onmessage = (event) => {
4395
+ void readWebSocketMessageText(event.data).then(async (text) => {
4396
+ var _a, _b, _c, _d, _e, _f;
4397
+ const parsed = await safeParseJSON2({ text });
4398
+ if (!parsed.success) return;
4399
+ const raw = parsed.value;
4400
+ if (includeRawChunks) {
4401
+ controller.enqueue({ type: "raw", rawValue: raw });
4402
+ }
4403
+ switch (raw.type) {
4404
+ case "transcript.created": {
4405
+ controller.enqueue({ type: "stream-start", warnings });
4406
+ void sendAudio().catch(finishWithError);
4407
+ break;
4408
+ }
4409
+ case "transcript.partial": {
4410
+ const id = channelId(raw.channel_index);
4411
+ const timing = timingFromXaiEvent(raw);
4412
+ if (raw.is_final) {
4413
+ controller.enqueue({
4414
+ type: "transcript-final",
4415
+ id,
4416
+ text: (_a = raw.text) != null ? _a : "",
4417
+ ...timing,
4418
+ channelIndex: raw.channel_index
4419
+ });
4420
+ } else {
4421
+ controller.enqueue({
4422
+ type: "transcript-partial",
4423
+ id,
4424
+ text: (_b = raw.text) != null ? _b : "",
4425
+ startSecond: raw.start,
4426
+ durationInSeconds: raw.duration,
4427
+ channelIndex: raw.channel_index
4428
+ });
4429
+ }
4430
+ break;
4431
+ }
4432
+ case "transcript.done": {
4433
+ const channelIndex = (_c = raw.channel_index) != null ? _c : 0;
4434
+ doneTexts.set(channelIndex, (_d = raw.text) != null ? _d : "");
4435
+ doneDuration = (_e = raw.duration) != null ? _e : doneDuration;
4436
+ maybeFinish();
4437
+ break;
4438
+ }
4439
+ case "error": {
4440
+ finishWithError(new Error((_f = raw.message) != null ? _f : "xAI STT error"));
4441
+ break;
4442
+ }
4443
+ }
4444
+ }).catch(finishWithError);
4445
+ };
4446
+ ws.onerror = () => {
4447
+ finishWithError(
4448
+ new Error(
4449
+ "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 })." : "")
4450
+ )
4451
+ );
4452
+ };
4453
+ ws.onclose = () => {
4454
+ if (finished) return;
4455
+ finished = true;
4456
+ cleanup();
4457
+ controller.close();
4458
+ };
4459
+ },
4460
+ cancel: () => {
4461
+ if (finished) return;
4462
+ finished = true;
4463
+ cleanup();
4464
+ }
4465
+ });
4466
+ }
4467
+ function buildXaiStreamingTranscriptionUrl({
4468
+ baseURL,
4469
+ inputAudioFormat,
4470
+ providerOptions
4471
+ }) {
4472
+ var _a, _b, _c, _d, _e, _f;
4473
+ const url = toWebSocketUrl(`${baseURL}/stt`);
4474
+ appendSearchParam(
4475
+ url,
4476
+ "sample_rate",
4477
+ (_a = providerOptions == null ? void 0 : providerOptions.sampleRate) != null ? _a : inputAudioFormat.rate
4478
+ );
4479
+ appendSearchParam(
4480
+ url,
4481
+ "encoding",
4482
+ (_b = providerOptions == null ? void 0 : providerOptions.audioFormat) != null ? _b : encodingFromInputAudioFormat(inputAudioFormat.type)
4483
+ );
4484
+ appendSearchParam(url, "language", providerOptions == null ? void 0 : providerOptions.language);
4485
+ appendSearchParam(url, "diarize", providerOptions == null ? void 0 : providerOptions.diarize);
4486
+ appendSearchParam(url, "filler_words", providerOptions == null ? void 0 : providerOptions.fillerWords);
4487
+ appendSearchParam(url, "multichannel", providerOptions == null ? void 0 : providerOptions.multichannel);
4488
+ appendSearchParam(url, "channels", providerOptions == null ? void 0 : providerOptions.channels);
4489
+ appendSearchParam(
4490
+ url,
4491
+ "interim_results",
4492
+ (_c = providerOptions == null ? void 0 : providerOptions.streaming) == null ? void 0 : _c.interimResults
4493
+ );
4494
+ appendSearchParam(
4495
+ url,
4496
+ "endpointing",
4497
+ (_d = providerOptions == null ? void 0 : providerOptions.streaming) == null ? void 0 : _d.endpointing
4498
+ );
4499
+ appendSearchParam(url, "smart_turn", (_e = providerOptions == null ? void 0 : providerOptions.streaming) == null ? void 0 : _e.smartTurn);
4500
+ appendSearchParam(
4501
+ url,
4502
+ "smart_turn_timeout",
4503
+ (_f = providerOptions == null ? void 0 : providerOptions.streaming) == null ? void 0 : _f.smartTurnTimeout
4504
+ );
4505
+ if ((providerOptions == null ? void 0 : providerOptions.keyterm) != null) {
4506
+ const keyterms = Array.isArray(providerOptions.keyterm) ? providerOptions.keyterm : [providerOptions.keyterm];
4507
+ for (const keyterm of keyterms) {
4508
+ url.searchParams.append("keyterm", keyterm);
4509
+ }
4510
+ }
4511
+ return url;
4512
+ }
4513
+ function appendSearchParam(url, key, value) {
4514
+ if (value != null) {
4515
+ url.searchParams.set(key, String(value));
4516
+ }
4517
+ }
4518
+ function isKnownInputAudioFormat(type) {
4519
+ return type === "audio/pcm" || type === "audio/pcmu" || type === "audio/pcma";
4520
+ }
4521
+ function encodingFromInputAudioFormat(type) {
4522
+ switch (type) {
4523
+ case "audio/pcmu":
4524
+ return "mulaw";
4525
+ case "audio/pcma":
4526
+ return "alaw";
4527
+ default:
4528
+ return "pcm";
4529
+ }
4530
+ }
4531
+ function channelId(channelIndex) {
4532
+ return channelIndex == null ? void 0 : `channel-${channelIndex}`;
4533
+ }
4534
+ function timingFromXaiEvent(event) {
4535
+ return {
4536
+ ...event.start != null ? { startSecond: event.start } : {},
4537
+ ...event.start != null && event.duration != null ? { endSecond: event.start + event.duration } : {}
4538
+ };
4539
+ }
4229
4540
  var xaiTranscriptionResponseSchema = z21.object({
4230
4541
  text: z21.string(),
4231
4542
  language: z21.string().nullish(),
@@ -4311,7 +4622,8 @@ function createXai(options = {}) {
4311
4622
  provider: "xai.transcription",
4312
4623
  baseURL,
4313
4624
  headers: getHeaders,
4314
- fetch: options.fetch
4625
+ fetch: options.fetch,
4626
+ webSocket: options.webSocket
4315
4627
  });
4316
4628
  };
4317
4629
  const experimentalRealtimeFactory = Object.assign(