@polyweave/mistral (1.0.8)
Installation
@polyweave:registry=https://hub.kl1.tenere.ai/api/packages/Polyweave/npm/npm install @polyweave/mistral@1.0.8"@polyweave/mistral": "1.0.8"About this package
@polyweave/mistral
Mistral AI adapter for Polyweave — Frame-duplex interface to Mistral's API supporting chat (streaming, tools, vision, reasoning), embeddings, FIM code completion, audio transcription (speech-to-text), TTS (text-to-speech), and cost estimation.
Synopsis
import { createMistralAdapter } from '@polyweave/mistral';
import { createTextFrame, createParamFrame } from '@polyweave/core';
const adapter = createMistralAdapter({ apiKey: process.env.MISTRAL_API_KEY });
// Chat with streaming and tools
adapter.sink.write(createParamFrame('model', 'mistral-large-2'));
adapter.sink.write(createParamFrame('stream', true));
adapter.sink.write(createParamFrame('tools', [{ type: 'function', function: { name: 'get_weather', parameters: { type: 'object', properties: { city: { type: 'string' } } } } }]));
adapter.sink.write(createTextFrame("What's the weather in Paris?"));
// Embeddings
adapter.sink.write(createParamFrame('task', 'embeddings'));
adapter.sink.write(createParamFrame('model', 'mistral-embed'));
adapter.sink.write(createTextFrame('Embed this text'));
// FIM (fill-in-middle) code completion
adapter.sink.write(createParamFrame('task', 'fim'));
adapter.sink.write(createParamFrame('prompt', 'function add(a, b) {'));
adapter.sink.write(createParamFrame('suffix', ' return sum;\n}'));
adapter.sink.write(createTextFrame(' let sum = a + b;'));
// Audio transcription (multipart file upload)
const audioBuffer = fs.readFileSync('audio.mp3');
adapter.sink.write({ type: 'AUDIO', content: { data: audioBuffer }, metadata: { mimeType: 'audio/mpeg' } });
// TTS
adapter.sink.write(createParamFrame('task', 'tts'));
adapter.sink.write(createParamFrame('voice', 'mistral'));
adapter.sink.write({ type: 'AUDIO', content: { text: 'Hello, world!' } });
// Cost estimation via COST_CHECK protocol
adapter.sink.write({ type: 'COST_CHECK', content: { maxBudget: 0.01 } });
adapter.sink.write(createTextFrame('Estimate cost only — no API call'));
// Runtime API key injection
adapter.setReady('sk-new-key'); // or via PARAM: adapter.sink.write(createParamFrame('apiKey', 'sk-...'))
Install
npm install @polyweave/mistral
Why
Mistral offers five distinct API surfaces (chat, embeddings, FIM, transcription, TTS) each with different request formats — JSON, multipart/form-data, and SSE streaming. Polyweave harnesses require these unified behind a single Frame-duplex protocol so that budget controllers, topology scanners, and test injectors can operate identically across all capabilities. This adapter translates every capability into the canonical PARAM-accumulation pattern, emits RECEIPT frames with per-second audio pricing and per-token text pricing, and supports progressive receipts during SSE streams for real-time cost tracking.
API
createMistralAdapter(config)
Creates an IFrameDuplex stream adapter. Returns { source, sink, setReady, resetConversation, _conversation, name }.
Parameters:
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
config.apiKey |
string |
No | process.env.MISTRAL_API_KEY |
Mistral API key |
config.baseURL |
string |
No | 'https://api.mistral.ai' |
API base URL |
config.timeout |
number |
No | 60000 |
Request timeout in milliseconds |
config.name |
string |
No | null |
Node name for PARAM targeting |
Return value:
IFrameDuplex — a duplex stream with .source (readable) and .sink (writable). Additional methods:
duplex.setReady(apiKey)— set API key at runtime, flushes buffered framesduplex.resetConversation()— clears accumulated conversation historyduplex._conversation()— returns a copy of the current conversation arrayduplex.name— node name (fromconfig.name, ornull)
Frame Protocol
Input frames:
| Frame Type | Purpose | Content fields |
|---|---|---|
PARAM |
Accumulate configuration | { key, value } — persists across requests until overridden |
TEXT |
Chat completion prompt | { text }, metadata: role, stream |
AUDIO |
Audio transcription or TTS | { data: Buffer | string }, metadata: mimeType |
EMBEDDING |
Embedding request | { text } (alternative to TEXT + task=embeddings) |
TOOL_RESULT |
Tool call results | { toolCallId, result, content } |
COST_CHECK |
Enable dry-run mode | { maxBudget, currency } |
ESTIMATED_TEXT |
Virtual text for cost estimation (no API call) | { text } |
ESTIMATED_AUDIO |
Virtual audio for cost estimation | { data, mimeType, text } |
ESTIMATED_OUTPUT |
Estimated output metadata | { length, tokens, count } |
ESTIMATED_IMAGE |
Virtual image for cost estimation | { width, height, quality } |
ESTIMATED_VIDEO |
Virtual video for cost estimation | { duration, size } |
START |
Request scope marker (pass-through) | — |
END |
Request scope marker (pass-through) | — |
TOPOLOGY_SCAN |
Trigger topology report | { path } |
TOPOLOGY_REPORT |
Topology reports (pass-through) | — |
TOPOLOGY_COMPLETE |
Topology complete (pass-through) | — |
TEST_INJECT |
Inject test frames | { frames: Frame[] } |
TEST_SNAPSHOT |
Request snapshot | — |
Output frames:
| Frame Type | Emitted when | Content |
|---|---|---|
TEXT |
Chat response chunks or full response | { text, embedding }, metadata: role, model, finishReason, streaming, type (embedding/transcription/fim) |
TOOL_CALL |
Tool calls from chat | { name, arguments }, metadata: toolCallId, toolCallType |
AUDIO |
TTS audio data | { data: Buffer }, content: { mimeType } |
RECEIPT |
Cost receipt after response or during streaming | { inputTokens, outputTokens, inputCost, outputCost, totalCost, audioSeconds }, metadata: provider, model, progressive, final |
COST |
Cost check estimate (before API call) | { cost, bounds, estimateSource, accuracy, forwardEstimate, inputBounds, maxOutputTokens } |
ERROR |
Error conditions | { message, code }, metadata: providerError, statusCode |
END |
End of response | metadata: usage, model, error (if error), language (audio) |
TOPOLOGY_REPORT |
Topology scan response | { kind, id, connections, state, receives, generates, path } |
TEST_SNAPSHOT |
Test snapshot | { kind, id, paused } |
META |
Unknown frame type pass-through | { passthrough, frameType, note } |
PARAM Keys (persistent configuration)
Chat: model, max_tokens, temperature, top_p, stream, system, tools, tool_choice, response_format, logprobs, top_logprobs, prompt_mode, safe_prompt, random_seed, frequency_penalty, presence_penalty, n, stop, messages
Embeddings: input, encoding_format, output_dimension, output_dtype
FIM: prompt, suffix, min_tokens
Audio: file_id, file_url, language, timestamp_granularities, filename
TTS: voice (default 'mistral'), output_format (default 'mp3')
Task routing: task — 'embeddings' / 'embed', 'fim' / 'code' / 'completion', 'tts' / 'speech' / 'synthesis'; defaults to chat
Cost/receipt: receipt_mode ('none' | 'final' | 'progressive', default 'final'), receipt_interval (default 50 tokens)
Auth: apiKey (triggers setReady and flushes buffered frames)
Task Routing
Frame types are routed to handlers based on params.task metadata:
| task value | Frame type | Handler | API endpoint |
|---|---|---|---|
| (default) | TEXT |
handleChat |
/v1/chat/completions |
embeddings / embed |
TEXT, EMBEDDING |
handleEmbeddings |
/v1/embeddings |
fim / code / completion |
TEXT |
handleFim |
/v1/fim/completions |
| (default) | AUDIO |
handleAudio |
/v1/audio/transcriptions |
tts / speech / synthesis |
AUDIO |
handleTTS |
/v1/audio/speech |
Cost Estimation Protocol
- Send a
COST_CHECKframe to enter dry-run mode - Send content frames (
TEXT,AUDIO,ESTIMATED_*) - The adapter estimates cost via
@polyweave/costsand emits aCOSTframe - If
maxBudgetis set on theCOST_CHECKframe and estimated cost exceeds it, emitsERRORwith code'BUDGET_EXCEEDED'followed byEND - Cost check mode exits after emitting the estimate
The cost frame content includes: cost (object with inputCost, outputCost, totalCost), bounds (lower/upper confidence bounds), estimateSource (e.g. 'token-count', 'character-heuristic'), accuracy (expected error), forwardEstimate, inputBounds, maxOutputTokens.
Error Conditions
All errors are emitted as ERROR frames (type 'ERROR') through duplex.source.sink, followed by an END frame with metadata: { error: true, errorCode }.
| Condition | Error Code | When |
|---|---|---|
| Network error | PROVIDER_ERROR |
HTTP request fails (req.on('error')), stream errors, upload failures |
| HTTP 401/403 | AUTHENTICATION_ERROR / AUTHORIZATION_ERROR |
Invalid or expired API key |
| HTTP 429 | RATE_LIMIT_EXCEEDED |
Rate limit exceeded |
| HTTP 400/422 | INVALID_INPUT |
Malformed request parameters |
| HTTP 500+ | PROVIDER_ERROR |
Mistral server error |
| JSON parse failure | PROTOCOL_ERROR |
API response cannot be parsed |
| No audio input | INVALID_INPUT |
Audio frame has no file buffer, file_id, or file_url |
| Budget exceeded (COST_CHECK) | BUDGET_EXCEEDED |
Estimated cost > maxBudget in cost check mode |
| Unrecognized frame type | META frame (not an error) |
Pass-through with passthrough: true note |
The mapMistralError function in src/utils/errorMapper.js maps Mistral errors using @polyweave/errors:
- Uses
error.statusto determine error code viamapHttpStatusToErrorCode - 401/403 → authentication/auth errors
- 429 → rate limit
- 400/422 → invalid input
- All others → provider error (with generic message for 500+)
- Includes
providerErrorobject with originalstatus,message,typein metadata
Audio Transcription Input Modes
The audio handler supports three input formats:
- File buffer (
Buffer.isBuffer(audioData)): Sends asmultipart/form-datawith auto-generated boundary. Usesparams.filenamefor the filename field (default'audio.mp3'). file_id(via params): JSON request withfile_idfield (previously uploaded file).file_url(via params): JSON request withfile_urlfield (remote file URL).
Streaming transcription emits incremental TEXT frames, detects and skips the final summary chunk (detected by presence of segments[] after content has accumulated), and handles TTS audio data as AUDIO frames.
Conversation Management
- Incoming TEXT frames push
{ role, content }to an internal conversation array - TOOL_RESULT frames push
{ role: 'tool', tool_call_id, content }to conversation - Assistant responses from chat are recorded via
recordAssistant()which captures role, content, and tool_calls duplex.resetConversation()clears the array;duplex._conversation()returns a snapshot
Re-exports
This package re-exports getModelCost and calculateTokenCost from @polyweave/costs via src/costs.js.
Tests
npm test
Runs node --test test/mistral.test.js test/rigor.test.js. Covers:
createMistralAdapterconstruction with default config- Adapter topology report structure — verifies receives and generates frame types
- PARAM frame accumulation (model, temperature, etc.)
- FIM, embedding, and audio default configurations
setReadyflushes buffered framesresetConversationclears conversationCOST_CHECKprotocol: entry, estimation frames, budget exceeded, exit
Requirements
- Node.js >= 22.0.0
- ESM only (
"type": "module") - Peer dependencies:
@polyweave/core,@polyweave/errors,@polyweave/costs
Dependencies
Development Dependencies
| ID | Version |
|---|---|
| @rigor/core | * |
Peer Dependencies
| ID | Version |
|---|---|
| @polyweave/core | * |
| @polyweave/costs | * |
| @polyweave/errors | * |