Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
f3e7762
test(node): Add failing integration tests for Mistral AI SDK
nicohrubec Sep 9, 2026
bc0fbe4
feat(node): Add Mistral AI integration
nicohrubec Sep 9, 2026
cd4eb98
fix(mistral): Fix CI — re-exports, deno snapshot, and CJS tests
nicohrubec Sep 9, 2026
962495d
feat(mistral): Drop fim (text_completion) from initial scope
nicohrubec Sep 9, 2026
befee31
test(mistral): Run the suite ESM-only
nicohrubec Sep 9, 2026
c2109c7
fix(mistral): Skip Mistral provider wrapping when LangChain is active
nicohrubec Sep 9, 2026
224d3e8
feat(mistral): Capture tool definitions and tool calls (OpenAI parity)
nicohrubec Sep 11, 2026
4d5db78
refactor(mistral): Source span-name model from extracted attributes
nicohrubec Sep 11, 2026
1af6176
refactor(mistral): Simplify streaming to match OpenAI
nicohrubec Sep 11, 2026
ed44af2
fix(mistral): Use `mistralai` as the provider name
nicohrubec Sep 11, 2026
ddcd554
refactor(mistral): Rename instrumentMistralClient to instrumentMistra…
nicohrubec Sep 11, 2026
df92709
Merge remote-tracking branch 'origin/develop' into nh/mistral-integra…
nicohrubec Sep 11, 2026
a06b6d9
fix(node): Address Mistral stream review comments
nicohrubec Sep 11, 2026
dafd2c6
chore(node): Bump @sentry/node size limits for Mistral integration
nicohrubec Sep 11, 2026
bf24777
fix(node): Address Mistral integration review findings
RulaKhaled Sep 14, 2026
4c27331
fix(node): Keep a cancelled Mistral stream out of error status
RulaKhaled Sep 14, 2026
a7a81f0
fix(node): Instrument Mistral streams drained via tee, pipeTo or pipe…
RulaKhaled Sep 14, 2026
72a43ce
test(node): Cover the Mistral structured-output entry points
RulaKhaled Sep 14, 2026
8a65c63
Update dev-packages/node-integration-tests/suites/tracing/mistral/ins…
JPeer264 Sep 15, 2026
16f1dde
Update dev-packages/node-integration-tests/suites/tracing/mistral/ins…
JPeer264 Sep 15, 2026
ca6cdad
Update dev-packages/node-integration-tests/suites/tracing/mistral/ins…
JPeer264 Sep 15, 2026
f736fe8
Update dev-packages/node-integration-tests/suites/tracing/mistral/ins…
JPeer264 Sep 15, 2026
89bf153
Update packages/server-utils/src/integrations/mistral.ts
JPeer264 Sep 15, 2026
4870f05
ref: Remove low cardinality span names for transactions
JPeer264 Sep 15, 2026
1579110
ref: Match OpenAI and AntropicAI usage of originalResult
JPeer264 Sep 15, 2026
6d04ea5
Merge remote-tracking branch 'origin' into nh/mistral-integration
JPeer264 Sep 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions dev-packages/node-integration-tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"@langchain/core": "^0.3.80",
"@langchain/langgraph": "^0.2.32",
"@langchain/openai": "^0.5.0",
"@mistralai/mistralai": "2.6.4",
"@modelcontextprotocol/client": "^2.0.0",
"@modelcontextprotocol/server": "^2.0.0",
"@nestjs/common": "^11",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import * as Sentry from '@sentry/node';
import { loggingTransport } from '@sentry-internal/node-integration-tests';

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
release: '1.0',
tracesSampleRate: 1.0,
dataCollection: { genAI: { inputs: true, outputs: true } },
transport: loggingTransport,
// `instrumentMistralAiClient` is the manual path for runtimes without the orchestrion hook.
// Drop the automatic integration so the scenario exercises it alone.
integrations: integrations => integrations.filter(integration => integration.name !== 'Mistral'),
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import * as Sentry from '@sentry/node';
import { loggingTransport } from '@sentry-internal/node-integration-tests';

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
release: '1.0',
tracesSampleRate: 1.0,
transport: loggingTransport,
integrations: [
Sentry.mistralAIIntegration({
recordInputs: true,
recordOutputs: true,
}),
],
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import * as Sentry from '@sentry/node';
import { loggingTransport } from '@sentry-internal/node-integration-tests';

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
release: '1.0',
tracesSampleRate: 1.0,
dataCollection: { genAI: { inputs: true, outputs: true } },
transport: loggingTransport,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import * as Sentry from '@sentry/node';
import { loggingTransport } from '@sentry-internal/node-integration-tests';

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
release: '1.0',
tracesSampleRate: 1.0,
dataCollection: { genAI: { inputs: false, outputs: false } },
transport: loggingTransport,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { Mistral } from '@mistralai/mistralai';
import * as Sentry from '@sentry/node';
import express from 'express';

