Mastra — Example Configuration
JavaScript / TypeScript only — Mastra is a TS agent framework built on the Vercel AI SDK. Weflayr instruments it via a transparent JS Proxy on the Agent instance; no Mastra internals are modified.
Setup
const { weflayr_setup, weflayr_instrument } = require('weflayr');
const { Agent } = require('@mastra/core');
const { openai } = require('@ai-sdk/openai');
weflayr_setup({
intake_url: process.env.WEFLAYR_INTAKE_URL,
client_id: process.env.WEFLAYR_CLIENT_ID,
client_secret: process.env.WEFLAYR_CLIENT_SECRET,
methods: [{ call: 'generate' }],
});
const agent = weflayr_instrument(
new Agent({
name: 'my-agent',
model: openai('gpt-4o-mini'),
instructions: 'You are a helpful assistant.',
})
);
Tagging calls
Pass __weflayr_tags in the options object (second argument to generate()). The key is stripped before Mastra receives the call.
await agent.generate(
[{ role: 'user', content: 'Hello' }],
{ __weflayr_tags: { feature: 'chat', customer_id: '42', env: 'prod' } },
);
Content privacy
Mastra’s result contains the full request body (system prompt, user messages) and raw API response. Strip content before it reaches the intake:
const { weflayr_setup, weflayr_instrument } = require('weflayr');
const { Agent } = require('@mastra/core');
const { openai } = require('@ai-sdk/openai');
weflayr_setup({
intake_url: process.env.WEFLAYR_INTAKE_URL,
client_id: process.env.WEFLAYR_CLIENT_ID,
client_secret: process.env.WEFLAYR_CLIENT_SECRET,
methods: [{ call: 'generate' }],
ignore_fields: (data) => {
delete data.text;
delete data.request;
delete data.steps;
if (data.response) {
delete data.response.body;
delete data.response.messages;
}
return data;
},
});
const agent = weflayr_instrument(
new Agent({
name: 'my-agent',
model: openai('gpt-4o-mini'),
instructions: 'You are a helpful assistant.',
})
);
After applying this policy, events retain usage, finishReason, modelId, and response headers — no message content.
Streaming with agent.stream()
Mastra’s StreamTextResult is not directly async-iterable — its text chunks live under a textStream property. Use streamPath: "textStream" to point weflayr at the right iterable.
Usage and model are not in the text chunks themselves; they resolve as promises on the full result after the stream drains. The streamMiddleware factory receives fullResult (the full StreamTextResult) so you can await them in finalize().
const { weflayr_setup, weflayr_instrument } = require('weflayr');
const { Agent } = require('@mastra/core');
const { openai } = require('@ai-sdk/openai');
weflayr_setup({
intake_url: process.env.WEFLAYR_INTAKE_URL,
client_id: process.env.WEFLAYR_CLIENT_ID,
client_secret: process.env.WEFLAYR_CLIENT_SECRET,
methods: [
{ call: 'generate' },
{
call: 'stream',
streamPath: 'textStream',
streamMiddleware: (fullResult) => ({
onChunk: () => false,
async finalize() {
const rawUsage = await fullResult.usage;
const rawResponse = await fullResult.response;
const response = rawResponse?.status?.value ?? rawResponse;
return {
usage: rawUsage,
model: response?.modelId ?? null,
};
},
}),
},
],
ignore_fields: (data) => {
delete data.text;
delete data.request;
delete data.steps;
if (data.response) {
delete data.response.body;
delete data.response.messages;
}
return data;
},
});
const agent = weflayr_instrument(
new Agent({
name: 'my-agent',
model: openai('gpt-4o-mini'),
instructions: 'You are a helpful assistant.',
})
);
const streamResult = await agent.stream(
[{ role: 'user', content: 'Hello' }],
{ __weflayr_tags: { feature: 'chat', customer_id: '42' } },
);
let text = '';
for await (const chunk of streamResult.textStream) {
text += chunk;
}
The after event for a stream call contains:
| Field | Value |
|---|---|
model |
e.g. "gpt-4o-mini-2024-07-18" — from fullResult.response.modelId |
usage |
{ promptTokens, completionTokens, totalTokens } — from fullResult.usage |
response |
Last text fragment from textStream (e.g. ".") — strip with ignore_fields if unwanted |
Why rawResponse?.status?.value ?? rawResponse Mastra wraps SDK results in internal DelayedPromise objects. Calling await on one returns the wrapper ({ status: { type: "resolved", value: {...} } }) rather than the resolved value. The ?.status?.value ?? rawResponse pattern unwraps it safely while staying compatible if the underlying SDK returns a real Promise.
Configuration notes
| Setting | Value | Reason |
|---|---|---|
methods[generate] |
generate |
Top-level method on the Agent instance |
methods[stream].streamPath |
textStream |
StreamTextResult is not directly iterable; text chunks are on textStream |
streamMiddleware factory arg |
fullResult |
Weflayr passes the full SDK result so usage and response.modelId are accessible after drain |
requestArgs |
Options object (last arg) | The messages array is the first arg and is ignored; the model is not in args |
What Weflayr captures
| Field | generate | stream |
|---|---|---|
elapsed_ms |
✓ | ✓ |
tags |
✓ | ✓ |
response |
Full GenerateTextResult |
Last text chunk from textStream |
model |
— (use middleware) | From fullResult.response.modelId via streamMiddleware |
usage |
— (use middleware) | From fullResult.usage via streamMiddleware |