Skip to content

Commit 67d20a1

Browse files
committed
feat(mistral): implement Mistral TTS plugin and STT configs
1 parent 1dd25ee commit 67d20a1

3 files changed

Lines changed: 35 additions & 18 deletions

File tree

plugins/mistral/src/models.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@ export type MistralChatModels =
1616

1717
export type MistralSTTModels =
1818
| 'voxtral-mini-transcribe-realtime-2602' //realtime streaming
19-
| 'voxtral-small-latest' //chat completions
20-
| 'voxtral-mini-latest' //chat completions
21-
| 'voxtral-mini-transcribe'; //chat completions
19+
| 'voxtral-mini-2602' //batch transcription
20+
| 'voxtral-mini-transcribe-2507'; //batch transcription (deprecated)
2221

2322
export type MistralTTSModels = 'voxtral-mini-tts-2603';

plugins/mistral/src/stt.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ export class SpeechStream extends stt.SpeechStream {
199199

200200
let connection: any;
201201
let sendAudioTask: Promise<void> | undefined;
202+
let sendError: unknown;
202203

203204
try {
204205
connection = await this.#client.connect(this.#stt.options.liveModel, {
@@ -233,8 +234,11 @@ export class SpeechStream extends stt.SpeechStream {
233234
);
234235
await connection.sendAudio(new Uint8Array(pcmBuffer));
235236
}
236-
} catch (err) {
237-
// Stream writing closed or errored
237+
} catch (err: unknown) {
238+
if (!stopRequested) {
239+
sendError = err;
240+
connection.close().catch(() => {});
241+
}
238242
} finally {
239243
if (!connection.isClosed) {
240244
await connection.flushAudio().catch(() => {});
@@ -342,7 +346,13 @@ export class SpeechStream extends stt.SpeechStream {
342346
});
343347
}
344348
}
349+
350+
if (sendError) {
351+
throw sendError;
352+
}
345353
} catch (error: unknown) {
354+
error = sendError ?? error;
355+
346356
// An aborted signal means the stream was intentionally closed — do not
347357
// wrap into APIConnectionError, which would trigger the retry loop.
348358
if (this.abortController.signal.aborted) throw error;
@@ -386,7 +396,7 @@ export class SpeechStream extends stt.SpeechStream {
386396
stopRequested = true;
387397
resolveAbortTask();
388398
if (connection) {
389-
await connection.close();
399+
await connection.close().catch(() => {});
390400
}
391401
if (sendAudioTask) {
392402
await sendAudioTask;

plugins/mistral/src/tts.ts

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,12 @@ export class TTS extends tts.TTS {
9393
}));
9494
}
9595

96-
synthesize(text: string, connOptions?: APIConnectOptions): ChunkedStream {
97-
return new ChunkedStream(this, text, this.#client, this.#opts, connOptions);
96+
synthesize(
97+
text: string,
98+
connOptions?: APIConnectOptions,
99+
abortSignal?: AbortSignal,
100+
): ChunkedStream {
101+
return new ChunkedStream(this, text, this.#client, this.#opts, connOptions, abortSignal);
98102
}
99103

100104
stream(): tts.SynthesizeStream {
@@ -118,8 +122,9 @@ export class ChunkedStream extends tts.ChunkedStream {
118122
client: Mistral,
119123
opts: TTSOptions,
120124
connOptions?: APIConnectOptions,
125+
abortSignal?: AbortSignal,
121126
) {
122-
super(text, ttsInstance, connOptions);
127+
super(text, ttsInstance, connOptions, abortSignal);
123128
this.#client = client;
124129
this.#opts = opts;
125130
this.#text = text;
@@ -128,13 +133,18 @@ export class ChunkedStream extends tts.ChunkedStream {
128133
protected async run(): Promise<void> {
129134
const logger = log();
130135
try {
131-
const eventStream = await this.#client.audio.speech.complete({
132-
input: this.#text,
133-
model: this.#opts.model ?? 'voxtral-mini-tts-2603',
134-
voiceId: this.#opts.voiceId,
135-
responseFormat: 'pcm',
136-
stream: true,
137-
});
136+
const eventStream = await this.#client.audio.speech.complete(
137+
{
138+
input: this.#text,
139+
model: this.#opts.model ?? 'voxtral-mini-tts-2603',
140+
voiceId: this.#opts.voiceId,
141+
responseFormat: 'pcm',
142+
stream: true,
143+
},
144+
{
145+
fetchOptions: { signal: this.abortController?.signal },
146+
},
147+
);
138148

139149
const requestId = crypto.randomUUID();
140150
const segmentId = crypto.randomUUID();
@@ -206,8 +216,6 @@ export class ChunkedStream extends tts.ChunkedStream {
206216
message: `Mistral TTS: ${err.message ?? 'unknown error'}`,
207217
options: { retryable: true },
208218
});
209-
} finally {
210-
this.queue.close();
211219
}
212220
}
213221
}

0 commit comments

Comments
 (0)