@ai-sdk/xai 4.0.38 → 4.0.40
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 +21 -0
- package/README.md +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.js +126 -59
- package/dist/index.js.map +1 -1
- package/docs/01-xai.mdx +55 -16
- package/package.json +3 -3
- package/src/xai-error.ts +12 -2
- package/src/xai-speech-model-options.ts +14 -0
- package/src/xai-speech-model.ts +78 -10
- package/src/xai-video-model.ts +19 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# @ai-sdk/xai
|
|
2
2
|
|
|
3
|
+
## 4.0.40
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 1ffa1d2: feat(xai): speech timestamps, pronunciation replacements, provider metadata, and error parsing
|
|
8
|
+
|
|
9
|
+
- Add `withTimestamps` and `replace` provider options for text to speech. With
|
|
10
|
+
`withTimestamps`, the JSON envelope is decoded and the audio returned as
|
|
11
|
+
usual, while duration, content type, and character-level alignment are
|
|
12
|
+
exposed via `providerMetadata.xai`.
|
|
13
|
+
- Return `providerMetadata.xai.traceId` (from the `x-trace-id` response
|
|
14
|
+
header) on every speech response.
|
|
15
|
+
- Parse the text to speech error shape (`{"error":"..."}`) so `APICallError`
|
|
16
|
+
messages carry xAI's real error detail instead of the HTTP reason phrase.
|
|
17
|
+
|
|
18
|
+
## 4.0.39
|
|
19
|
+
|
|
20
|
+
### Patch Changes
|
|
21
|
+
|
|
22
|
+
- 646c86e: fix(provider/xai): report video moderation blocks and missing URLs as an error status instead of throwing
|
|
23
|
+
|
|
3
24
|
## 4.0.38
|
|
4
25
|
|
|
5
26
|
### Patch Changes
|
package/README.md
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -65,6 +65,8 @@ declare const xaiErrorDataSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
|
65
65
|
}, z.core.$strip>, z.ZodObject<{
|
|
66
66
|
code: z.ZodString;
|
|
67
67
|
error: z.ZodString;
|
|
68
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
69
|
+
error: z.ZodString;
|
|
68
70
|
}, z.core.$strip>]>;
|
|
69
71
|
type XaiErrorData = z.infer<typeof xaiErrorDataSchema>;
|
|
70
72
|
|
|
@@ -218,6 +220,8 @@ declare const xaiSpeechModelOptionsSchema: _ai_sdk_provider_utils.LazySchema<{
|
|
|
218
220
|
bitRate?: 32000 | 64000 | 96000 | 128000 | 192000 | null | undefined;
|
|
219
221
|
optimizeStreamingLatency?: 0 | 1 | 2 | null | undefined;
|
|
220
222
|
textNormalization?: boolean | null | undefined;
|
|
223
|
+
withTimestamps?: boolean | null | undefined;
|
|
224
|
+
replace?: Record<string, string> | null | undefined;
|
|
221
225
|
}>;
|
|
222
226
|
type XaiSpeechModelOptions = InferSchema<typeof xaiSpeechModelOptionsSchema>;
|
|
223
227
|
|
package/dist/index.js
CHANGED
|
@@ -384,13 +384,22 @@ var responsesErrorSchema = z3.object({
|
|
|
384
384
|
code: z3.string(),
|
|
385
385
|
error: z3.string()
|
|
386
386
|
});
|
|
387
|
+
var speechErrorSchema = z3.object({
|
|
388
|
+
error: z3.string()
|
|
389
|
+
});
|
|
387
390
|
var xaiErrorDataSchema = z3.union([
|
|
388
391
|
chatCompletionsErrorSchema,
|
|
389
|
-
responsesErrorSchema
|
|
392
|
+
responsesErrorSchema,
|
|
393
|
+
speechErrorSchema
|
|
390
394
|
]);
|
|
391
395
|
var xaiFailedResponseHandler = createJsonErrorResponseHandler({
|
|
392
396
|
errorSchema: xaiErrorDataSchema,
|
|
393
|
-
errorToMessage: (data) =>
|
|
397
|
+
errorToMessage: (data) => {
|
|
398
|
+
if (typeof data.error === "string") {
|
|
399
|
+
return "code" in data ? `${data.code}: ${data.error}` : data.error;
|
|
400
|
+
}
|
|
401
|
+
return data.error.message;
|
|
402
|
+
}
|
|
394
403
|
});
|
|
395
404
|
|
|
396
405
|
// src/xai-prepare-tools.ts
|
|
@@ -3720,7 +3729,7 @@ var xaiTools = {
|
|
|
3720
3729
|
};
|
|
3721
3730
|
|
|
3722
3731
|
// src/version.ts
|
|
3723
|
-
var VERSION = true ? "4.0.
|
|
3732
|
+
var VERSION = true ? "4.0.40" : "0.0.0-test";
|
|
3724
3733
|
|
|
3725
3734
|
// src/files/xai-files.ts
|
|
3726
3735
|
import {
|
|
@@ -4218,16 +4227,26 @@ var XaiVideoModel = class {
|
|
|
4218
4227
|
}
|
|
4219
4228
|
if (statusResponse.status === "done" || statusResponse.status == null && ((_h = statusResponse.video) == null ? void 0 : _h.url)) {
|
|
4220
4229
|
if (((_i = statusResponse.video) == null ? void 0 : _i.respect_moderation) === false) {
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4230
|
+
return {
|
|
4231
|
+
status: "error",
|
|
4232
|
+
error: "Video generation was blocked due to a content policy violation.",
|
|
4233
|
+
response: {
|
|
4234
|
+
timestamp: currentDate,
|
|
4235
|
+
modelId: this.modelId,
|
|
4236
|
+
headers: responseHeaders
|
|
4237
|
+
}
|
|
4238
|
+
};
|
|
4225
4239
|
}
|
|
4226
4240
|
if (!((_j = statusResponse.video) == null ? void 0 : _j.url)) {
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
|
|
4230
|
-
|
|
4241
|
+
return {
|
|
4242
|
+
status: "error",
|
|
4243
|
+
error: "Video generation completed but no video URL was returned.",
|
|
4244
|
+
response: {
|
|
4245
|
+
timestamp: currentDate,
|
|
4246
|
+
modelId: this.modelId,
|
|
4247
|
+
headers: responseHeaders
|
|
4248
|
+
}
|
|
4249
|
+
};
|
|
4231
4250
|
}
|
|
4232
4251
|
return {
|
|
4233
4252
|
status: "completed",
|
|
@@ -4350,7 +4369,9 @@ var xaiVideoStatusResponseHandler = async (options) => {
|
|
|
4350
4369
|
// src/xai-speech-model.ts
|
|
4351
4370
|
import {
|
|
4352
4371
|
combineHeaders as combineHeaders6,
|
|
4372
|
+
convertBase64ToUint8Array,
|
|
4353
4373
|
createBinaryResponseHandler as createBinaryResponseHandler2,
|
|
4374
|
+
createJsonResponseHandler as createJsonResponseHandler6,
|
|
4354
4375
|
parseProviderOptions as parseProviderOptions8,
|
|
4355
4376
|
postJsonToApi as postJsonToApi5,
|
|
4356
4377
|
resolve,
|
|
@@ -4358,6 +4379,7 @@ import {
|
|
|
4358
4379
|
WORKFLOW_DESERIALIZE as WORKFLOW_DESERIALIZE4,
|
|
4359
4380
|
WORKFLOW_SERIALIZE as WORKFLOW_SERIALIZE4
|
|
4360
4381
|
} from "@ai-sdk/provider-utils";
|
|
4382
|
+
import { z as z22 } from "zod/v4";
|
|
4361
4383
|
|
|
4362
4384
|
// src/xai-speech-model-options.ts
|
|
4363
4385
|
import {
|
|
@@ -4396,7 +4418,19 @@ var xaiSpeechModelOptionsSchema = lazySchema9(
|
|
|
4396
4418
|
/**
|
|
4397
4419
|
* Normalize written-form text into spoken-form text before synthesis.
|
|
4398
4420
|
*/
|
|
4399
|
-
textNormalization: z21.boolean().nullish()
|
|
4421
|
+
textNormalization: z21.boolean().nullish(),
|
|
4422
|
+
/**
|
|
4423
|
+
* Return character-level timing metadata alongside the audio. When
|
|
4424
|
+
* enabled, the response carries per-character start/end times and the
|
|
4425
|
+
* total duration, exposed via `providerMetadata.xai`.
|
|
4426
|
+
*/
|
|
4427
|
+
withTimestamps: z21.boolean().nullish(),
|
|
4428
|
+
/**
|
|
4429
|
+
* Map of phrases to spoken substitutions applied before synthesis.
|
|
4430
|
+
* Values may be respellings (`{ 'Acme Mobile': 'Acme Mobull' }`) or IPA
|
|
4431
|
+
* phonetics (`{ nginx: '/ˈɛndʒɪn ˈɛks/' }`).
|
|
4432
|
+
*/
|
|
4433
|
+
replace: z21.record(z21.string(), z21.string()).nullish()
|
|
4400
4434
|
})
|
|
4401
4435
|
)
|
|
4402
4436
|
);
|
|
@@ -4476,19 +4510,21 @@ var XaiSpeechModel = class _XaiSpeechModel {
|
|
|
4476
4510
|
output_format,
|
|
4477
4511
|
speed,
|
|
4478
4512
|
optimize_streaming_latency: xaiOptions == null ? void 0 : xaiOptions.optimizeStreamingLatency,
|
|
4479
|
-
text_normalization: xaiOptions == null ? void 0 : xaiOptions.textNormalization
|
|
4513
|
+
text_normalization: xaiOptions == null ? void 0 : xaiOptions.textNormalization,
|
|
4514
|
+
with_timestamps: xaiOptions == null ? void 0 : xaiOptions.withTimestamps,
|
|
4515
|
+
replace: xaiOptions == null ? void 0 : xaiOptions.replace
|
|
4516
|
+
};
|
|
4517
|
+
return {
|
|
4518
|
+
requestBody,
|
|
4519
|
+
warnings,
|
|
4520
|
+
withTimestamps: (xaiOptions == null ? void 0 : xaiOptions.withTimestamps) === true
|
|
4480
4521
|
};
|
|
4481
|
-
return { requestBody, warnings };
|
|
4482
4522
|
}
|
|
4483
4523
|
async doGenerate(options) {
|
|
4484
4524
|
var _a, _b, _c;
|
|
4485
4525
|
const currentDate = (_c = (_b = (_a = this.config._internal) == null ? void 0 : _a.currentDate) == null ? void 0 : _b.call(_a)) != null ? _c : /* @__PURE__ */ new Date();
|
|
4486
|
-
const { requestBody, warnings } = await this.getArgs(options);
|
|
4487
|
-
const {
|
|
4488
|
-
value: audio,
|
|
4489
|
-
responseHeaders,
|
|
4490
|
-
rawValue: rawResponse
|
|
4491
|
-
} = await postJsonToApi5({
|
|
4526
|
+
const { requestBody, warnings, withTimestamps } = await this.getArgs(options);
|
|
4527
|
+
const { value, responseHeaders, rawValue } = await postJsonToApi5({
|
|
4492
4528
|
url: `${this.config.baseURL}/tts`,
|
|
4493
4529
|
headers: combineHeaders6(
|
|
4494
4530
|
this.config.headers ? await resolve(this.config.headers) : void 0,
|
|
@@ -4496,10 +4532,19 @@ var XaiSpeechModel = class _XaiSpeechModel {
|
|
|
4496
4532
|
),
|
|
4497
4533
|
body: requestBody,
|
|
4498
4534
|
failedResponseHandler: xaiFailedResponseHandler,
|
|
4499
|
-
successfulResponseHandler: createBinaryResponseHandler2(),
|
|
4535
|
+
successfulResponseHandler: withTimestamps ? createJsonResponseHandler6(xaiSpeechTimestampsResponseSchema) : createBinaryResponseHandler2(),
|
|
4500
4536
|
abortSignal: options.abortSignal,
|
|
4501
4537
|
fetch: this.config.fetch
|
|
4502
4538
|
});
|
|
4539
|
+
let audio;
|
|
4540
|
+
let envelope;
|
|
4541
|
+
if (value instanceof Uint8Array) {
|
|
4542
|
+
audio = value;
|
|
4543
|
+
} else {
|
|
4544
|
+
envelope = value;
|
|
4545
|
+
audio = envelope.audio != null ? convertBase64ToUint8Array(envelope.audio) : new Uint8Array(0);
|
|
4546
|
+
}
|
|
4547
|
+
const traceId = responseHeaders == null ? void 0 : responseHeaders["x-trace-id"];
|
|
4503
4548
|
return {
|
|
4504
4549
|
audio,
|
|
4505
4550
|
warnings,
|
|
@@ -4510,11 +4555,33 @@ var XaiSpeechModel = class _XaiSpeechModel {
|
|
|
4510
4555
|
timestamp: currentDate,
|
|
4511
4556
|
modelId: this.modelId,
|
|
4512
4557
|
headers: responseHeaders,
|
|
4513
|
-
body:
|
|
4558
|
+
body: rawValue
|
|
4559
|
+
},
|
|
4560
|
+
providerMetadata: {
|
|
4561
|
+
xai: {
|
|
4562
|
+
...traceId != null ? { traceId } : {},
|
|
4563
|
+
...(envelope == null ? void 0 : envelope.duration) != null ? { duration: envelope.duration } : {},
|
|
4564
|
+
...(envelope == null ? void 0 : envelope.content_type) != null ? { contentType: envelope.content_type } : {},
|
|
4565
|
+
...(envelope == null ? void 0 : envelope.audio_timestamps) != null ? {
|
|
4566
|
+
audioTimestamps: {
|
|
4567
|
+
graphChars: envelope.audio_timestamps.graph_chars,
|
|
4568
|
+
graphTimes: envelope.audio_timestamps.graph_times
|
|
4569
|
+
}
|
|
4570
|
+
} : {}
|
|
4571
|
+
}
|
|
4514
4572
|
}
|
|
4515
4573
|
};
|
|
4516
4574
|
}
|
|
4517
4575
|
};
|
|
4576
|
+
var xaiSpeechTimestampsResponseSchema = z22.object({
|
|
4577
|
+
audio: z22.string().nullish(),
|
|
4578
|
+
content_type: z22.string().nullish(),
|
|
4579
|
+
duration: z22.number().nullish(),
|
|
4580
|
+
audio_timestamps: z22.object({
|
|
4581
|
+
graph_chars: z22.array(z22.string()),
|
|
4582
|
+
graph_times: z22.array(z22.tuple([z22.number(), z22.number()]))
|
|
4583
|
+
}).nullish()
|
|
4584
|
+
});
|
|
4518
4585
|
|
|
4519
4586
|
// src/xai-transcription-model.ts
|
|
4520
4587
|
import {
|
|
@@ -4522,8 +4589,8 @@ import {
|
|
|
4522
4589
|
} from "@ai-sdk/provider";
|
|
4523
4590
|
import {
|
|
4524
4591
|
combineHeaders as combineHeaders7,
|
|
4525
|
-
convertBase64ToUint8Array,
|
|
4526
|
-
createJsonResponseHandler as
|
|
4592
|
+
convertBase64ToUint8Array as convertBase64ToUint8Array2,
|
|
4593
|
+
createJsonResponseHandler as createJsonResponseHandler7,
|
|
4527
4594
|
connectToWebSocket,
|
|
4528
4595
|
mediaTypeToExtension,
|
|
4529
4596
|
parseProviderOptions as parseProviderOptions9,
|
|
@@ -4535,80 +4602,80 @@ import {
|
|
|
4535
4602
|
WORKFLOW_DESERIALIZE as WORKFLOW_DESERIALIZE5,
|
|
4536
4603
|
WORKFLOW_SERIALIZE as WORKFLOW_SERIALIZE5
|
|
4537
4604
|
} from "@ai-sdk/provider-utils";
|
|
4538
|
-
import { z as
|
|
4605
|
+
import { z as z24 } from "zod/v4";
|
|
4539
4606
|
|
|
4540
4607
|
// src/xai-transcription-model-options.ts
|
|
4541
4608
|
import {
|
|
4542
4609
|
lazySchema as lazySchema10,
|
|
4543
4610
|
zodSchema as zodSchema10
|
|
4544
4611
|
} from "@ai-sdk/provider-utils";
|
|
4545
|
-
import { z as
|
|
4612
|
+
import { z as z23 } from "zod/v4";
|
|
4546
4613
|
var xaiTranscriptionModelOptionsSchema = lazySchema10(
|
|
4547
4614
|
() => zodSchema10(
|
|
4548
|
-
|
|
4615
|
+
z23.object({
|
|
4549
4616
|
/**
|
|
4550
4617
|
* Audio encoding for raw, headerless input audio.
|
|
4551
4618
|
*/
|
|
4552
|
-
audioFormat:
|
|
4619
|
+
audioFormat: z23.enum(["pcm", "mulaw", "alaw"]).nullish(),
|
|
4553
4620
|
/**
|
|
4554
4621
|
* Sample rate of the input audio in Hz.
|
|
4555
4622
|
*/
|
|
4556
|
-
sampleRate:
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
|
|
4561
|
-
|
|
4562
|
-
|
|
4623
|
+
sampleRate: z23.union([
|
|
4624
|
+
z23.literal(8e3),
|
|
4625
|
+
z23.literal(16e3),
|
|
4626
|
+
z23.literal(22050),
|
|
4627
|
+
z23.literal(24e3),
|
|
4628
|
+
z23.literal(44100),
|
|
4629
|
+
z23.literal(48e3)
|
|
4563
4630
|
]).nullish(),
|
|
4564
4631
|
/**
|
|
4565
4632
|
* Language code used for inverse text normalization.
|
|
4566
4633
|
*/
|
|
4567
|
-
language:
|
|
4634
|
+
language: z23.string().nullish(),
|
|
4568
4635
|
/**
|
|
4569
4636
|
* Enable inverse text normalization. Requires `language`.
|
|
4570
4637
|
*/
|
|
4571
|
-
format:
|
|
4638
|
+
format: z23.boolean().nullish(),
|
|
4572
4639
|
/**
|
|
4573
4640
|
* Enable per-channel transcription for multichannel audio.
|
|
4574
4641
|
*/
|
|
4575
|
-
multichannel:
|
|
4642
|
+
multichannel: z23.boolean().nullish(),
|
|
4576
4643
|
/**
|
|
4577
4644
|
* Number of interleaved audio channels.
|
|
4578
4645
|
*/
|
|
4579
|
-
channels:
|
|
4646
|
+
channels: z23.number().int().min(2).max(8).nullish(),
|
|
4580
4647
|
/**
|
|
4581
4648
|
* Enable speaker diarization.
|
|
4582
4649
|
*/
|
|
4583
|
-
diarize:
|
|
4650
|
+
diarize: z23.boolean().nullish(),
|
|
4584
4651
|
/**
|
|
4585
4652
|
* Terms to bias transcription toward.
|
|
4586
4653
|
*/
|
|
4587
|
-
keyterm:
|
|
4654
|
+
keyterm: z23.union([z23.string(), z23.array(z23.string())]).nullish(),
|
|
4588
4655
|
/**
|
|
4589
4656
|
* Include filler words such as "uh" and "um" in the transcript.
|
|
4590
4657
|
*/
|
|
4591
|
-
fillerWords:
|
|
4658
|
+
fillerWords: z23.boolean().nullish(),
|
|
4592
4659
|
/**
|
|
4593
4660
|
* Options for streaming speech-to-text over WebSocket.
|
|
4594
4661
|
*/
|
|
4595
|
-
streaming:
|
|
4662
|
+
streaming: z23.object({
|
|
4596
4663
|
/**
|
|
4597
4664
|
* Emit interim transcript results while speech is being processed.
|
|
4598
4665
|
*/
|
|
4599
|
-
interimResults:
|
|
4666
|
+
interimResults: z23.boolean().optional(),
|
|
4600
4667
|
/**
|
|
4601
4668
|
* Silence duration in milliseconds before an utterance-final event.
|
|
4602
4669
|
*/
|
|
4603
|
-
endpointing:
|
|
4670
|
+
endpointing: z23.number().int().min(0).max(5e3).optional(),
|
|
4604
4671
|
/**
|
|
4605
4672
|
* End-of-turn detection threshold. When set, enables Smart Turn.
|
|
4606
4673
|
*/
|
|
4607
|
-
smartTurn:
|
|
4674
|
+
smartTurn: z23.number().min(0).max(1).optional(),
|
|
4608
4675
|
/**
|
|
4609
4676
|
* Maximum silence duration in milliseconds before forcing speech_final.
|
|
4610
4677
|
*/
|
|
4611
|
-
smartTurnTimeout:
|
|
4678
|
+
smartTurnTimeout: z23.number().int().min(1).max(5e3).optional()
|
|
4612
4679
|
}).optional()
|
|
4613
4680
|
})
|
|
4614
4681
|
)
|
|
@@ -4666,7 +4733,7 @@ var XaiTranscriptionModel = class _XaiTranscriptionModel {
|
|
|
4666
4733
|
formData.append("keyterm", keyterm);
|
|
4667
4734
|
}
|
|
4668
4735
|
}
|
|
4669
|
-
const blob = audio instanceof Uint8Array ? new Blob([audio]) : new Blob([
|
|
4736
|
+
const blob = audio instanceof Uint8Array ? new Blob([audio]) : new Blob([convertBase64ToUint8Array2(audio)]);
|
|
4670
4737
|
const fileExtension = mediaTypeToExtension(mediaType);
|
|
4671
4738
|
formData.append(
|
|
4672
4739
|
"file",
|
|
@@ -4688,7 +4755,7 @@ var XaiTranscriptionModel = class _XaiTranscriptionModel {
|
|
|
4688
4755
|
headers: combineHeaders7((_f = (_e = this.config).headers) == null ? void 0 : _f.call(_e), options.headers),
|
|
4689
4756
|
formData,
|
|
4690
4757
|
failedResponseHandler: xaiFailedResponseHandler,
|
|
4691
|
-
successfulResponseHandler:
|
|
4758
|
+
successfulResponseHandler: createJsonResponseHandler7(
|
|
4692
4759
|
xaiTranscriptionResponseSchema
|
|
4693
4760
|
),
|
|
4694
4761
|
abortSignal: options.abortSignal,
|
|
@@ -4825,7 +4892,7 @@ function createXaiStreamingTranscriptionStream({
|
|
|
4825
4892
|
const { done, value } = await audioReader.read();
|
|
4826
4893
|
if (done || finished) break;
|
|
4827
4894
|
socket.send(
|
|
4828
|
-
value instanceof Uint8Array ? value :
|
|
4895
|
+
value instanceof Uint8Array ? value : convertBase64ToUint8Array2(value)
|
|
4829
4896
|
);
|
|
4830
4897
|
await waitForWebSocketBufferDrain(socket);
|
|
4831
4898
|
}
|
|
@@ -5007,15 +5074,15 @@ function timingFromXaiEvent(event) {
|
|
|
5007
5074
|
...event.start != null && event.duration != null ? { endSecond: event.start + event.duration } : {}
|
|
5008
5075
|
};
|
|
5009
5076
|
}
|
|
5010
|
-
var xaiTranscriptionResponseSchema =
|
|
5011
|
-
text:
|
|
5012
|
-
language:
|
|
5013
|
-
duration:
|
|
5014
|
-
words:
|
|
5015
|
-
|
|
5016
|
-
text:
|
|
5017
|
-
start:
|
|
5018
|
-
end:
|
|
5077
|
+
var xaiTranscriptionResponseSchema = z24.object({
|
|
5078
|
+
text: z24.string(),
|
|
5079
|
+
language: z24.string().nullish(),
|
|
5080
|
+
duration: z24.number().nullish(),
|
|
5081
|
+
words: z24.array(
|
|
5082
|
+
z24.object({
|
|
5083
|
+
text: z24.string(),
|
|
5084
|
+
start: z24.number(),
|
|
5085
|
+
end: z24.number()
|
|
5019
5086
|
})
|
|
5020
5087
|
).nullish()
|
|
5021
5088
|
});
|