Skip to content

Mistral provider

Standalone — usable in any Node project, no @warlock.js/core required.

@warlock.js/ai-mistral is the Mistral AI provider adapter for @warlock.js/ai. Mistral exposes an OpenAI-compatible API, so MistralSDK is a thin wrapper over @warlock.js/ai-openai — it does not re-implement the wire protocol, streaming, tool calls, structured output, error wrapping, or token accounting. It builds one internal OpenAISDK pointed at Mistral’s baseURL (https://api.mistral.ai/v1) with provider: "mistral", then layers on Mistral-aware capability inference and default pricing.

Terminal window
npm install @warlock.js/ai @warlock.js/ai-mistral
import { MistralSDK } from "@warlock.js/ai-mistral";
const mistral = new MistralSDK({
apiKey: process.env.MISTRAL_API_KEY!,
});

baseURL defaults to https://api.mistral.ai/v1 (the OpenAI-compatible endpoint serving /chat/completions and /embeddings). provider defaults to "mistral" and flows through to ModelContract.provider, AgentReport.model.provider, and logs. Override baseURL only to reach a Mistral-compatible gateway or proxy — every other upstream OpenAI ClientOptions value (timeout, maxRetries, defaultHeaders, fetch, …) is forwarded verbatim.

const model = mistral.model({ name: "mistral-large-latest" });
// Pass to any @warlock.js/ai agent / workflow / supervisor.

The headline -latest aliases are exported as MISTRAL_MODELS ({ chat, vision, reasoning, embedding }) for convenience, but mistral.model() also accepts any version-pinned id (mistral-large-2512, magistral-medium-2509):

mistral.model({ name: "mistral-large-latest" }); // multimodal flagship
mistral.model({ name: "magistral-medium-latest" }); // reasoning
mistral.model({ name: "pixtral-large-latest" }); // vision
const embedder = mistral.embedder({ name: "mistral-embed" });
const { vector, dimensions, usage } = await embedder.embed("Hello world");
const { vectors } = await embedder.embedMany(["doc 1", "doc 2"]);

mistral-embed is reachable through the OpenAI-compatible /v1/embeddings endpoint; the embedder delegates straight to the wrapped OpenAI embedder.

The wrapper injects Mistral-aware inference before delegating to the OpenAI adapter (the OpenAI prefix lists never match Mistral names):

  • Vision — auto-inferred true for the pixtral family and the recent multimodal generations (mistral-large, mistral-medium, ministral-3); false otherwise.
  • Reasoning — auto-inferred true for the magistral family and the hybrid mistral-small generation; false otherwise. Drives whether reasoning_effort is forwarded.
  • Structured output — true by default, unless responseFormat is forced to "json_object" or "text".
  • Prompt caching — true, inherited from the OpenAI adapter (read-side cachedTokens accounting).
  • PDF / audio — false by default; opt in with .model({ pdf: true }) or { audio: true }.

An explicit flag always wins over inference:

const model = mistral.model({ name: "some-fine-tune", vision: true });

The adapter ships a default registry (MISTRAL_DEFAULT_PRICING, USD per 1,000,000 tokens, with the prompt-cache rate as cachedInput) so cost truth works out of the box. The rates come from Mistral’s pricing and the model pages (API ids and aliases), fetched 2026-10-17: Large 3, Medium 3.5, Small 4, Ministral 14B, Codestral and mistral-embed, under their -latest aliases and pinned ids. Mistral Large 4 (preview, temporary sale price) and the retired Pixtral / Magistral models are not included. Pass your own pricing — keyed by model id — to win per model:

import { MistralSDK } from "@warlock.js/ai-mistral";
const mistral = new MistralSDK({
apiKey: process.env.MISTRAL_API_KEY ?? "",
pricing: {
// USD per 1M tokens — contract rates win over the defaults.
"mistral-large-latest": { input: 0.45, output: 1.35, cachedInput: 0.045 },
},
});
// Or per model, overriding the SDK entry:
const model = mistral.model({
name: "mistral-small-latest",
pricing: { input: 0.15, output: 0.6, cachedInput: 0.015 },
});

Resolution at model() time: per-model pricing > SDK-level pricing > MISTRAL_DEFAULT_PRICING > undefined.

Every response reports token usage (input, output, cachedTokens, reasoningTokens) — on model.complete() and the streaming done chunk too, with usage.cost when priced. prompt_tokens contains the cached tokens (inputIncludesCache: true). Need an offline estimate before a call? mistral.count(text) returns a fast character-heuristic approximation — good for budgeting and quota guards, not for billing.

Prompt caching. Mistral documents a prompt_cache_key request field (prompt caching). Set promptCacheKey on the model — sent while the SDK keeps its default "mistral" provider label — to route related requests to the same cache:

const cached = mistral.model({ name: "mistral-large-latest", promptCacheKey: "conversation:42" });

An overloaded endpoint (HTTP 503) surfaces as ProviderOverloadedError (code: "PROVIDER_OVERLOADED", retryable: true); every provider error carries status, retryable and billed (false for 401 / 402 / 403, otherwise "unknown").

Because Mistral speaks the OpenAI Chat Completions + Embeddings protocol, everything the OpenAI adapter does applies unchanged here: streaming deltas with consolidated tool calls, response_format: json_schema structured output (override per model with responseFormat), reasoning_effort for reasoning models, multipart image input, and the neutral Usage breakdown. Raw errors are wrapped into the typed @warlock.js/ai AIError hierarchy by the wrapped adapter (dispatch keys on APIError.status + code).

For the full wire-level surface — it is identical here — see the OpenAI provider page and the warlock-js-ai-mistral skill.

  • OpenAI provider — the wrapped adapter; identical wire behavior.
  • Groq provider — another OpenAI-compatible wrapper, for Groq-hosted open models.
  • @warlock.js/ai — passing the model into ai.agent({ ... }).

This adapter delegates Chat Completions to the OpenAI adapter, so providerOptions.openai.body adds native Chat request fields. Other provider keys are ignored. Plain-object fields merge one level deep with neutral options; caller-provided keys win, while arrays, scalars and null replace the field. The OpenAI adapter’s protected conversation, tool and transport fields remain protected here too. Send only fields supported by this provider; do not reuse OpenAI-only bodies across a mixed-provider fallback chain.

Models from this adapter have no native countTokens method. Check model.countTokens before using an optional native counting capability; sdk.count(text) is a separate offline estimate.