@openclaw/mistral-provider 2026.10.1-beta.2 → 2026.10.5-beta.1

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.
@@ -49,13 +49,6 @@ function normalizeMistralRealtimeBaseUrl(value) {
49
49
  url.pathname = url.pathname.replace(/\/v1\/?$/, "").replace(/\/+$/, "");
50
50
  return url.toString().replace(/\/+$/, "");
51
51
  }
52
- function toMistralRealtimeWsUrl(config) {
53
- const base = new URL(`${normalizeMistralRealtimeBaseUrl(config.baseUrl)}/`);
54
- const url = new URL("v1/audio/transcriptions/realtime", base);
55
- url.searchParams.set("model", config.model);
56
- if (config.targetStreamingDelayMs != null) url.searchParams.set("target_streaming_delay_ms", String(config.targetStreamingDelayMs));
57
- return url.toString();
58
- }
59
52
  function normalizeProviderConfig(config) {
60
53
  const raw = readNestedMistralConfig(config);
61
54
  return {
@@ -87,107 +80,6 @@ function measureTranscriptDeltaBytes(partialText, delta) {
87
80
  const completesSplitSurrogatePair = previousCodeUnit >= 55296 && previousCodeUnit <= 56319 && nextCodeUnit >= 56320 && nextCodeUnit <= 57343;
88
81
  return Buffer.byteLength(delta, "utf8") - (completesSplitSurrogatePair ? 2 : 0);
89
82
  }
90
- function createMistralRealtimeTranscriptionSession(config, createRealtimeTranscriptionWebSocketSession) {
91
- let partialText = "";
92
- let partialBytes = 0;
93
- let hasFinalSegment = false;
94
- let terminal = false;
95
- const clearPartial = () => {
96
- partialText = "";
97
- partialBytes = 0;
98
- };
99
- const emitFinalTranscript = (text, source) => {
100
- if (!text.trim() || source === "pending" && !MISTRAL_REALTIME_SPEECH_CONTENT.test(text)) return;
101
- hasFinalSegment ||= source === "segment";
102
- config.onTranscript?.(text);
103
- };
104
- const failTerminal = (error, transport) => {
105
- if (terminal) return;
106
- terminal = true;
107
- clearPartial();
108
- transport.closeNow();
109
- try {
110
- config.onError?.(error);
111
- } catch {}
112
- };
113
- const handleEvent = (event, transport) => {
114
- if (terminal) return;
115
- if (event.type === "session.created") {
116
- clearPartial();
117
- hasFinalSegment = false;
118
- transport.sendJson({
119
- type: "session.update",
120
- session: { audio_format: {
121
- encoding: config.encoding,
122
- sample_rate: config.sampleRate
123
- } }
124
- });
125
- transport.markReady();
126
- return;
127
- }
128
- if (!transport.isReady() && event.type === "error") {
129
- transport.failConnect(new Error(readErrorDetail(event)));
130
- return;
131
- }
132
- switch (event.type) {
133
- case "transcription.text.delta":
134
- if (event.text) {
135
- const deltaBytes = measureTranscriptDeltaBytes(partialText, event.text);
136
- if (deltaBytes > MISTRAL_REALTIME_MAX_PARTIAL_TRANSCRIPT_BYTES - partialBytes) {
137
- failTerminal(/* @__PURE__ */ new Error(MISTRAL_REALTIME_PARTIAL_TRANSCRIPT_OVERFLOW_MESSAGE), transport);
138
- return;
139
- }
140
- partialText += event.text;
141
- partialBytes += deltaBytes;
142
- config.onPartial?.(partialText);
143
- }
144
- return;
145
- case "transcription.segment":
146
- if (event.text?.trim()) {
147
- emitFinalTranscript(event.text, "segment");
148
- clearPartial();
149
- }
150
- return;
151
- case "transcription.done": {
152
- terminal = true;
153
- const source = hasFinalSegment ? "pending" : "terminal";
154
- const terminalText = source === "pending" ? partialText : event.text?.trim() ? event.text : partialText;
155
- clearPartial();
156
- try {
157
- emitFinalTranscript(terminalText, source);
158
- } finally {
159
- transport.closeNow();
160
- }
161
- return;
162
- }
163
- case "error": failTerminal(new Error(readErrorDetail(event)), transport);
164
- }
165
- };
166
- return createRealtimeTranscriptionWebSocketSession({
167
- providerId: "mistral",
168
- callbacks: config,
169
- url: () => toMistralRealtimeWsUrl(config),
170
- headers: { Authorization: `Bearer ${config.apiKey}` },
171
- connectTimeoutMs: MISTRAL_REALTIME_CONNECT_TIMEOUT_MS,
172
- closeTimeoutMs: MISTRAL_REALTIME_CLOSE_TIMEOUT_MS,
173
- maxReconnectAttempts: MISTRAL_REALTIME_MAX_RECONNECT_ATTEMPTS,
174
- reconnectDelayMs: MISTRAL_REALTIME_RECONNECT_DELAY_MS,
175
- maxQueuedBytes: MISTRAL_REALTIME_MAX_QUEUED_BYTES,
176
- connectTimeoutMessage: "Mistral realtime transcription connection timeout",
177
- reconnectLimitMessage: "Mistral realtime transcription reconnect limit reached",
178
- sendAudio: (audio, transport) => {
179
- transport.sendJson({
180
- type: "input_audio.append",
181
- audio: audio.toString("base64")
182
- });
183
- },
184
- onClose: (transport) => {
185
- transport.sendJson({ type: "input_audio.flush" });
186
- transport.sendJson({ type: "input_audio.end" });
187
- },
188
- onMessage: handleEvent
189
- });
190
- }
191
83
  function buildMistralRealtimeTranscriptionProvider({ createRealtimeTranscriptionWebSocketSession }) {
192
84
  return {
193
85
  id: "mistral",
@@ -201,15 +93,112 @@ function buildMistralRealtimeTranscriptionProvider({ createRealtimeTranscription
201
93
  const config = normalizeProviderConfig(req.providerConfig);
202
94
  const apiKey = config.apiKey || normalizeMistralApiKey(process.env.MISTRAL_API_KEY);
203
95
  if (!apiKey) throw new Error("Mistral API key missing");
204
- return createMistralRealtimeTranscriptionSession({
205
- ...req,
206
- apiKey,
207
- baseUrl: normalizeMistralRealtimeBaseUrl(config.baseUrl),
208
- model: config.model ?? MISTRAL_REALTIME_DEFAULT_MODEL,
209
- sampleRate: config.sampleRate ?? MISTRAL_REALTIME_DEFAULT_SAMPLE_RATE,
210
- encoding: config.encoding ?? MISTRAL_REALTIME_DEFAULT_ENCODING,
211
- targetStreamingDelayMs: config.targetStreamingDelayMs ?? MISTRAL_REALTIME_DEFAULT_DELAY_MS
212
- }, createRealtimeTranscriptionWebSocketSession);
96
+ const baseUrl = normalizeMistralRealtimeBaseUrl(config.baseUrl);
97
+ const callbacks = { ...req };
98
+ let partialText = "";
99
+ let partialBytes = 0;
100
+ let hasFinalSegment = false;
101
+ let terminal = false;
102
+ const clearPartial = () => {
103
+ partialText = "";
104
+ partialBytes = 0;
105
+ };
106
+ const emitFinalTranscript = (text, source) => {
107
+ if (!text.trim() || source === "pending" && !MISTRAL_REALTIME_SPEECH_CONTENT.test(text)) return;
108
+ hasFinalSegment ||= source === "segment";
109
+ callbacks.onTranscript?.(text);
110
+ };
111
+ const failTerminal = (error, transport) => {
112
+ if (terminal) return;
113
+ terminal = true;
114
+ clearPartial();
115
+ transport.closeNow();
116
+ try {
117
+ callbacks.onError?.(error);
118
+ } catch {}
119
+ };
120
+ return createRealtimeTranscriptionWebSocketSession({
121
+ providerId: "mistral",
122
+ callbacks,
123
+ url: () => {
124
+ const base = new URL(`${normalizeMistralRealtimeBaseUrl(baseUrl)}/`);
125
+ const url = new URL("v1/audio/transcriptions/realtime", base);
126
+ url.searchParams.set("model", config.model ?? MISTRAL_REALTIME_DEFAULT_MODEL);
127
+ url.searchParams.set("target_streaming_delay_ms", String(config.targetStreamingDelayMs ?? MISTRAL_REALTIME_DEFAULT_DELAY_MS));
128
+ return url.toString();
129
+ },
130
+ headers: { Authorization: `Bearer ${apiKey}` },
131
+ connectTimeoutMs: MISTRAL_REALTIME_CONNECT_TIMEOUT_MS,
132
+ closeTimeoutMs: MISTRAL_REALTIME_CLOSE_TIMEOUT_MS,
133
+ maxReconnectAttempts: MISTRAL_REALTIME_MAX_RECONNECT_ATTEMPTS,
134
+ reconnectDelayMs: MISTRAL_REALTIME_RECONNECT_DELAY_MS,
135
+ maxQueuedBytes: MISTRAL_REALTIME_MAX_QUEUED_BYTES,
136
+ connectTimeoutMessage: "Mistral realtime transcription connection timeout",
137
+ reconnectLimitMessage: "Mistral realtime transcription reconnect limit reached",
138
+ sendAudio: (audio, transport) => {
139
+ transport.sendJson({
140
+ type: "input_audio.append",
141
+ audio: audio.toString("base64")
142
+ });
143
+ },
144
+ onClose: (transport) => {
145
+ transport.sendJson({ type: "input_audio.flush" });
146
+ transport.sendJson({ type: "input_audio.end" });
147
+ },
148
+ onMessage: (event, transport) => {
149
+ if (terminal) return;
150
+ if (event.type === "session.created") {
151
+ clearPartial();
152
+ hasFinalSegment = false;
153
+ transport.sendJson({
154
+ type: "session.update",
155
+ session: { audio_format: {
156
+ encoding: config.encoding ?? MISTRAL_REALTIME_DEFAULT_ENCODING,
157
+ sample_rate: config.sampleRate ?? MISTRAL_REALTIME_DEFAULT_SAMPLE_RATE
158
+ } }
159
+ });
160
+ transport.markReady();
161
+ return;
162
+ }
163
+ if (!transport.isReady() && event.type === "error") {
164
+ transport.failConnect(new Error(readErrorDetail(event)));
165
+ return;
166
+ }
167
+ switch (event.type) {
168
+ case "transcription.text.delta":
169
+ if (event.text) {
170
+ const deltaBytes = measureTranscriptDeltaBytes(partialText, event.text);
171
+ if (deltaBytes > MISTRAL_REALTIME_MAX_PARTIAL_TRANSCRIPT_BYTES - partialBytes) {
172
+ failTerminal(/* @__PURE__ */ new Error(MISTRAL_REALTIME_PARTIAL_TRANSCRIPT_OVERFLOW_MESSAGE), transport);
173
+ return;
174
+ }
175
+ partialText += event.text;
176
+ partialBytes += deltaBytes;
177
+ callbacks.onPartial?.(partialText);
178
+ }
179
+ return;
180
+ case "transcription.segment":
181
+ if (event.text?.trim()) {
182
+ emitFinalTranscript(event.text, "segment");
183
+ clearPartial();
184
+ }
185
+ return;
186
+ case "transcription.done": {
187
+ terminal = true;
188
+ const source = hasFinalSegment ? "pending" : "terminal";
189
+ const terminalText = source === "pending" ? partialText : event.text?.trim() ? event.text : partialText;
190
+ clearPartial();
191
+ try {
192
+ emitFinalTranscript(terminalText, source);
193
+ } finally {
194
+ transport.closeNow();
195
+ }
196
+ return;
197
+ }
198
+ case "error": failTerminal(new Error(readErrorDetail(event)), transport);
199
+ }
200
+ }
201
+ });
213
202
  }
214
203
  };
215
204
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/mistral-provider",
3
- "version": "2026.10.1-beta.2",
3
+ "version": "2026.10.5-beta.1",
4
4
  "description": "OpenClaw Mistral provider plugin",
5
5
  "repository": {
6
6
  "type": "git",
@@ -18,10 +18,10 @@
18
18
  "minHostVersion": ">=2026.7.2"
19
19
  },
20
20
  "compat": {
21
- "pluginApi": ">=2026.10.1-beta.2"
21
+ "pluginApi": ">=2026.10.5-beta.1"
22
22
  },
23
23
  "build": {
24
- "openclawVersion": "2026.10.1-beta.2",
24
+ "openclawVersion": "2026.10.5-beta.1",
25
25
  "bundledDist": false
26
26
  },
27
27
  "release": {
@@ -40,7 +40,7 @@
40
40
  "assets/activity.svg"
41
41
  ],
42
42
  "peerDependencies": {
43
- "openclaw": ">=2026.10.1-beta.2"
43
+ "openclaw": ">=2026.10.5-beta.1"
44
44
  },
45
45
  "peerDependenciesMeta": {
46
46
  "openclaw": {