function startMockServer() {
const app = express();
app.use(express.json());

app.post('/v1/agents/completions', (req, res) => {
const { agent_id: agentId, stream } = req.body;

if (agentId === 'error-agent') {
res.status(404).set('x-request-id', 'mock-request-123').end('Agent not found');
return;
}

if (stream) {
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');

const chunks = [
{
id: 'agentcmpl-stream-123',
object: 'chat.completion.chunk',
created: 1677652300,
model: 'mistral-large-latest',
choices: [
{
index: 0,
delta: { role: 'assistant', content: '' },
finish_reason: null,
},
],
},
{
id: 'agentcmpl-stream-123',
object: 'chat.completion.chunk',
created: 1677652300,
model: 'mistral-large-latest',
choices: [
{
index: 0,
delta: { content: 'Hello from Mistral agent streaming!' },
finish_reason: null,
},
],
},
{
id: 'agentcmpl-stream-123',
object: 'chat.completion.chunk',
created: 1677652300,
model: 'mistral-large-latest',
choices: [{ index: 0, delta: {}, finish_reason: 'stop' }],
usage: { prompt_tokens: 12, completion_tokens: 18, total_tokens: 30 },
},
];

chunks.forEach((chunk, index) => {
setTimeout(() => {
res.write(`data: ${JSON.stringify(chunk)}\n\n`);
if (index === chunks.length - 1) {
res.write('data: [DONE]\n\n');
res.end();
}
}, index * 10);
});
} else {
res.send({
id: 'agentcmpl-mock123',
object: 'chat.completion',
created: 1677652288,
model: 'mistral-large-latest',
choices: [
{
index: 0,
message: {
role: 'assistant',
content: 'Hello from Mistral agent!',
},
finish_reason: 'stop',
},
],
usage: { prompt_tokens: 10, completion_tokens: 15, total_tokens: 25 },
});
}
});

return new Promise(resolve => {
const server = app.listen(0, () => {
resolve(server);
});
});
}

async function run() {
const server = await startMockServer();

await Sentry.startSpan({ op: 'function', name: 'main' }, async () => {
const client = new Mistral({
apiKey: 'mock-api-key',
serverURL: `http://localhost:${server.address().port}`,
});

await client.agents.complete({
agentId: 'ag-mock-123',
messages: [{ role: 'user', content: 'Who is the best French painter?' }],
});

const stream = await client.agents.stream({
agentId: 'ag-mock-123',
messages: [{ role: 'user', content: 'Tell me about streaming' }],
});

for await (const event of stream) {
void event;
}
});

await Sentry.flush(2000);
server.close();
}

run();
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import { Mistral } from '@mistralai/mistralai';
import * as Sentry from '@sentry/node';
import express from 'express';

