Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 4 additions & 2 deletions dev-packages/node-integration-tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,10 @@
"@koa/router": "^12.0.1",
"@langchain/anthropic": "^0.3.10",
"@langchain/core": "^0.3.80",
"@langchain/openai": "^0.5.0",
"@langchain/langgraph": "^0.2.32",
"@langchain/openai": "^0.5.0",
"@mastra/core": "1.63.2",
"@mastra/observability": "1.17.4",
"@modelcontextprotocol/client": "^2.0.0",
"@modelcontextprotocol/server": "^2.0.0",
"@nestjs/common": "^11",
Expand All @@ -53,9 +55,9 @@
"@prisma/client": "6.15.0",
"@sentry/aws-serverless": "10.67.0",
"@sentry/core": "10.67.0",
"@sentry/server-utils": "10.67.0",
"@sentry/hono": "10.67.0",
"@sentry/node": "10.67.0",
"@sentry/server-utils": "10.67.0",
"@types/mongodb": "^3.6.20",
"@types/mysql": "^2.15.21",
"@types/pg": "^8.6.5",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import * as Sentry from '@sentry/node';
import { loggingTransport } from '@sentry-internal/node-integration-tests';

Sentry.init({
traceLifecycle: 'static',
dsn: 'https://public@dsn.ingest.sentry.io/1337',
release: '1.0',
tracesSampleRate: 1.0,
dataCollection: { genAI: { inputs: false, outputs: false } },
integrations: [Sentry.mastraIntegration({ bootstrapObservability: false })],
transport: loggingTransport,
beforeSendTransaction: event => {
// Drop the in-process mock provider's own transactions.
return event.transaction?.includes('/v1/chat/completions') ? null : event;
},
});
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({
traceLifecycle: 'static',
dsn: 'https://public@dsn.ingest.sentry.io/1337',
release: '1.0',
tracesSampleRate: 1.0,
dataCollection: { genAI: { inputs: true, outputs: true } },
transport: loggingTransport,
beforeSendTransaction: event => {
// Drop the in-process mock provider's own transactions.
return event.transaction?.includes('/v1/chat/completions') ? null : event;
},
});
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({
traceLifecycle: 'static',
dsn: 'https://public@dsn.ingest.sentry.io/1337',
release: '1.0',
tracesSampleRate: 1.0,
dataCollection: { genAI: { inputs: false, outputs: false } },
transport: loggingTransport,
beforeSendTransaction: event => {
// Drop the in-process mock provider's own transactions.
return event.transaction?.includes('/v1/chat/completions') ? null : event;
},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import express from 'express';
import * as Sentry from '@sentry/node';
import { Mastra } from '@mastra/core';
import { Agent } from '@mastra/core/agent';

// Inlined OpenAI-compatible mock: the ESM/CJS runner copies only this file into its temp dir.
function startMockProvider(responses) {
const app = express();
app.use(express.json());

let call = 0;
app.post('/v1/chat/completions', (req, res) => {
const response = responses[Math.min(call, responses.length - 1)];
call++;
res.json({
id: response.id,
object: 'chat.completion',
created: 1,
model: req.body.model,
choices: [
{
index: 0,
finish_reason: response.toolCalls ? 'tool_calls' : 'stop',
message: {
role: 'assistant',
content: response.content ?? null,
...(response.toolCalls ? { tool_calls: response.toolCalls } : {}),
},
},
],
usage: response.usage,
});
});

const server = app.listen(0);
return {
url: `http://localhost:${server.address().port}/v1`,
close: () => server.close(),
};
}

const provider = startMockProvider([
{
id: 'chatcmpl-1',
content: 'It is 22C in Berlin.',
usage: { prompt_tokens: 12, completion_tokens: 7, total_tokens: 19 },
},
]);

async function run() {
const agent = new Agent({
id: 'weather_agent',
name: 'weather_agent',
instructions: 'You report the weather.',
model: { id: 'openai/gpt-4o-mini', url: provider.url, apiKey: 'test' },
});

const mastra = new Mastra({
agents: { weather_agent: agent },
logger: false,
});

await Sentry.startSpan({ op: 'function', name: 'mastra-test' }, async () => {
await mastra.getAgent('weather_agent').generate('What is the weather in Berlin?');
});

await mastra.observability.shutdown();
await Sentry.flush(2000);
provider.close();
}

run();
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import express from 'express';
import * as Sentry from '@sentry/node';
import { Mastra } from '@mastra/core';
import { Agent } from '@mastra/core/agent';
import { Observability } from '@mastra/observability';

// Inlined OpenAI-compatible mock: the ESM/CJS runner copies only this file into its temp dir.
function startMockProvider(responses) {
const app = express();
app.use(express.json());

let call = 0;
app.post('/v1/chat/completions', (req, res) => {
const response = responses[Math.min(call, responses.length - 1)];
call++;
res.json({
id: response.id,
object: 'chat.completion',
created: 1,
model: req.body.model,
choices: [
{
index: 0,
finish_reason: response.toolCalls ? 'tool_calls' : 'stop',
message: {
role: 'assistant',
content: response.content ?? null,
...(response.toolCalls ? { tool_calls: response.toolCalls } : {}),
},
},
],
usage: response.usage,
});
});

const server = app.listen(0);
return {
url: `http://localhost:${server.address().port}/v1`,
close: () => server.close(),
};
}

const provider = startMockProvider([
{
id: 'chatcmpl-1',
content: 'It is 22C in Berlin.',
usage: { prompt_tokens: 12, completion_tokens: 7, total_tokens: 19 },
},
]);

async function run() {
// Stub of `@mastra/sentry`: same `name: 'sentry'`, no brand. The real package calls `Sentry.init()`.
const communityExporter = {
name: 'sentry',
async exportTracingEvent() {},
async flush() {},
async shutdown() {},
};

const agent = new Agent({
id: 'weather_agent',
name: 'weather_agent',
instructions: 'You report the weather.',
model: { id: 'openai/gpt-4o-mini', url: provider.url, apiKey: 'test' },
});

const mastra = new Mastra({
agents: { weather_agent: agent },
logger: false,
observability: new Observability({
configs: {
default: { serviceName: 'mastra-test', exporters: [communityExporter] },
},
}),
});

await Sentry.startSpan({ op: 'function', name: 'mastra-test' }, async () => {
await mastra.getAgent('weather_agent').generate('What is the weather in Berlin?');
});

await mastra.observability.shutdown();
await Sentry.flush(2000);
provider.close();
}

run();
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import express from 'express';
import * as Sentry from '@sentry/node';
import { z } from 'zod';
import { Mastra } from '@mastra/core';
import { Agent } from '@mastra/core/agent';
import { createTool } from '@mastra/core/tools';
import { Observability } from '@mastra/observability';
import { SentryMastraExporter } from '@sentry/node';

// Two-step agent loop. Inlined OpenAI-compatible mock: the ESM/CJS runner copies only this file.
function startMockProvider(responses) {
const app = express();
app.use(express.json());

let call = 0;
app.post('/v1/chat/completions', (req, res) => {
const response = responses[Math.min(call, responses.length - 1)];
call++;
res.json({
id: response.id,
object: 'chat.completion',
created: 1,
model: req.body.model,
choices: [
{
index: 0,
finish_reason: response.toolCalls ? 'tool_calls' : 'stop',
message: {
role: 'assistant',
content: response.content ?? null,
...(response.toolCalls ? { tool_calls: response.toolCalls } : {}),
},
},
],
usage: response.usage,
});
});

const server = app.listen(0);
return {
url: `http://localhost:${server.address().port}/v1`,
close: () => server.close(),
};
}

const provider = startMockProvider([
{
id: 'chatcmpl-tool',
toolCalls: [
{
id: 'call_1',
type: 'function',
function: { name: 'get_weather', arguments: '{"city":"Berlin"}' },
},
],
usage: { prompt_tokens: 20, completion_tokens: 5, total_tokens: 25 },
},
{
id: 'chatcmpl-final',
content: 'It is 22C in Berlin.',
usage: { prompt_tokens: 30, completion_tokens: 8, total_tokens: 38 },
},
]);

async function run() {
const agent = new Agent({
id: 'weather_agent',
name: 'weather_agent',
instructions: 'Use the weather tool.',
model: { id: 'openai/gpt-4o-mini', url: provider.url, apiKey: 'test' },
tools: {
get_weather: createTool({
id: 'get_weather',
description: 'Get weather for a city',
inputSchema: z.object({ city: z.string() }),
execute: async ({ city }) => ({ temperature: 22, city }),
}),
},
});

const mastra = new Mastra({
agents: { weather_agent: agent },
logger: false,
observability: new Observability({
configs: {
default: {
serviceName: 'mastra-test',
exporters: [new SentryMastraExporter()],
},
},
}),
});

await Sentry.startSpan({ op: 'function', name: 'mastra-test' }, async () => {
await mastra.getAgent('weather_agent').generate('Weather in Berlin?', { maxSteps: 3 });
});

await mastra.observability.shutdown();
await Sentry.flush(2000);
provider.close();
}

run();
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import * as Sentry from '@sentry/node';
import { z } from 'zod';
import { Mastra } from '@mastra/core';
import { createStep, createWorkflow } from '@mastra/core/workflows';
import { Observability } from '@mastra/observability';
import { SentryMastraExporter } from '@sentry/node';

// `workflow_run` maps to `gen_ai.invoke_agent`; `workflow_step` has no conventional op and is dropped.
const double = createStep({
id: 'double',
inputSchema: z.object({ n: z.number() }),
outputSchema: z.object({ n: z.number() }),
execute: async ({ inputData }) => ({ n: inputData.n * 2 }),
});

const workflow = createWorkflow({
id: 'math_workflow',
inputSchema: z.object({ n: z.number() }),
outputSchema: z.object({ n: z.number() }),
})
.then(double)
.commit();

async function run() {
const mastra = new Mastra({
workflows: { math_workflow: workflow },
logger: false,
observability: new Observability({
configs: {
default: {
serviceName: 'mastra-test',
exporters: [new SentryMastraExporter()],
},
},
}),
});

await Sentry.startSpan({ op: 'function', name: 'mastra-test' }, async () => {
const workflowRun = await mastra.getWorkflow('math_workflow').createRun();
await workflowRun.start({ inputData: { n: 21 } });
});

await mastra.observability.shutdown();
await Sentry.flush(2000);
}

run();
Loading
Loading