@ai-sdk/xai 4.0.39 → 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 +15 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +108 -51
- package/dist/index.js.map +1 -1
- package/docs/01-xai.mdx +39 -0
- package/package.json +1 -1
- 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/docs/01-xai.mdx
CHANGED
|
@@ -746,6 +746,45 @@ const result = await generateSpeech({
|
|
|
746
746
|
|
|
747
747
|
Whether to normalize written-form input text before synthesizing speech.
|
|
748
748
|
|
|
749
|
+
- **withTimestamps** _boolean_
|
|
750
|
+
|
|
751
|
+
Return character-level timing metadata alongside the audio. The timing data
|
|
752
|
+
and total duration are exposed via `providerMetadata.xai` (see below).
|
|
753
|
+
|
|
754
|
+
- **replace** _Record<string, string>_
|
|
755
|
+
|
|
756
|
+
Map of phrases to spoken substitutions applied before synthesis. Values may
|
|
757
|
+
be respellings (`{ 'Acme Mobile': 'Acme Mobull' }`) or IPA phonetics
|
|
758
|
+
(`{ nginx: '/ˈɛndʒɪn ˈɛks/' }`).
|
|
759
|
+
|
|
760
|
+
### Provider Metadata
|
|
761
|
+
|
|
762
|
+
xAI speech results include provider-specific metadata under
|
|
763
|
+
`providerMetadata.xai`:
|
|
764
|
+
|
|
765
|
+
- **traceId** _string_ — the xAI trace ID for the request, useful for
|
|
766
|
+
debugging with xAI support.
|
|
767
|
+
- **duration** _number_ — total audio duration in seconds (only with
|
|
768
|
+
`withTimestamps`).
|
|
769
|
+
- **contentType** _string_ — MIME type of the decoded audio, e.g.
|
|
770
|
+
`'audio/mpeg'` (only with `withTimestamps`).
|
|
771
|
+
- **audioTimestamps** `{ graphChars: string[]; graphTimes: [number, number][] }` —
|
|
772
|
+
per-character alignment data (only with `withTimestamps`). `graphChars[i]`
|
|
773
|
+
is the character spoken during the `[start, end]` interval
|
|
774
|
+
`graphTimes[i]`, in seconds.
|
|
775
|
+
|
|
776
|
+
```ts
|
|
777
|
+
const result = await generateSpeech({
|
|
778
|
+
model: xai.speech(),
|
|
779
|
+
text: 'Hello world.',
|
|
780
|
+
providerOptions: {
|
|
781
|
+
xai: { withTimestamps: true } satisfies XaiSpeechModelOptions,
|
|
782
|
+
},
|
|
783
|
+
});
|
|
784
|
+
|
|
785
|
+
const { traceId, duration, audioTimestamps } = result.providerMetadata.xai;
|
|
786
|
+
```
|
|
787
|
+
|
|
749
788
|
### Model Capabilities
|
|
750
789
|
|
|
751
790
|
| Model | Language | Speed | Output Formats |
|
package/package.json
CHANGED
package/src/xai-error.ts
CHANGED
|
@@ -15,15 +15,25 @@ const responsesErrorSchema = z.object({
|
|
|
15
15
|
error: z.string(),
|
|
16
16
|
});
|
|
17
17
|
|
|
18
|
+
// Text to Speech error shape, e.g. {"error":"speed must be between 0.7 and 1.5"}
|
|
19
|
+
const speechErrorSchema = z.object({
|
|
20
|
+
error: z.string(),
|
|
21
|
+
});
|
|
22
|
+
|
|
18
23
|
export const xaiErrorDataSchema = z.union([
|
|
19
24
|
chatCompletionsErrorSchema,
|
|
20
25
|
responsesErrorSchema,
|
|
26
|
+
speechErrorSchema,
|
|
21
27
|
]);
|
|
22
28
|
|
|
23
29
|
export type XaiErrorData = z.infer<typeof xaiErrorDataSchema>;
|
|
24
30
|
|
|
25
31
|
export const xaiFailedResponseHandler = createJsonErrorResponseHandler({
|
|
26
32
|
errorSchema: xaiErrorDataSchema,
|
|
27
|
-
errorToMessage: data =>
|
|
28
|
-
|
|
33
|
+
errorToMessage: data => {
|
|
34
|
+
if (typeof data.error === 'string') {
|
|
35
|
+
return 'code' in data ? `${data.code}: ${data.error}` : data.error;
|
|
36
|
+
}
|
|
37
|
+
return data.error.message;
|
|
38
|
+
},
|
|
29
39
|
});
|
|
@@ -46,6 +46,20 @@ export const xaiSpeechModelOptionsSchema = lazySchema(() =>
|
|
|
46
46
|
* Normalize written-form text into spoken-form text before synthesis.
|
|
47
47
|
*/
|
|
48
48
|
textNormalization: z.boolean().nullish(),
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Return character-level timing metadata alongside the audio. When
|
|
52
|
+
* enabled, the response carries per-character start/end times and the
|
|
53
|
+
* total duration, exposed via `providerMetadata.xai`.
|
|
54
|
+
*/
|
|
55
|
+
withTimestamps: z.boolean().nullish(),
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Map of phrases to spoken substitutions applied before synthesis.
|
|
59
|
+
* Values may be respellings (`{ 'Acme Mobile': 'Acme Mobull' }`) or IPA
|
|
60
|
+
* phonetics (`{ nginx: '/ˈɛndʒɪn ˈɛks/' }`).
|
|
61
|
+
*/
|
|
62
|
+
replace: z.record(z.string(), z.string()).nullish(),
|
|
49
63
|
}),
|
|
50
64
|
),
|
|
51
65
|
);
|
package/src/xai-speech-model.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { SharedV4Warning, SpeechModelV4 } from '@ai-sdk/provider';
|
|
2
2
|
import {
|
|
3
3
|
combineHeaders,
|
|
4
|
+
convertBase64ToUint8Array,
|
|
4
5
|
createBinaryResponseHandler,
|
|
6
|
+
createJsonResponseHandler,
|
|
5
7
|
parseProviderOptions,
|
|
6
8
|
postJsonToApi,
|
|
7
9
|
resolve,
|
|
@@ -11,6 +13,7 @@ import {
|
|
|
11
13
|
type FetchFunction,
|
|
12
14
|
type Resolvable,
|
|
13
15
|
} from '@ai-sdk/provider-utils';
|
|
16
|
+
import { z } from 'zod/v4';
|
|
14
17
|
import { xaiFailedResponseHandler } from './xai-error';
|
|
15
18
|
import { xaiSpeechModelOptionsSchema } from './xai-speech-model-options';
|
|
16
19
|
|
|
@@ -122,22 +125,30 @@ export class XaiSpeechModel implements SpeechModelV4 {
|
|
|
122
125
|
speed,
|
|
123
126
|
optimize_streaming_latency: xaiOptions?.optimizeStreamingLatency,
|
|
124
127
|
text_normalization: xaiOptions?.textNormalization,
|
|
128
|
+
with_timestamps: xaiOptions?.withTimestamps,
|
|
129
|
+
replace: xaiOptions?.replace,
|
|
125
130
|
};
|
|
126
131
|
|
|
127
|
-
return {
|
|
132
|
+
return {
|
|
133
|
+
requestBody,
|
|
134
|
+
warnings,
|
|
135
|
+
withTimestamps: xaiOptions?.withTimestamps === true,
|
|
136
|
+
};
|
|
128
137
|
}
|
|
129
138
|
|
|
130
139
|
async doGenerate(
|
|
131
140
|
options: Parameters<SpeechModelV4['doGenerate']>[0],
|
|
132
141
|
): Promise<Awaited<ReturnType<SpeechModelV4['doGenerate']>>> {
|
|
133
142
|
const currentDate = this.config._internal?.currentDate?.() ?? new Date();
|
|
134
|
-
const { requestBody, warnings } =
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
} = await postJsonToApi
|
|
143
|
+
const { requestBody, warnings, withTimestamps } =
|
|
144
|
+
await this.getArgs(options);
|
|
145
|
+
|
|
146
|
+
// With `with_timestamps` the API returns a JSON envelope carrying
|
|
147
|
+
// base64-encoded audio plus character-level timings instead of raw
|
|
148
|
+
// audio bytes.
|
|
149
|
+
const { value, responseHeaders, rawValue } = await postJsonToApi<
|
|
150
|
+
Uint8Array | XaiSpeechTimestampsResponse
|
|
151
|
+
>({
|
|
141
152
|
url: `${this.config.baseURL}/tts`,
|
|
142
153
|
headers: combineHeaders(
|
|
143
154
|
this.config.headers ? await resolve(this.config.headers) : undefined,
|
|
@@ -145,11 +156,30 @@ export class XaiSpeechModel implements SpeechModelV4 {
|
|
|
145
156
|
),
|
|
146
157
|
body: requestBody,
|
|
147
158
|
failedResponseHandler: xaiFailedResponseHandler,
|
|
148
|
-
successfulResponseHandler:
|
|
159
|
+
successfulResponseHandler: withTimestamps
|
|
160
|
+
? createJsonResponseHandler(xaiSpeechTimestampsResponseSchema)
|
|
161
|
+
: createBinaryResponseHandler(),
|
|
149
162
|
abortSignal: options.abortSignal,
|
|
150
163
|
fetch: this.config.fetch,
|
|
151
164
|
});
|
|
152
165
|
|
|
166
|
+
let audio: Uint8Array;
|
|
167
|
+
let envelope: XaiSpeechTimestampsResponse | undefined;
|
|
168
|
+
if (value instanceof Uint8Array) {
|
|
169
|
+
audio = value;
|
|
170
|
+
} else {
|
|
171
|
+
envelope = value;
|
|
172
|
+
// Empty audio is returned as-is so the core layer throws
|
|
173
|
+
// NoSpeechGeneratedError.
|
|
174
|
+
audio =
|
|
175
|
+
envelope.audio != null
|
|
176
|
+
? convertBase64ToUint8Array(envelope.audio)
|
|
177
|
+
: new Uint8Array(0);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// xAI returns a trace id on every response (success and error).
|
|
181
|
+
const traceId = responseHeaders?.['x-trace-id'];
|
|
182
|
+
|
|
153
183
|
return {
|
|
154
184
|
audio,
|
|
155
185
|
warnings,
|
|
@@ -160,8 +190,46 @@ export class XaiSpeechModel implements SpeechModelV4 {
|
|
|
160
190
|
timestamp: currentDate,
|
|
161
191
|
modelId: this.modelId,
|
|
162
192
|
headers: responseHeaders,
|
|
163
|
-
body:
|
|
193
|
+
body: rawValue,
|
|
194
|
+
},
|
|
195
|
+
providerMetadata: {
|
|
196
|
+
xai: {
|
|
197
|
+
...(traceId != null ? { traceId } : {}),
|
|
198
|
+
...(envelope?.duration != null
|
|
199
|
+
? { duration: envelope.duration }
|
|
200
|
+
: {}),
|
|
201
|
+
...(envelope?.content_type != null
|
|
202
|
+
? { contentType: envelope.content_type }
|
|
203
|
+
: {}),
|
|
204
|
+
...(envelope?.audio_timestamps != null
|
|
205
|
+
? {
|
|
206
|
+
audioTimestamps: {
|
|
207
|
+
graphChars: envelope.audio_timestamps.graph_chars,
|
|
208
|
+
graphTimes: envelope.audio_timestamps.graph_times,
|
|
209
|
+
},
|
|
210
|
+
}
|
|
211
|
+
: {}),
|
|
212
|
+
},
|
|
164
213
|
},
|
|
165
214
|
};
|
|
166
215
|
}
|
|
167
216
|
}
|
|
217
|
+
|
|
218
|
+
// Minimal schema for the `with_timestamps` JSON envelope: only the fields
|
|
219
|
+
// the implementation reads, with `.nullish()` so provider API changes don't
|
|
220
|
+
// break parsing.
|
|
221
|
+
const xaiSpeechTimestampsResponseSchema = z.object({
|
|
222
|
+
audio: z.string().nullish(),
|
|
223
|
+
content_type: z.string().nullish(),
|
|
224
|
+
duration: z.number().nullish(),
|
|
225
|
+
audio_timestamps: z
|
|
226
|
+
.object({
|
|
227
|
+
graph_chars: z.array(z.string()),
|
|
228
|
+
graph_times: z.array(z.tuple([z.number(), z.number()])),
|
|
229
|
+
})
|
|
230
|
+
.nullish(),
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
type XaiSpeechTimestampsResponse = z.infer<
|
|
234
|
+
typeof xaiSpeechTimestampsResponseSchema
|
|
235
|
+
>;
|