@ai-sdk/xai 4.0.9 → 4.0.12
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 +42 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +107 -58
- package/dist/index.js.map +1 -1
- package/docs/01-xai.mdx +27 -40
- package/package.json +6 -6
- package/src/responses/xai-responses-language-model-options.ts +1 -0
- package/src/xai-chat-language-model-options.ts +1 -0
- package/src/xai-image-model.ts +3 -0
- package/src/xai-transcription-model.ts +122 -93
- package/src/xai-video-model.ts +52 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,47 @@
|
|
|
1
1
|
# @ai-sdk/xai
|
|
2
2
|
|
|
3
|
+
## 4.0.12
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 4be62c1: fix(provider-utils): validate provider-response URLs in `getFromApi`
|
|
8
|
+
|
|
9
|
+
`getFromApi` now has a `validateUrl` flag. It is optional so existing callers keep compiling (omitting it behaves like `false`, i.e. no validation), but all AI SDK provider packages set it explicitly at every call site so each one makes a visible trust decision. When `true`, the URL is routed through `fetchWithValidatedRedirects` — the same guard used by `downloadBlob` — which rejects private/loopback/link-local targets, re-validates every redirect hop, strips proxy/metadata/cookie request headers, and drops all caller headers except the user-agent on cross-origin redirects (custom API-key headers must not follow a redirect off-origin any more than `Authorization` may); blocked URLs throw `DownloadError`. It is enabled at the image/video/audio download and polling call sites where the URL comes from a provider response body; URLs built from developer-configured endpoints pass `validateUrl: false` and are unaffected.
|
|
10
|
+
|
|
11
|
+
A new optional `credentialedOrigin` withholds caller headers unless the URL is same-origin with it, so the API key is not sent to a response-supplied host on a different origin.
|
|
12
|
+
|
|
13
|
+
A new optional `trustedOrigin` exempts URLs (and redirect hops) that are same-origin with the developer-configured provider endpoint from target validation, so self-hosted and localhost deployments whose response URLs point back at the configured host keep working; all other hops are still validated.
|
|
14
|
+
|
|
15
|
+
Also closes range gaps in `validateDownloadUrl` (IPv4 `224.0.0.0/4` multicast and the TEST-NET documentation ranges `192.0.2.0/24`, `198.51.100.0/24`, `203.0.113.0/24`; IPv6 documentation ranges `2001:db8::/32` and `3fff::/20`), and follows only the fetch-spec redirect status codes (301/302/303/307/308) — a `Location` header on any other status is not followed. This guard performs string/literal checks only and does not resolve DNS; hostnames that resolve to private addresses and DNS rebinding remain out of scope and must be constrained at the network layer (or by injecting a Node `fetch` that pins the resolved IP at connect time) for server deployments handling untrusted URLs. See `contributing/secure-url-handling.md`.
|
|
16
|
+
|
|
17
|
+
- Updated dependencies [4be62c1]
|
|
18
|
+
- Updated dependencies [7805e4a]
|
|
19
|
+
- Updated dependencies [cd12954]
|
|
20
|
+
- @ai-sdk/provider-utils@5.0.9
|
|
21
|
+
- @ai-sdk/openai-compatible@3.0.9
|
|
22
|
+
|
|
23
|
+
## 4.0.11
|
|
24
|
+
|
|
25
|
+
### Patch Changes
|
|
26
|
+
|
|
27
|
+
- e193290: Add `connectToWebSocket` to `@ai-sdk/provider-utils`: a shared WebSocket connect layer (constructor resolution, header hygiene, abort wiring, message decoding) analogous to `postToApi` for HTTP. The openai and xai streaming transcription models now use it instead of hand-rolled connects. For openai and xai this also means WebSocket constructor failures now surface as stream errors instead of throwing synchronously from `doStream`, an already-aborted signal no longer constructs a socket, and the caller's audio stream is cancelled on pre-open failures. Messages are processed in order with close handling deferred behind pending frames, audio send loops apply backpressure via the socket's bufferedAmount, and failed sends cancel the caller's audio stream.
|
|
28
|
+
- e193290: Strip undefined header values before the streaming transcription WebSocket constructor (header-capable implementations like `ws` throw on undefined values).
|
|
29
|
+
- e193290: Fix streaming transcription stream parts against the live xAI STT API: `transcript-final` was emitted for every `is_final: true` event, but xAI re-sends the finalized text with `speech_final: false` before the `speech_final: true` event (duplicating finals) and also finalizes _fragments_ whose text the eventual `speech_final` event merges and re-punctuates (later-revised finals). `transcript-final` is now only emitted on `speech_final: true`; finalized fragments surface as `transcript-partial`. Additionally, xAI's `transcript.done` event arrives with an empty `text`, which produced an empty `finish.text` and made `experimental_streamTranscribe` throw `NoTranscriptGeneratedError` despite a full transcript having streamed — the finish text now falls back to the accumulated finalized utterances (plus any trailing unfinalized text).
|
|
30
|
+
- Updated dependencies [e193290]
|
|
31
|
+
- @ai-sdk/provider-utils@5.0.8
|
|
32
|
+
- @ai-sdk/openai-compatible@3.0.8
|
|
33
|
+
|
|
34
|
+
## 4.0.10
|
|
35
|
+
|
|
36
|
+
### Patch Changes
|
|
37
|
+
|
|
38
|
+
- 0f93c57: feat (video): support video (not just image) reference inputs in `inputReferences` for reference-to-video generation
|
|
39
|
+
- d25a084: feat (provider/xai): add grok-4.5 model id
|
|
40
|
+
- Updated dependencies [0f93c57]
|
|
41
|
+
- @ai-sdk/provider@4.0.3
|
|
42
|
+
- @ai-sdk/openai-compatible@3.0.7
|
|
43
|
+
- @ai-sdk/provider-utils@5.0.7
|
|
44
|
+
|
|
3
45
|
## 4.0.9
|
|
4
46
|
|
|
5
47
|
### Patch Changes
|
package/dist/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import * as _ai_sdk_provider_utils from '@ai-sdk/provider-utils';
|
|
|
3
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
|
-
type XaiChatModelId = 'grok-4.20-non-reasoning' | 'grok-4.20-reasoning' | 'grok-4.3' | 'grok-latest' | (string & {});
|
|
6
|
+
type XaiChatModelId = 'grok-4.20-non-reasoning' | 'grok-4.20-reasoning' | 'grok-4.3' | 'grok-4.5' | 'grok-latest' | (string & {});
|
|
7
7
|
declare const xaiLanguageModelChatOptions: z.ZodObject<{
|
|
8
8
|
reasoningEffort: z.ZodOptional<z.ZodEnum<{
|
|
9
9
|
none: "none";
|
|
@@ -72,7 +72,7 @@ declare const xaiFilePartProviderOptions: z.ZodObject<{
|
|
|
72
72
|
}, z.core.$strip>;
|
|
73
73
|
type XaiFilePartProviderOptions = z.infer<typeof xaiFilePartProviderOptions>;
|
|
74
74
|
|
|
75
|
-
type XaiResponsesModelId = 'grok-4.20-non-reasoning' | 'grok-4.20-reasoning' | 'grok-4.3' | 'grok-latest' | (string & {});
|
|
75
|
+
type XaiResponsesModelId = 'grok-4.20-non-reasoning' | 'grok-4.20-reasoning' | 'grok-4.3' | 'grok-4.5' | 'grok-latest' | (string & {});
|
|
76
76
|
/**
|
|
77
77
|
* @see https://docs.x.ai/docs/api-reference#create-new-response
|
|
78
78
|
*/
|
package/dist/index.js
CHANGED
|
@@ -1181,6 +1181,9 @@ var XaiImageModel = class _XaiImageModel {
|
|
|
1181
1181
|
async downloadImage(url, abortSignal) {
|
|
1182
1182
|
const { value } = await getFromApi({
|
|
1183
1183
|
url,
|
|
1184
|
+
// url is a generated-image URL from the provider response; validate it.
|
|
1185
|
+
validateUrl: true,
|
|
1186
|
+
trustedOrigin: this.config.baseURL,
|
|
1184
1187
|
abortSignal,
|
|
1185
1188
|
failedResponseHandler: createStatusCodeErrorResponseHandler(),
|
|
1186
1189
|
successfulResponseHandler: createBinaryResponseHandler(),
|
|
@@ -3474,7 +3477,7 @@ var xaiTools = {
|
|
|
3474
3477
|
};
|
|
3475
3478
|
|
|
3476
3479
|
// src/version.ts
|
|
3477
|
-
var VERSION = true ? "4.0.
|
|
3480
|
+
var VERSION = true ? "4.0.12" : "0.0.0-test";
|
|
3478
3481
|
|
|
3479
3482
|
// src/files/xai-files.ts
|
|
3480
3483
|
import {
|
|
@@ -3587,6 +3590,7 @@ import {
|
|
|
3587
3590
|
createJsonResponseHandler as createJsonResponseHandler5,
|
|
3588
3591
|
delay,
|
|
3589
3592
|
getFromApi as getFromApi2,
|
|
3593
|
+
getTopLevelMediaType as getTopLevelMediaType3,
|
|
3590
3594
|
parseProviderOptions as parseProviderOptions7,
|
|
3591
3595
|
postJsonToApi as postJsonToApi4
|
|
3592
3596
|
} from "@ai-sdk/provider-utils";
|
|
@@ -3661,17 +3665,28 @@ function resolveStartImage(options) {
|
|
|
3661
3665
|
var _a;
|
|
3662
3666
|
return (_a = getFirstFrameImage(options)) != null ? _a : options.image;
|
|
3663
3667
|
}
|
|
3668
|
+
var isVideoFile = (file) => file.mediaType != null && getTopLevelMediaType3(file.mediaType) === "video";
|
|
3664
3669
|
function fileToXaiImageUrl(file) {
|
|
3665
|
-
var _a;
|
|
3666
3670
|
if (file.type === "url") {
|
|
3667
3671
|
return file.url;
|
|
3668
3672
|
}
|
|
3669
3673
|
const base64Data = typeof file.data === "string" ? file.data : convertUint8ArrayToBase64(file.data);
|
|
3670
|
-
return `data:${
|
|
3674
|
+
return `data:${file.mediaType};base64,${base64Data}`;
|
|
3671
3675
|
}
|
|
3672
|
-
function resolveReferenceImages(options, xaiOptions) {
|
|
3676
|
+
function resolveReferenceImages(options, xaiOptions, warnings) {
|
|
3673
3677
|
if (options.inputReferences != null && options.inputReferences.length > 0) {
|
|
3674
|
-
|
|
3678
|
+
const imageReferences = options.inputReferences.filter((reference) => {
|
|
3679
|
+
if (isVideoFile(reference)) {
|
|
3680
|
+
warnings.push({
|
|
3681
|
+
type: "unsupported",
|
|
3682
|
+
feature: "inputReferences",
|
|
3683
|
+
details: 'xAI reference-to-video accepts image references only. The video reference was ignored. Use providerOptions.xai.mode "extend-video" to continue from a video.'
|
|
3684
|
+
});
|
|
3685
|
+
return false;
|
|
3686
|
+
}
|
|
3687
|
+
return true;
|
|
3688
|
+
});
|
|
3689
|
+
return imageReferences.map((reference) => ({
|
|
3675
3690
|
url: fileToXaiImageUrl(reference)
|
|
3676
3691
|
}));
|
|
3677
3692
|
}
|
|
@@ -3809,17 +3824,31 @@ var XaiVideoModel = class {
|
|
|
3809
3824
|
}
|
|
3810
3825
|
const startImage = resolveStartImage(options);
|
|
3811
3826
|
if (startImage != null) {
|
|
3812
|
-
|
|
3827
|
+
if (isVideoFile(startImage)) {
|
|
3828
|
+
const fromFrameImages = getFirstFrameImage(options) != null;
|
|
3829
|
+
warnings.push({
|
|
3830
|
+
type: "unsupported",
|
|
3831
|
+
feature: fromFrameImages ? "frameImages" : "image",
|
|
3832
|
+
details: 'xAI does not accept a video as a start/frame image. The video was ignored. Use providerOptions.xai.mode "extend-video" to continue from a video instead.'
|
|
3833
|
+
});
|
|
3834
|
+
} else {
|
|
3835
|
+
body.image = { url: fileToXaiImageUrl(startImage) };
|
|
3836
|
+
}
|
|
3813
3837
|
}
|
|
3814
|
-
|
|
3838
|
+
const lastFrameImage = getLastFrameImage(options);
|
|
3839
|
+
if (lastFrameImage != null) {
|
|
3815
3840
|
warnings.push({
|
|
3816
3841
|
type: "unsupported",
|
|
3817
3842
|
feature: "frameImages",
|
|
3818
|
-
details: `xAI video models do not support last_frame. Use providerOptions.xai.mode "extend-video" to continue from a video's last frame. The last frame image was ignored.`
|
|
3843
|
+
details: isVideoFile(lastFrameImage) ? 'xAI does not accept a video as a start/frame image. The video last frame was ignored. Use providerOptions.xai.mode "extend-video" to continue from a video instead.' : `xAI video models do not support last_frame. Use providerOptions.xai.mode "extend-video" to continue from a video's last frame. The last frame image was ignored.`
|
|
3819
3844
|
});
|
|
3820
3845
|
}
|
|
3821
3846
|
if (hasReferenceImages) {
|
|
3822
|
-
const referenceImages = resolveReferenceImages(
|
|
3847
|
+
const referenceImages = resolveReferenceImages(
|
|
3848
|
+
options,
|
|
3849
|
+
xaiOptions,
|
|
3850
|
+
warnings
|
|
3851
|
+
);
|
|
3823
3852
|
if (referenceImages != null) {
|
|
3824
3853
|
body.reference_images = referenceImages;
|
|
3825
3854
|
}
|
|
@@ -3886,6 +3915,7 @@ var XaiVideoModel = class {
|
|
|
3886
3915
|
}
|
|
3887
3916
|
const { value: statusResponse, responseHeaders: pollHeaders } = await getFromApi2({
|
|
3888
3917
|
url: `${baseURL}/videos/${requestId}`,
|
|
3918
|
+
validateUrl: false,
|
|
3889
3919
|
headers: combineHeaders5(this.config.headers(), options.headers),
|
|
3890
3920
|
successfulResponseHandler: createJsonResponseHandler5(
|
|
3891
3921
|
xaiVideoStatusResponseSchema
|
|
@@ -4146,14 +4176,14 @@ import {
|
|
|
4146
4176
|
combineHeaders as combineHeaders7,
|
|
4147
4177
|
convertBase64ToUint8Array,
|
|
4148
4178
|
createJsonResponseHandler as createJsonResponseHandler6,
|
|
4149
|
-
|
|
4179
|
+
connectToWebSocket,
|
|
4150
4180
|
mediaTypeToExtension,
|
|
4151
4181
|
parseProviderOptions as parseProviderOptions9,
|
|
4152
4182
|
postFormDataToApi as postFormDataToApi2,
|
|
4153
|
-
readWebSocketMessageText,
|
|
4154
4183
|
safeParseJSON as safeParseJSON2,
|
|
4155
4184
|
serializeModelOptions as serializeModelOptions5,
|
|
4156
4185
|
toWebSocketUrl,
|
|
4186
|
+
waitForWebSocketBufferDrain,
|
|
4157
4187
|
WORKFLOW_DESERIALIZE as WORKFLOW_DESERIALIZE5,
|
|
4158
4188
|
WORKFLOW_SERIALIZE as WORKFLOW_SERIALIZE5
|
|
4159
4189
|
} from "@ai-sdk/provider-utils";
|
|
@@ -4404,19 +4434,21 @@ function createXaiStreamingTranscriptionStream({
|
|
|
4404
4434
|
};
|
|
4405
4435
|
return new ReadableStream({
|
|
4406
4436
|
start: (controller) => {
|
|
4407
|
-
const WebSocketConstructor = getWebSocketConstructor(webSocket);
|
|
4408
|
-
const ws = new WebSocketConstructor(url, void 0, { headers });
|
|
4409
4437
|
const doneTexts = /* @__PURE__ */ new Map();
|
|
4438
|
+
const finalizedTexts = /* @__PURE__ */ new Map();
|
|
4439
|
+
const pendingTexts = /* @__PURE__ */ new Map();
|
|
4410
4440
|
let doneDuration;
|
|
4411
4441
|
let audioReader;
|
|
4442
|
+
let connection;
|
|
4412
4443
|
cleanup = (closeCode) => {
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4444
|
+
if (audioReader != null) {
|
|
4445
|
+
void audioReader.cancel().catch(() => {
|
|
4446
|
+
});
|
|
4447
|
+
} else {
|
|
4448
|
+
void audio.cancel().catch(() => {
|
|
4449
|
+
});
|
|
4419
4450
|
}
|
|
4451
|
+
connection == null ? void 0 : connection.close(closeCode);
|
|
4420
4452
|
};
|
|
4421
4453
|
const finishWithError = (error) => {
|
|
4422
4454
|
if (finished) return;
|
|
@@ -4438,35 +4470,34 @@ function createXaiStreamingTranscriptionStream({
|
|
|
4438
4470
|
controller.close();
|
|
4439
4471
|
cleanup(1e3);
|
|
4440
4472
|
};
|
|
4441
|
-
const
|
|
4442
|
-
var _a;
|
|
4443
|
-
finishWithError((_a = abortSignal == null ? void 0 : abortSignal.reason) != null ? _a : new Error("Aborted"));
|
|
4444
|
-
};
|
|
4445
|
-
if (abortSignal == null ? void 0 : abortSignal.aborted) {
|
|
4446
|
-
abort();
|
|
4447
|
-
return;
|
|
4448
|
-
}
|
|
4449
|
-
abortSignal == null ? void 0 : abortSignal.addEventListener("abort", abort, { once: true });
|
|
4450
|
-
const sendAudio = async () => {
|
|
4473
|
+
const sendAudio = async (socket) => {
|
|
4451
4474
|
audioReader = audio.getReader();
|
|
4452
4475
|
try {
|
|
4453
4476
|
while (true) {
|
|
4454
4477
|
const { done, value } = await audioReader.read();
|
|
4455
4478
|
if (done || finished) break;
|
|
4456
|
-
|
|
4479
|
+
socket.send(
|
|
4457
4480
|
value instanceof Uint8Array ? value : convertBase64ToUint8Array(value)
|
|
4458
4481
|
);
|
|
4482
|
+
await waitForWebSocketBufferDrain(socket);
|
|
4459
4483
|
}
|
|
4460
4484
|
} finally {
|
|
4461
4485
|
audioReader.releaseLock();
|
|
4486
|
+
audioReader = void 0;
|
|
4462
4487
|
}
|
|
4463
4488
|
if (!finished) {
|
|
4464
|
-
|
|
4489
|
+
socket.send(JSON.stringify({ type: "audio.done" }));
|
|
4465
4490
|
}
|
|
4466
4491
|
};
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4492
|
+
connection = connectToWebSocket({
|
|
4493
|
+
url,
|
|
4494
|
+
headers,
|
|
4495
|
+
webSocket,
|
|
4496
|
+
abortSignal,
|
|
4497
|
+
onAbort: finishWithError,
|
|
4498
|
+
onProcessingError: finishWithError,
|
|
4499
|
+
onMessageText: async (text) => {
|
|
4500
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
4470
4501
|
const parsed = await safeParseJSON2({ text });
|
|
4471
4502
|
if (!parsed.success) return;
|
|
4472
4503
|
const raw = parsed.value;
|
|
@@ -4476,25 +4507,39 @@ function createXaiStreamingTranscriptionStream({
|
|
|
4476
4507
|
switch (raw.type) {
|
|
4477
4508
|
case "transcript.created": {
|
|
4478
4509
|
controller.enqueue({ type: "stream-start", warnings });
|
|
4479
|
-
void
|
|
4510
|
+
const socket = connection == null ? void 0 : connection.socket;
|
|
4511
|
+
if (socket == null) {
|
|
4512
|
+
finishWithError(new Error("WebSocket is not connected."));
|
|
4513
|
+
break;
|
|
4514
|
+
}
|
|
4515
|
+
void sendAudio(socket).catch(finishWithError);
|
|
4480
4516
|
break;
|
|
4481
4517
|
}
|
|
4482
4518
|
case "transcript.partial": {
|
|
4483
4519
|
const id = channelId(raw.channel_index);
|
|
4484
|
-
const
|
|
4485
|
-
if (raw.is_final) {
|
|
4520
|
+
const channelIndex = (_a = raw.channel_index) != null ? _a : 0;
|
|
4521
|
+
if (raw.is_final && raw.speech_final) {
|
|
4522
|
+
const timing = timingFromXaiEvent(raw);
|
|
4523
|
+
if (raw.text) {
|
|
4524
|
+
finalizedTexts.set(channelIndex, [
|
|
4525
|
+
...(_b = finalizedTexts.get(channelIndex)) != null ? _b : [],
|
|
4526
|
+
raw.text
|
|
4527
|
+
]);
|
|
4528
|
+
}
|
|
4529
|
+
pendingTexts.delete(channelIndex);
|
|
4486
4530
|
controller.enqueue({
|
|
4487
4531
|
type: "transcript-final",
|
|
4488
4532
|
id,
|
|
4489
|
-
text: (
|
|
4533
|
+
text: (_c = raw.text) != null ? _c : "",
|
|
4490
4534
|
...timing,
|
|
4491
4535
|
channelIndex: raw.channel_index
|
|
4492
4536
|
});
|
|
4493
4537
|
} else {
|
|
4538
|
+
pendingTexts.set(channelIndex, (_d = raw.text) != null ? _d : "");
|
|
4494
4539
|
controller.enqueue({
|
|
4495
4540
|
type: "transcript-partial",
|
|
4496
4541
|
id,
|
|
4497
|
-
text: (
|
|
4542
|
+
text: (_e = raw.text) != null ? _e : "",
|
|
4498
4543
|
startSecond: raw.start,
|
|
4499
4544
|
durationInSeconds: raw.duration,
|
|
4500
4545
|
channelIndex: raw.channel_index
|
|
@@ -4503,32 +4548,36 @@ function createXaiStreamingTranscriptionStream({
|
|
|
4503
4548
|
break;
|
|
4504
4549
|
}
|
|
4505
4550
|
case "transcript.done": {
|
|
4506
|
-
const channelIndex = (
|
|
4507
|
-
|
|
4508
|
-
|
|
4551
|
+
const channelIndex = (_f = raw.channel_index) != null ? _f : 0;
|
|
4552
|
+
const accumulated = [
|
|
4553
|
+
...(_g = finalizedTexts.get(channelIndex)) != null ? _g : [],
|
|
4554
|
+
...pendingTexts.get(channelIndex) ? [pendingTexts.get(channelIndex)] : []
|
|
4555
|
+
].join(" ");
|
|
4556
|
+
doneTexts.set(channelIndex, raw.text || accumulated);
|
|
4557
|
+
doneDuration = (_h = raw.duration) != null ? _h : doneDuration;
|
|
4509
4558
|
maybeFinish();
|
|
4510
4559
|
break;
|
|
4511
4560
|
}
|
|
4512
4561
|
case "error": {
|
|
4513
|
-
finishWithError(new Error((
|
|
4562
|
+
finishWithError(new Error((_i = raw.message) != null ? _i : "xAI STT error"));
|
|
4514
4563
|
break;
|
|
4515
4564
|
}
|
|
4516
4565
|
}
|
|
4517
|
-
}
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
)
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
};
|
|
4566
|
+
},
|
|
4567
|
+
onSocketError: () => {
|
|
4568
|
+
finishWithError(
|
|
4569
|
+
new Error(
|
|
4570
|
+
"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 })." : "")
|
|
4571
|
+
)
|
|
4572
|
+
);
|
|
4573
|
+
},
|
|
4574
|
+
onClose: () => {
|
|
4575
|
+
if (finished) return;
|
|
4576
|
+
finished = true;
|
|
4577
|
+
cleanup();
|
|
4578
|
+
controller.close();
|
|
4579
|
+
}
|
|
4580
|
+
});
|
|
4532
4581
|
},
|
|
4533
4582
|
cancel: () => {
|
|
4534
4583
|
if (finished) return;
|