function startMockServer() {
const app = express();
app.use(express.json());

app.post('/v1/chat/completions', (req, res) => {
const { model, stream } = req.body;

// error-model returns 404 (not retried by the SDK) so the span records an error
if (model === 'error-model') {
res.status(404).set('x-request-id', 'mock-request-123').end('Model not found');
return;
}

if (stream) {
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');

const chunks = [
{
id: 'chatcmpl-stream-123',
object: 'chat.completion.chunk',
created: 1677652300,
model,
choices: [
{
index: 0,
delta: { role: 'assistant', content: '' },
finish_reason: null,
},
],
},
{
id: 'chatcmpl-stream-123',
object: 'chat.completion.chunk',
created: 1677652300,
model,
choices: [
{
index: 0,
delta: { content: 'Hello from Mistral streaming!' },
finish_reason: null,
},
],
},
{
id: 'chatcmpl-stream-123',
object: 'chat.completion.chunk',
created: 1677652300,
model,
choices: [{ index: 0, delta: {}, finish_reason: 'stop' }],
usage: { prompt_tokens: 12, completion_tokens: 18, total_tokens: 30 },
},
];

chunks.forEach((chunk, index) => {
setTimeout(() => {
res.write(`data: ${JSON.stringify(chunk)}\n\n`);
if (index === chunks.length - 1) {
res.write('data: [DONE]\n\n');
res.end();
}
}, index * 10);
});
} else {
res.send({
id: 'chatcmpl-mock123',
object: 'chat.completion',
created: 1677652288,
model,
choices: [
{
index: 0,
message: { role: 'assistant', content: 'Hello from Mistral mock!' },
finish_reason: 'stop',
},
],
usage: { prompt_tokens: 10, completion_tokens: 15, total_tokens: 25 },
});
}
});

return new Promise(resolve => {
const server = app.listen(0, () => {
resolve(server);
});
});
}

async function run() {
const server = await startMockServer();

await Sentry.startSpan({ op: 'function', name: 'main' }, async () => {
const client = new Mistral({
apiKey: 'mock-api-key',
serverURL: `http://localhost:${server.address().port}`,
});

await client.chat.complete({
model: 'mistral-small-latest',
messages: [
{ role: 'system', content: 'You are a helpful assistant.' },
{ role: 'user', content: 'What is the capital of France?' },
],
temperature: 0.7,
maxTokens: 100,
});

try {
await client.chat.complete({
model: 'error-model',
messages: [{ role: 'user', content: 'This will fail' }],
});
} catch {
// expected
}

const stream = await client.chat.stream({
model: 'mistral-large-latest',
messages: [{ role: 'user', content: 'Tell me about streaming' }],
temperature: 0.8,
});

for await (const event of stream) {
void event;
}
});

await Sentry.flush(2000);
server.close();
}

run();
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { Mistral } from '@mistralai/mistralai';
import * as Sentry from '@sentry/node';
import express from 'express';

function startMockServer() {
const app = express();
app.use(express.json());

app.post('/v1/embeddings', (req, res) => {
const { model, inputs } = req.body;

if (model === 'error-model') {
res.status(404).set('x-request-id', 'mock-request-123').end('Model not found');
return;
}

// Distinct id per call shape so tests can target the single-input span unambiguously.
res.send({
id: Array.isArray(inputs) ? 'embd-mock-multi' : 'embd-mock123',
object: 'list',
model,
data: [{ object: 'embedding', embedding: [0.1, 0.2, 0.3], index: 0 }],
usage: { prompt_tokens: 8, total_tokens: 8 },
});
});

return new Promise(resolve => {
const server = app.listen(0, () => {
resolve(server);
});
});
}

async function run() {
const server = await startMockServer();

await Sentry.startSpan({ op: 'function', name: 'main' }, async () => {
const client = new Mistral({
apiKey: 'mock-api-key',
serverURL: `http://localhost:${server.address().port}`,
});

await client.embeddings.create({
model: 'mistral-embed',
inputs: 'Embedding test!',
});

try {
await client.embeddings.create({
model: 'error-model',
inputs: 'Error embedding test!',
});
} catch {
// expected
}

await client.embeddings.create({
model: 'mistral-embed',
inputs: ['First input text', 'Second input text'],
});
});

await Sentry.flush(2000);
server.close();
}

run();
Loading
Loading