@openclaw/mistral-provider 0.0.0 → 2026.8.1-beta.2

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/README.md CHANGED
@@ -1,3 +1,16 @@
1
- # @openclaw/mistral-provider
1
+ # OpenClaw Mistral Provider
2
2
 
3
- Reserved placeholder for the official OpenClaw plugin package. Use a published release version instead.
3
+ Official OpenClaw provider plugin for Mistral models, Voxtral transcription, and
4
+ Mistral memory embeddings.
5
+
6
+ Install from OpenClaw:
7
+
8
+ ```bash
9
+ openclaw plugins install @openclaw/mistral-provider
10
+ openclaw gateway restart
11
+ ```
12
+
13
+ Set `MISTRAL_API_KEY`, then select a `mistral/*` model or configure Mistral for
14
+ media transcription, realtime transcription, or memory embeddings.
15
+
16
+ See <https://docs.openclaw.ai/providers/mistral> for setup and configuration.
package/dist/api.js ADDED
@@ -0,0 +1,48 @@
1
+ import { buildMistralProvider } from "./provider-catalog.js";
2
+ import { MISTRAL_BASE_URL, MISTRAL_DEFAULT_MODEL_ID, MISTRAL_DEFAULT_MODEL_REF, buildMistralModelDefinition } from "./model-definitions.js";
3
+ import { applyMistralConfig, applyMistralProviderConfig } from "./onboard.js";
4
+ const MISTRAL_MODEL_TRANSPORT_PATCH = {
5
+ supportsStore: false,
6
+ supportsPromptCacheKey: true,
7
+ supportsLongCacheRetention: false,
8
+ maxTokensField: "max_tokens"
9
+ };
10
+ const MISTRAL_SMALL_LATEST_REASONING_EFFORT_MAP = {
11
+ off: "none",
12
+ minimal: "none",
13
+ low: "high",
14
+ medium: "high",
15
+ high: "high",
16
+ xhigh: "high",
17
+ adaptive: "high",
18
+ max: "high"
19
+ };
20
+ const MISTRAL_SMALL_LATEST_ID = "mistral-small-latest";
21
+ const MISTRAL_SMALL_4_ID = "mistral-small-2603";
22
+ const MISTRAL_MEDIUM_3_5_ID = "mistral-medium-3-5";
23
+ function resolveMistralCompatPatch(model) {
24
+ const reasoningEnabled = model.id === "mistral-small-latest" || model.id === "mistral-small-2603" || model.id === "mistral-medium-3-5";
25
+ return {
26
+ ...MISTRAL_MODEL_TRANSPORT_PATCH,
27
+ supportsReasoningEffort: reasoningEnabled,
28
+ reasoningEffortMap: reasoningEnabled ? MISTRAL_SMALL_LATEST_REASONING_EFFORT_MAP : void 0
29
+ };
30
+ }
31
+ function compatMatchesResolved(compat, modelId) {
32
+ const expected = resolveMistralCompatPatch({ id: modelId });
33
+ return compat?.supportsStore === expected.supportsStore && compat?.supportsPromptCacheKey === expected.supportsPromptCacheKey && compat?.supportsLongCacheRetention === expected.supportsLongCacheRetention && compat?.supportsReasoningEffort === expected.supportsReasoningEffort && compat?.maxTokensField === expected.maxTokensField && compat?.reasoningEffortMap === expected.reasoningEffortMap;
34
+ }
35
+ function applyMistralModelCompat(model) {
36
+ const compat = model.compat && typeof model.compat === "object" ? model.compat : void 0;
37
+ if (compatMatchesResolved(compat, model.id)) return model;
38
+ const patch = resolveMistralCompatPatch(model);
39
+ return {
40
+ ...model,
41
+ compat: {
42
+ ...compat,
43
+ ...patch
44
+ }
45
+ };
46
+ }
47
+ //#endregion
48
+ export { MISTRAL_BASE_URL, MISTRAL_DEFAULT_MODEL_ID, MISTRAL_DEFAULT_MODEL_REF, MISTRAL_MEDIUM_3_5_ID, MISTRAL_MODEL_TRANSPORT_PATCH, MISTRAL_SMALL_4_ID, MISTRAL_SMALL_LATEST_ID, applyMistralConfig, applyMistralModelCompat, applyMistralProviderConfig, buildMistralModelDefinition, buildMistralProvider, resolveMistralCompatPatch };
@@ -0,0 +1,32 @@
1
+ import { createRemoteEmbeddingProvider, normalizeEmbeddingModelWithPrefixes, resolveRemoteEmbeddingClient } from "openclaw/plugin-sdk/memory-core-host-engine-embeddings";
2
+ //#region extensions/mistral/embedding-provider.ts
3
+ const DEFAULT_MISTRAL_EMBEDDING_MODEL = "mistral-embed";
4
+ const DEFAULT_MISTRAL_BASE_URL = "https://api.mistral.ai/v1";
5
+ function normalizeMistralModel(model) {
6
+ return normalizeEmbeddingModelWithPrefixes({
7
+ model,
8
+ defaultModel: DEFAULT_MISTRAL_EMBEDDING_MODEL,
9
+ prefixes: ["mistral/"]
10
+ });
11
+ }
12
+ async function createMistralEmbeddingProvider(options) {
13
+ const client = await resolveMistralEmbeddingClient(options);
14
+ return {
15
+ provider: createRemoteEmbeddingProvider({
16
+ id: "mistral",
17
+ client,
18
+ errorPrefix: "mistral embeddings failed"
19
+ }),
20
+ client
21
+ };
22
+ }
23
+ async function resolveMistralEmbeddingClient(options) {
24
+ return await resolveRemoteEmbeddingClient({
25
+ provider: "mistral",
26
+ options,
27
+ defaultBaseUrl: DEFAULT_MISTRAL_BASE_URL,
28
+ normalizeModel: normalizeMistralModel
29
+ });
30
+ }
31
+ //#endregion
32
+ export { DEFAULT_MISTRAL_EMBEDDING_MODEL, createMistralEmbeddingProvider };
package/dist/index.js ADDED
@@ -0,0 +1,44 @@
1
+ import { t as openclaw_plugin_default } from "./openclaw.plugin-CxIFdez7.js";
2
+ import { applyMistralConfig } from "./onboard.js";
3
+ import { applyMistralModelCompat } from "./api.js";
4
+ import { mistralMediaUnderstandingProvider } from "./media-understanding-provider.js";
5
+ import { mistralMemoryEmbeddingProviderAdapter } from "./memory-embedding-adapter.js";
6
+ import { buildMistralRealtimeTranscriptionProvider } from "./realtime-transcription-provider.js";
7
+ import { defineSingleProviderPluginEntry } from "openclaw/plugin-sdk/provider-entry";
8
+ //#region extensions/mistral/index.ts
9
+ const PROVIDER_ID = "mistral";
10
+ function buildMistralReplayPolicy() {
11
+ return {
12
+ sanitizeToolCallIds: true,
13
+ toolCallIdMode: "strict9"
14
+ };
15
+ }
16
+ var mistral_default = defineSingleProviderPluginEntry({
17
+ id: PROVIDER_ID,
18
+ name: "Mistral Provider",
19
+ description: "Official Mistral provider plugin",
20
+ manifest: openclaw_plugin_default,
21
+ provider: {
22
+ label: "Mistral",
23
+ docsPath: "/providers/models",
24
+ manifestAuth: { applyConfig: applyMistralConfig },
25
+ catalog: {
26
+ allowExplicitBaseUrl: true,
27
+ liveModelDiscovery: true
28
+ },
29
+ matchesContextOverflowError: ({ errorMessage }) => /\bmistral\b.*(?:input.*too long|token limit.*exceeded)/i.test(errorMessage),
30
+ normalizeResolvedModel: ({ model }) => applyMistralModelCompat(model),
31
+ resolveThinkingProfile: ({ modelId }) => modelId === "mistral-small-latest" || modelId === "mistral-small-2603" || modelId === "mistral-medium-3-5" ? {
32
+ levels: [{ id: "off" }, { id: "high" }],
33
+ defaultLevel: "off"
34
+ } : void 0,
35
+ buildReplayPolicy: () => buildMistralReplayPolicy()
36
+ },
37
+ register(api) {
38
+ api.registerMemoryEmbeddingProvider(mistralMemoryEmbeddingProviderAdapter);
39
+ api.registerMediaUnderstandingProvider(mistralMediaUnderstandingProvider);
40
+ api.registerRealtimeTranscriptionProvider(buildMistralRealtimeTranscriptionProvider());
41
+ }
42
+ });
43
+ //#endregion
44
+ export { mistral_default as default };
@@ -0,0 +1,18 @@
1
+ import { transcribeOpenAiCompatibleAudio } from "openclaw/plugin-sdk/media-understanding";
2
+ //#region extensions/mistral/media-understanding-provider.ts
3
+ const DEFAULT_MISTRAL_AUDIO_BASE_URL = "https://api.mistral.ai/v1";
4
+ const DEFAULT_MISTRAL_AUDIO_MODEL = "voxtral-mini-latest";
5
+ const mistralMediaUnderstandingProvider = {
6
+ id: "mistral",
7
+ capabilities: ["audio"],
8
+ defaultModels: { audio: DEFAULT_MISTRAL_AUDIO_MODEL },
9
+ autoPriority: { audio: 50 },
10
+ transcribeAudio: async (req) => await transcribeOpenAiCompatibleAudio({
11
+ ...req,
12
+ baseUrl: req.baseUrl ?? DEFAULT_MISTRAL_AUDIO_BASE_URL,
13
+ defaultBaseUrl: DEFAULT_MISTRAL_AUDIO_BASE_URL,
14
+ defaultModel: DEFAULT_MISTRAL_AUDIO_MODEL
15
+ })
16
+ };
17
+ //#endregion
18
+ export { mistralMediaUnderstandingProvider };
@@ -0,0 +1,31 @@
1
+ import { DEFAULT_MISTRAL_EMBEDDING_MODEL, createMistralEmbeddingProvider } from "./embedding-provider.js";
2
+ import { isMissingEmbeddingApiKeyError } from "openclaw/plugin-sdk/memory-core-host-engine-embeddings";
3
+ //#region extensions/mistral/memory-embedding-adapter.ts
4
+ const mistralMemoryEmbeddingProviderAdapter = {
5
+ id: "mistral",
6
+ defaultModel: DEFAULT_MISTRAL_EMBEDDING_MODEL,
7
+ transport: "remote",
8
+ authProviderId: "mistral",
9
+ autoSelectPriority: 50,
10
+ allowExplicitWhenConfiguredAuto: true,
11
+ shouldContinueAutoSelection: isMissingEmbeddingApiKeyError,
12
+ create: async (options) => {
13
+ const { provider, client } = await createMistralEmbeddingProvider({
14
+ ...options,
15
+ provider: "mistral",
16
+ fallback: "none"
17
+ });
18
+ return {
19
+ provider,
20
+ runtime: {
21
+ id: "mistral",
22
+ cacheKeyData: {
23
+ provider: "mistral",
24
+ model: client.model
25
+ }
26
+ }
27
+ };
28
+ }
29
+ };
30
+ //#endregion
31
+ export { mistralMemoryEmbeddingProviderAdapter };
@@ -0,0 +1,20 @@
1
+ import { t as openclaw_plugin_default } from "./openclaw.plugin-CxIFdez7.js";
2
+ import { buildManifestModelProviderConfig, readManifestProviderDefaultModelRef } from "openclaw/plugin-sdk/provider-catalog-shared";
3
+ //#region extensions/mistral/model-definitions.ts
4
+ const MISTRAL_MANIFEST_CATALOG = openclaw_plugin_default.modelCatalog.providers.mistral;
5
+ const MISTRAL_BASE_URL = MISTRAL_MANIFEST_CATALOG.baseUrl;
6
+ const MISTRAL_DEFAULT_MODEL_REF = readManifestProviderDefaultModelRef(openclaw_plugin_default, "mistral");
7
+ const MISTRAL_DEFAULT_MODEL_ID = MISTRAL_DEFAULT_MODEL_REF.slice(8);
8
+ function buildMistralModelDefinition() {
9
+ const model = buildMistralCatalogModels().find((entry) => entry.id === MISTRAL_DEFAULT_MODEL_ID);
10
+ if (!model) throw new Error(`Missing Mistral provider model ${MISTRAL_DEFAULT_MODEL_ID}`);
11
+ return model;
12
+ }
13
+ function buildMistralCatalogModels() {
14
+ return buildManifestModelProviderConfig({
15
+ providerId: "mistral",
16
+ catalog: MISTRAL_MANIFEST_CATALOG
17
+ }).models;
18
+ }
19
+ //#endregion
20
+ export { MISTRAL_BASE_URL, MISTRAL_DEFAULT_MODEL_ID, MISTRAL_DEFAULT_MODEL_REF, buildMistralModelDefinition };
@@ -0,0 +1,19 @@
1
+ import { MISTRAL_BASE_URL, MISTRAL_DEFAULT_MODEL_ID, MISTRAL_DEFAULT_MODEL_REF, buildMistralModelDefinition } from "./model-definitions.js";
2
+ import { createDefaultModelPresetAppliers } from "openclaw/plugin-sdk/provider-onboard";
3
+ //#region extensions/mistral/onboard.ts
4
+ const { applyConfig: applyMistralConfig, applyProviderConfig: applyMistralProviderConfig } = createDefaultModelPresetAppliers({
5
+ primaryModelRef: MISTRAL_DEFAULT_MODEL_REF,
6
+ resolveParams: () => ({
7
+ providerId: "mistral",
8
+ api: "openai-completions",
9
+ baseUrl: MISTRAL_BASE_URL,
10
+ defaultModel: buildMistralModelDefinition(),
11
+ defaultModelId: MISTRAL_DEFAULT_MODEL_ID,
12
+ aliases: [{
13
+ modelRef: MISTRAL_DEFAULT_MODEL_REF,
14
+ alias: "Mistral"
15
+ }]
16
+ })
17
+ });
18
+ //#endregion
19
+ export { applyMistralConfig, applyMistralProviderConfig };
@@ -0,0 +1,156 @@
1
+ //#region extensions/mistral/openclaw.plugin.json
2
+ var openclaw_plugin_default = {
3
+ id: "mistral",
4
+ icon: "https://cdn.simpleicons.org/mistralai",
5
+ activation: { "onStartup": false },
6
+ enabledByDefault: true,
7
+ providers: ["mistral"],
8
+ providerEndpoints: [{
9
+ "endpointClass": "mistral-public",
10
+ "hosts": ["api.mistral.ai"]
11
+ }],
12
+ providerRequest: { "providers": { "mistral": { "family": "mistral" } } },
13
+ modelCatalog: {
14
+ "providers": { "mistral": {
15
+ "baseUrl": "https://api.mistral.ai/v1",
16
+ "api": "openai-completions",
17
+ "defaultModel": "mistral-large-latest",
18
+ "models": [
19
+ {
20
+ "id": "codestral-latest",
21
+ "name": "Codestral (latest)",
22
+ "input": ["text"],
23
+ "contextWindow": 128e3,
24
+ "maxTokens": 4096,
25
+ "cost": {
26
+ "input": .3,
27
+ "output": .9,
28
+ "cacheRead": .03,
29
+ "cacheWrite": 0
30
+ }
31
+ },
32
+ {
33
+ "id": "devstral-medium-latest",
34
+ "name": "Devstral 2 (latest)",
35
+ "status": "deprecated",
36
+ "replacedBy": "mistral-medium-3-5",
37
+ "input": ["text"],
38
+ "contextWindow": 262144,
39
+ "maxTokens": 32768,
40
+ "cost": {
41
+ "input": .4,
42
+ "output": 2,
43
+ "cacheRead": .04,
44
+ "cacheWrite": 0
45
+ }
46
+ },
47
+ {
48
+ "id": "mistral-large-latest",
49
+ "name": "Mistral Large 3 (latest)",
50
+ "input": ["text", "image"],
51
+ "contextWindow": 262144,
52
+ "maxTokens": 16384,
53
+ "cost": {
54
+ "input": .5,
55
+ "output": 1.5,
56
+ "cacheRead": .05,
57
+ "cacheWrite": 0
58
+ }
59
+ },
60
+ {
61
+ "id": "mistral-medium-2508",
62
+ "name": "Mistral Medium 3.1",
63
+ "status": "deprecated",
64
+ "replacedBy": "mistral-medium-3-5",
65
+ "input": ["text", "image"],
66
+ "contextWindow": 128e3,
67
+ "maxTokens": 8192,
68
+ "cost": {
69
+ "input": .4,
70
+ "output": 2,
71
+ "cacheRead": .04,
72
+ "cacheWrite": 0
73
+ }
74
+ },
75
+ {
76
+ "id": "mistral-medium-3-5",
77
+ "name": "Mistral Medium 3.5",
78
+ "input": ["text", "image"],
79
+ "reasoning": true,
80
+ "contextWindow": 262144,
81
+ "maxTokens": 8192,
82
+ "cost": {
83
+ "input": 1.5,
84
+ "output": 7.5,
85
+ "cacheRead": .15,
86
+ "cacheWrite": 0
87
+ }
88
+ },
89
+ {
90
+ "id": "mistral-small-latest",
91
+ "name": "Mistral Small 4 (latest)",
92
+ "input": ["text", "image"],
93
+ "reasoning": true,
94
+ "contextWindow": 262144,
95
+ "maxTokens": 16384,
96
+ "cost": {
97
+ "input": .15,
98
+ "output": .6,
99
+ "cacheRead": .015,
100
+ "cacheWrite": 0
101
+ }
102
+ },
103
+ {
104
+ "id": "mistral-small-2603",
105
+ "name": "Mistral Small 4 (26.03)",
106
+ "input": ["text", "image"],
107
+ "reasoning": true,
108
+ "contextWindow": 262144,
109
+ "maxTokens": 16384,
110
+ "cost": {
111
+ "input": .15,
112
+ "output": .6,
113
+ "cacheRead": .015,
114
+ "cacheWrite": 0
115
+ }
116
+ }
117
+ ]
118
+ } },
119
+ "discovery": { "mistral": "refreshable" }
120
+ },
121
+ setup: { "providers": [{
122
+ "id": "mistral",
123
+ "envVars": ["MISTRAL_API_KEY"]
124
+ }] },
125
+ providerAuthChoices: [{
126
+ "provider": "mistral",
127
+ "method": "api-key",
128
+ "choiceId": "mistral-api-key",
129
+ "appGuidedSecret": true,
130
+ "choiceLabel": "Mistral API key",
131
+ "groupId": "mistral",
132
+ "groupLabel": "Mistral AI",
133
+ "groupHint": "API key",
134
+ "optionKey": "mistralApiKey",
135
+ "cliFlag": "--mistral-api-key",
136
+ "cliOption": "--mistral-api-key <key>",
137
+ "cliDescription": "Mistral API key"
138
+ }],
139
+ contracts: {
140
+ "memoryEmbeddingProviders": ["mistral"],
141
+ "mediaUnderstandingProviders": ["mistral"],
142
+ "realtimeTranscriptionProviders": ["mistral"]
143
+ },
144
+ mediaUnderstandingProviderMetadata: { "mistral": {
145
+ "capabilities": ["audio"],
146
+ "defaultModels": { "audio": "voxtral-mini-latest" },
147
+ "autoPriority": { "audio": 50 }
148
+ } },
149
+ configSchema: {
150
+ "type": "object",
151
+ "additionalProperties": false,
152
+ "properties": {}
153
+ }
154
+ };
155
+ //#endregion
156
+ export { openclaw_plugin_default as t };
@@ -0,0 +1,11 @@
1
+ import { t as openclaw_plugin_default } from "./openclaw.plugin-CxIFdez7.js";
2
+ import { buildManifestModelProviderConfig } from "openclaw/plugin-sdk/provider-catalog-shared";
3
+ //#region extensions/mistral/provider-catalog.ts
4
+ function buildMistralProvider() {
5
+ return buildManifestModelProviderConfig({
6
+ providerId: "mistral",
7
+ catalog: openclaw_plugin_default.modelCatalog.providers.mistral
8
+ });
9
+ }
10
+ //#endregion
11
+ export { buildMistralProvider };
@@ -0,0 +1,217 @@
1
+ import { createRealtimeTranscriptionWebSocketSession } from "openclaw/plugin-sdk/realtime-transcription";
2
+ import { normalizeResolvedSecretInputString, normalizeSecretInput } from "openclaw/plugin-sdk/secret-input";
3
+ import { asOptionalRecord, normalizeOptionalString, parseFiniteNumber } from "openclaw/plugin-sdk/string-coerce-runtime";
4
+ //#region extensions/mistral/realtime-transcription-provider.ts
5
+ const MISTRAL_REALTIME_DEFAULT_BASE_URL = "wss://api.mistral.ai";
6
+ const MISTRAL_REALTIME_DEFAULT_MODEL = "voxtral-mini-transcribe-realtime-2602";
7
+ const MISTRAL_REALTIME_DEFAULT_SAMPLE_RATE = 8e3;
8
+ const MISTRAL_REALTIME_DEFAULT_ENCODING = "pcm_mulaw";
9
+ const MISTRAL_REALTIME_DEFAULT_DELAY_MS = 800;
10
+ const MISTRAL_REALTIME_CONNECT_TIMEOUT_MS = 1e4;
11
+ const MISTRAL_REALTIME_CLOSE_TIMEOUT_MS = 5e3;
12
+ const MISTRAL_REALTIME_MAX_RECONNECT_ATTEMPTS = 5;
13
+ const MISTRAL_REALTIME_RECONNECT_DELAY_MS = 1e3;
14
+ const MISTRAL_REALTIME_MAX_QUEUED_BYTES = 2 * 1024 * 1024;
15
+ const MISTRAL_REALTIME_SPEECH_CONTENT = /[\p{L}\p{N}]/u;
16
+ const MISTRAL_REALTIME_MAX_PARTIAL_TRANSCRIPT_BYTES = 256 * 1024;
17
+ const MISTRAL_REALTIME_PARTIAL_TRANSCRIPT_OVERFLOW_MESSAGE = "Mistral realtime transcription exceeded the 256 KiB in-progress transcript limit";
18
+ function readNestedMistralConfig(rawConfig) {
19
+ const raw = asOptionalRecord(rawConfig);
20
+ return asOptionalRecord(asOptionalRecord(raw?.providers)?.mistral ?? raw?.mistral ?? raw) ?? {};
21
+ }
22
+ function normalizeMistralEncoding(value) {
23
+ const normalized = normalizeOptionalString(value)?.toLowerCase();
24
+ if (!normalized) return;
25
+ switch (normalized) {
26
+ case "pcm":
27
+ case "linear16":
28
+ case "pcm_s16le": return "pcm_s16le";
29
+ case "pcm_s32le":
30
+ case "pcm_f16le":
31
+ case "pcm_f32le": return normalized;
32
+ case "mulaw":
33
+ case "ulaw":
34
+ case "g711_ulaw":
35
+ case "g711-mulaw":
36
+ case "pcm_mulaw": return "pcm_mulaw";
37
+ case "alaw":
38
+ case "g711_alaw":
39
+ case "g711-alaw":
40
+ case "pcm_alaw": return "pcm_alaw";
41
+ default: throw new Error(`Invalid Mistral realtime transcription encoding: ${normalized}`);
42
+ }
43
+ }
44
+ function normalizeMistralRealtimeBaseUrl(value) {
45
+ const raw = normalizeOptionalString(value ?? process.env.MISTRAL_REALTIME_BASE_URL);
46
+ if (!raw) return MISTRAL_REALTIME_DEFAULT_BASE_URL;
47
+ const url = new URL(raw);
48
+ url.protocol = url.protocol === "http:" ? "ws:" : url.protocol === "https:" ? "wss:" : url.protocol;
49
+ url.pathname = url.pathname.replace(/\/v1\/?$/, "").replace(/\/+$/, "");
50
+ return url.toString().replace(/\/+$/, "");
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
+ function normalizeProviderConfig(config) {
60
+ const raw = readNestedMistralConfig(config);
61
+ return {
62
+ apiKey: normalizeMistralApiKey(raw.apiKey),
63
+ baseUrl: normalizeOptionalString(raw.baseUrl),
64
+ model: normalizeOptionalString(raw.model ?? raw.sttModel),
65
+ sampleRate: parseFiniteNumber(raw.sampleRate ?? raw.sample_rate),
66
+ encoding: normalizeMistralEncoding(raw.encoding),
67
+ targetStreamingDelayMs: parseFiniteNumber(raw.targetStreamingDelayMs ?? raw.target_streaming_delay_ms ?? raw.delayMs)
68
+ };
69
+ }
70
+ function normalizeMistralApiKey(value) {
71
+ return normalizeSecretInput(normalizeResolvedSecretInputString({
72
+ value,
73
+ path: "plugins.entries.voice-call.config.streaming.providers.mistral.apiKey"
74
+ })) || void 0;
75
+ }
76
+ function readErrorDetail(event) {
77
+ const message = event.error?.message;
78
+ if (typeof message === "string") return message;
79
+ if (message && typeof message === "object") return JSON.stringify(message);
80
+ if (typeof event.error?.code === "number") return `Mistral realtime transcription error (${event.error.code})`;
81
+ return "Mistral realtime transcription error";
82
+ }
83
+ function measureTranscriptDeltaBytes(partialText, delta) {
84
+ const previousCodeUnit = partialText.charCodeAt(partialText.length - 1);
85
+ const nextCodeUnit = delta.charCodeAt(0);
86
+ const completesSplitSurrogatePair = previousCodeUnit >= 55296 && previousCodeUnit <= 56319 && nextCodeUnit >= 56320 && nextCodeUnit <= 57343;
87
+ return Buffer.byteLength(delta, "utf8") - (completesSplitSurrogatePair ? 2 : 0);
88
+ }
89
+ function createMistralRealtimeTranscriptionSession(config) {
90
+ let partialText = "";
91
+ let partialBytes = 0;
92
+ let hasFinalSegment = false;
93
+ let terminal = false;
94
+ const clearPartial = () => {
95
+ partialText = "";
96
+ partialBytes = 0;
97
+ };
98
+ const emitFinalTranscript = (text, source) => {
99
+ if (!text.trim() || source === "pending" && !MISTRAL_REALTIME_SPEECH_CONTENT.test(text)) return;
100
+ hasFinalSegment ||= source === "segment";
101
+ config.onTranscript?.(text);
102
+ };
103
+ const failTerminal = (error, transport) => {
104
+ if (terminal) return;
105
+ terminal = true;
106
+ clearPartial();
107
+ transport.closeNow();
108
+ try {
109
+ config.onError?.(error);
110
+ } catch {}
111
+ };
112
+ const handleEvent = (event, transport) => {
113
+ if (terminal) return;
114
+ if (event.type === "session.created") {
115
+ clearPartial();
116
+ hasFinalSegment = false;
117
+ transport.sendJson({
118
+ type: "session.update",
119
+ session: { audio_format: {
120
+ encoding: config.encoding,
121
+ sample_rate: config.sampleRate
122
+ } }
123
+ });
124
+ transport.markReady();
125
+ return;
126
+ }
127
+ if (!transport.isReady() && event.type === "error") {
128
+ transport.failConnect(new Error(readErrorDetail(event)));
129
+ return;
130
+ }
131
+ switch (event.type) {
132
+ case "transcription.text.delta":
133
+ if (event.text) {
134
+ const deltaBytes = measureTranscriptDeltaBytes(partialText, event.text);
135
+ if (deltaBytes > MISTRAL_REALTIME_MAX_PARTIAL_TRANSCRIPT_BYTES - partialBytes) {
136
+ failTerminal(/* @__PURE__ */ new Error(MISTRAL_REALTIME_PARTIAL_TRANSCRIPT_OVERFLOW_MESSAGE), transport);
137
+ return;
138
+ }
139
+ partialText += event.text;
140
+ partialBytes += deltaBytes;
141
+ config.onPartial?.(partialText);
142
+ }
143
+ return;
144
+ case "transcription.segment":
145
+ if (event.text?.trim()) {
146
+ emitFinalTranscript(event.text, "segment");
147
+ clearPartial();
148
+ }
149
+ return;
150
+ case "transcription.done": {
151
+ terminal = true;
152
+ const source = hasFinalSegment ? "pending" : "terminal";
153
+ const terminalText = source === "pending" ? partialText : event.text?.trim() ? event.text : partialText;
154
+ clearPartial();
155
+ try {
156
+ emitFinalTranscript(terminalText, source);
157
+ } finally {
158
+ transport.closeNow();
159
+ }
160
+ return;
161
+ }
162
+ case "error": failTerminal(new Error(readErrorDetail(event)), transport);
163
+ default:
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
+ function buildMistralRealtimeTranscriptionProvider() {
192
+ return {
193
+ id: "mistral",
194
+ label: "Mistral Realtime Transcription",
195
+ aliases: ["mistral-realtime", "voxtral-realtime"],
196
+ defaultModel: MISTRAL_REALTIME_DEFAULT_MODEL,
197
+ autoSelectOrder: 45,
198
+ resolveConfig: ({ rawConfig }) => normalizeProviderConfig(rawConfig),
199
+ isConfigured: ({ providerConfig }) => Boolean(normalizeProviderConfig(providerConfig).apiKey || normalizeMistralApiKey(process.env.MISTRAL_API_KEY)),
200
+ createSession: (req) => {
201
+ const config = normalizeProviderConfig(req.providerConfig);
202
+ const apiKey = config.apiKey || normalizeMistralApiKey(process.env.MISTRAL_API_KEY);
203
+ 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
+ });
213
+ }
214
+ };
215
+ }
216
+ //#endregion
217
+ export { buildMistralRealtimeTranscriptionProvider };
@@ -0,0 +1,179 @@
1
+ {
2
+ "id": "mistral",
3
+ "icon": "https://cdn.simpleicons.org/mistralai",
4
+ "activation": {
5
+ "onStartup": false
6
+ },
7
+ "enabledByDefault": true,
8
+ "providers": ["mistral"],
9
+ "providerEndpoints": [
10
+ {
11
+ "endpointClass": "mistral-public",
12
+ "hosts": ["api.mistral.ai"]
13
+ }
14
+ ],
15
+ "providerRequest": {
16
+ "providers": {
17
+ "mistral": {
18
+ "family": "mistral"
19
+ }
20
+ }
21
+ },
22
+ "modelCatalog": {
23
+ "providers": {
24
+ "mistral": {
25
+ "baseUrl": "https://api.mistral.ai/v1",
26
+ "api": "openai-completions",
27
+ "defaultModel": "mistral-large-latest",
28
+ "models": [
29
+ {
30
+ "id": "codestral-latest",
31
+ "name": "Codestral (latest)",
32
+ "input": ["text"],
33
+ "contextWindow": 128000,
34
+ "maxTokens": 4096,
35
+ "cost": {
36
+ "input": 0.3,
37
+ "output": 0.9,
38
+ "cacheRead": 0.03,
39
+ "cacheWrite": 0
40
+ }
41
+ },
42
+ {
43
+ "id": "devstral-medium-latest",
44
+ "name": "Devstral 2 (latest)",
45
+ "status": "deprecated",
46
+ "replacedBy": "mistral-medium-3-5",
47
+ "input": ["text"],
48
+ "contextWindow": 262144,
49
+ "maxTokens": 32768,
50
+ "cost": {
51
+ "input": 0.4,
52
+ "output": 2,
53
+ "cacheRead": 0.04,
54
+ "cacheWrite": 0
55
+ }
56
+ },
57
+ {
58
+ "id": "mistral-large-latest",
59
+ "name": "Mistral Large 3 (latest)",
60
+ "input": ["text", "image"],
61
+ "contextWindow": 262144,
62
+ "maxTokens": 16384,
63
+ "cost": {
64
+ "input": 0.5,
65
+ "output": 1.5,
66
+ "cacheRead": 0.05,
67
+ "cacheWrite": 0
68
+ }
69
+ },
70
+ {
71
+ "id": "mistral-medium-2508",
72
+ "name": "Mistral Medium 3.1",
73
+ "status": "deprecated",
74
+ "replacedBy": "mistral-medium-3-5",
75
+ "input": ["text", "image"],
76
+ "contextWindow": 128000,
77
+ "maxTokens": 8192,
78
+ "cost": {
79
+ "input": 0.4,
80
+ "output": 2,
81
+ "cacheRead": 0.04,
82
+ "cacheWrite": 0
83
+ }
84
+ },
85
+ {
86
+ "id": "mistral-medium-3-5",
87
+ "name": "Mistral Medium 3.5",
88
+ "input": ["text", "image"],
89
+ "reasoning": true,
90
+ "contextWindow": 262144,
91
+ "maxTokens": 8192,
92
+ "cost": {
93
+ "input": 1.5,
94
+ "output": 7.5,
95
+ "cacheRead": 0.15,
96
+ "cacheWrite": 0
97
+ }
98
+ },
99
+ {
100
+ "id": "mistral-small-latest",
101
+ "name": "Mistral Small 4 (latest)",
102
+ "input": ["text", "image"],
103
+ "reasoning": true,
104
+ "contextWindow": 262144,
105
+ "maxTokens": 16384,
106
+ "cost": {
107
+ "input": 0.15,
108
+ "output": 0.6,
109
+ "cacheRead": 0.015,
110
+ "cacheWrite": 0
111
+ }
112
+ },
113
+ {
114
+ "id": "mistral-small-2603",
115
+ "name": "Mistral Small 4 (26.03)",
116
+ "input": ["text", "image"],
117
+ "reasoning": true,
118
+ "contextWindow": 262144,
119
+ "maxTokens": 16384,
120
+ "cost": {
121
+ "input": 0.15,
122
+ "output": 0.6,
123
+ "cacheRead": 0.015,
124
+ "cacheWrite": 0
125
+ }
126
+ }
127
+ ]
128
+ }
129
+ },
130
+ "discovery": {
131
+ "mistral": "refreshable"
132
+ }
133
+ },
134
+ "setup": {
135
+ "providers": [
136
+ {
137
+ "id": "mistral",
138
+ "envVars": ["MISTRAL_API_KEY"]
139
+ }
140
+ ]
141
+ },
142
+ "providerAuthChoices": [
143
+ {
144
+ "provider": "mistral",
145
+ "method": "api-key",
146
+ "choiceId": "mistral-api-key",
147
+ "appGuidedSecret": true,
148
+ "choiceLabel": "Mistral API key",
149
+ "groupId": "mistral",
150
+ "groupLabel": "Mistral AI",
151
+ "groupHint": "API key",
152
+ "optionKey": "mistralApiKey",
153
+ "cliFlag": "--mistral-api-key",
154
+ "cliOption": "--mistral-api-key <key>",
155
+ "cliDescription": "Mistral API key"
156
+ }
157
+ ],
158
+ "contracts": {
159
+ "memoryEmbeddingProviders": ["mistral"],
160
+ "mediaUnderstandingProviders": ["mistral"],
161
+ "realtimeTranscriptionProviders": ["mistral"]
162
+ },
163
+ "mediaUnderstandingProviderMetadata": {
164
+ "mistral": {
165
+ "capabilities": ["audio"],
166
+ "defaultModels": {
167
+ "audio": "voxtral-mini-latest"
168
+ },
169
+ "autoPriority": {
170
+ "audio": 50
171
+ }
172
+ }
173
+ },
174
+ "configSchema": {
175
+ "type": "object",
176
+ "additionalProperties": false,
177
+ "properties": {}
178
+ }
179
+ }
package/package.json CHANGED
@@ -1,14 +1,49 @@
1
1
  {
2
2
  "name": "@openclaw/mistral-provider",
3
- "version": "0.0.0",
4
- "description": "Reserved package name for an official OpenClaw plugin.",
5
- "license": "MIT",
3
+ "version": "2026.8.1-beta.2",
4
+ "description": "OpenClaw Mistral provider plugin",
6
5
  "repository": {
7
6
  "type": "git",
8
- "url": "git+https://github.com/openclaw/openclaw.git"
7
+ "url": "https://github.com/openclaw/openclaw"
9
8
  },
10
- "publishConfig": {
11
- "access": "public",
12
- "tag": "placeholder"
13
- }
9
+ "type": "module",
10
+ "openclaw": {
11
+ "extensions": [
12
+ "./index.ts"
13
+ ],
14
+ "install": {
15
+ "clawhubSpec": "clawhub:@openclaw/mistral-provider",
16
+ "npmSpec": "@openclaw/mistral-provider",
17
+ "defaultChoice": "npm",
18
+ "minHostVersion": ">=2026.7.2"
19
+ },
20
+ "compat": {
21
+ "pluginApi": ">=2026.8.1-beta.2"
22
+ },
23
+ "build": {
24
+ "openclawVersion": "2026.8.1-beta.2",
25
+ "bundledDist": false
26
+ },
27
+ "release": {
28
+ "publishToClawHub": true,
29
+ "publishToNpm": true
30
+ },
31
+ "runtimeExtensions": [
32
+ "./dist/index.js"
33
+ ]
34
+ },
35
+ "files": [
36
+ "dist/**",
37
+ "openclaw.plugin.json",
38
+ "README.md"
39
+ ],
40
+ "peerDependencies": {
41
+ "openclaw": ">=2026.8.1-beta.2"
42
+ },
43
+ "peerDependenciesMeta": {
44
+ "openclaw": {
45
+ "optional": true
46
+ }
47
+ },
48
+ "bundledDependencies": []
14
49
  }