feat(node): Add Mistral AI integration - #24243
Conversation
Add span-streaming (`traceLifecycle: 'stream'`) node integration tests for a planned `@mistralai/mistralai` gen_ai integration, mirroring the OpenAI suite. Covers chat, embeddings, agents (invoke_agent) and fim (text_completion), across PII-off, PII-on and explicit-integration-option variants. These tests are expected to fail until the `mistralAIIntegration` / `instrumentMistralClient` instrumentation is implemented (TDD step 1). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
size-limit report 📦
|
Instrument `@mistralai/mistralai` v2 with gen_ai spans, turning the previously-failing integration tests green. - Automatic `mistralAIIntegration()` via the orchestrion diagnostics channels (default-on in Node) - Manual `instrumentMistralClient()` proxy for edge runtimes - Covers chat, embeddings, agents (invoke_agent) and fim (text_completion), including streaming, with `recordInputs` / `recordOutputs` controls Mistral's typed responses/usage are camelCase, so the response/stream mapping reads `promptTokens`/`completionTokens`/`totalTokens` and `choices[].finishReason` directly. `@mistralai/mistralai` v2 is ESM-only, so the CJS test variants are marked `failsOnCjs`. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Remove `failsOnCjs` from the Mistral suite: CJS auto-instrumentation works on a full build (it only failed under a partial local rebuild), so the tests pass in both ESM and CJS. - Re-export `mistralAIIntegration` / `instrumentMistralClient` from the dependent SDK packages (aws-serverless, bun, elysia, deno, google-cloud-serverless, astro, cloudflare, vercel-edge) so the node-exports consistency check passes. - Add `Mistral` to the Deno default-integrations snapshot. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Exclude the fim endpoints for now — `text_completion` is not yet used by any other AI integration, so defer it to a follow-up. Remaining scope: chat, embeddings, and agents. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`@mistralai/mistralai` v2 ships no CJS build, so CJS consumers load it via `require(esm)`, whose auto-instrumentation is inconsistent across Node versions (works on 24/26, fails on 22). The SDK's native mode is ESM, so use `createEsmTests` and cover it there only. Also give the embeddings mock a distinct id per call shape so the single-input span is targeted unambiguously. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`@langchain/mistralai` drives the `@mistralai/mistralai` SDK under the hood, so with the LangChain integration active both it and `mistralAIIntegration` would instrument the same call, producing two spans. Add `Mistral` to LangChain's `SKIPPED_PROVIDERS`, matching the existing OpenAI/Anthropic/Google handling. This also puts the previously unused `MISTRAL_INTEGRATION_NAME` constant to use. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Record request tool definitions on `gen_ai.tool.definitions` (gated by recordInputs), restoring the `recordInputs` arg to `extractRequestAttributes`. - Accumulate streamed tool calls by index (concatenating fragmented `function.arguments`) and emit them on `gen_ai.response.tool_calls`; non-streaming tool calls were already captured. - Add `scenario-tools.mjs` + a test asserting tool definitions and tool calls for both streaming and non-streaming chat. Brings Mistral to parity with the OpenAI integration for function tools. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Read the span-name token from the already-extracted request attributes (model, or the agent name for invoke_agent spans) instead of re-reading the raw params — mirroring the OpenAI integration. Removes the `getModelForSpanName` helper. No change to emitted attributes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Hoist the single `recordOutputs` check to wrap both content and tool calls (was checked twice), matching the OpenAI streaming path. - Drop the dead `?? ''` in tool-call argument accumulation (the first chunk already seeds it to ''). - Simplify the chunk unwrap to a single object guard. No behavior change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Align `gen_ai.provider.name` (and the matching `sentry.origin`) with the package-scope slug `mistralai` — consistent with how `@google/genai` maps to `google_genai`, and with the existing LangChain path which already reports Mistral as `mistralai`. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…lAiClient Align the manual instrumentation API with the other providers, which all use the `instrument<Provider>AiClient` form (`instrumentOpenAiClient`, `instrumentAnthropicAiClient`, `instrumentWorkersAiClient`). Renamed the export and all per-package re-exports. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tion # Conflicts: # packages/deno/test/__snapshots__/mod.test.ts.snap
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 3 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit df92709. Configure here.
- Set error status on the span when a Mistral stream throws mid-iteration, so failed streams no longer end as successful gen_ai spans. - Decide streaming from the SDK method alone; `stream: true` on `complete` still returns a completion in v2, so it must not be wrapped as a stream. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The default-on Mistral AI integration grows the @sentry/node bundle past the current budgets (130.52 kB > 130, 109.42 kB > 109). Raise both limits. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
| options.recordOutputs ?? false, | ||
| ) as unknown as R; | ||
| } catch (error) { | ||
| span.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' }); | ||
| span.end(); | ||
| throw error; | ||
| } | ||
| })(); | ||
| }); | ||
|
|
||
| return wrapPromiseWithMethods(originalResult, instrumentedPromise); |
There was a problem hiding this comment.
Bug: The manual instrumentation for chat.stream() returns a Promise instead of a direct async-iterable, breaking the native SDK contract and causing a TypeError for users who don't await the result.
Severity: HIGH
Suggested Fix
The instrumentation should return the async-iterable stream directly, without wrapping it in a Promise. The behavior should match the orchestrion-based integration (mistralAIIntegration), which patches the stream in-place and returns the original EventStream, preserving the native SDK's API contract.
Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.
Location: packages/server-utils/src/ai/mistral/index.ts#L135-L161
Potential issue: The manual instrumentation for the Mistral AI client,
`instrumentMistralAiClient`, alters the return type of streaming methods like
`chat.stream()`. The native SDK returns an async-iterable `EventStream` that can be
iterated over directly. However, the instrumented version wraps this in a promise,
returning a `Promise<AsyncGenerator>`. This change is not backward-compatible. Code that
follows the native SDK's usage pattern, such as `for await (const event of
client.chat.stream(...))`, will fail with a `TypeError` at runtime because the promise
returned by the instrumented function is not directly iterable. Users are forced to
change their code to `await` the result first before iterating.
Did we get this right? 👍 / 👎 to inform future reviews.

Adds a
gen_aiintegration for the@mistralai/mistralaiv2 SDK.Sentry.mistralAIIntegration()Sentry.instrumentMistralAiClient(client)Instruments
chat.complete/stream(gen_ai.chat),embeddings.create(gen_ai.embeddings), andagents.complete/stream(gen_ai.invoke_agent), including streaming, withrecordInputs/recordOutputs(PII) controls. Providermistral, originauto.ai.mistral.Trace from my local sample app:

Sorry for the large PR and the AI integrations more generally could also use some refactors. However, to get this out soon I suggest to follow up on this with a broader sweep.