weflayr 0.11.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/instrument.js +10 -6
- package/tests/index.test.js +224 -0
package/package.json
CHANGED
package/src/instrument.js
CHANGED
|
@@ -94,7 +94,7 @@ function _wrapFn(fn, methodName, methodConfig) {
|
|
|
94
94
|
|
|
95
95
|
if (methodConfig.streamPath && result && _isAsyncIterable(result[methodConfig.streamPath])) {
|
|
96
96
|
_sendEvent('stream_start', { event_id: eventId, method: methodName, tags });
|
|
97
|
-
const wrappedStream = _wrapStream(result[methodConfig.streamPath], methodName, tags, requestArgs, settings, startTime, methodConfig, eventId);
|
|
97
|
+
const wrappedStream = _wrapStream(result[methodConfig.streamPath], methodName, tags, requestArgs, settings, startTime, methodConfig, eventId, result);
|
|
98
98
|
return { ...result, [methodConfig.streamPath]: wrappedStream };
|
|
99
99
|
}
|
|
100
100
|
|
|
@@ -128,9 +128,12 @@ function _wrapFn(fn, methodName, methodConfig) {
|
|
|
128
128
|
};
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
-
|
|
131
|
+
// fullResult is only set for the streamPath case — it's the full SDK result object
|
|
132
|
+
// (e.g. Mastra's StreamTextResult) so streamMiddleware factories can close over it
|
|
133
|
+
// and await properties like result.usage after the stream drains.
|
|
134
|
+
async function* _wrapStream(stream, methodName, tags, requestArgs, settings, startTime, methodConfig, eventId, fullResult) {
|
|
132
135
|
let lastChunk = null;
|
|
133
|
-
const accumulator = methodConfig.streamMiddleware ? methodConfig.streamMiddleware() : null;
|
|
136
|
+
const accumulator = methodConfig.streamMiddleware ? methodConfig.streamMiddleware(fullResult) : null;
|
|
134
137
|
|
|
135
138
|
try {
|
|
136
139
|
for await (const chunk of stream) {
|
|
@@ -141,16 +144,17 @@ async function* _wrapStream(stream, methodName, tags, requestArgs, settings, sta
|
|
|
141
144
|
method: methodName,
|
|
142
145
|
tags,
|
|
143
146
|
elapsed_ms: Date.now() - startTime,
|
|
144
|
-
...accumulator.finalize(),
|
|
147
|
+
...(await accumulator.finalize()),
|
|
145
148
|
});
|
|
146
149
|
}
|
|
147
150
|
yield chunk;
|
|
148
151
|
}
|
|
149
152
|
|
|
150
|
-
const
|
|
151
|
-
? accumulator.finalize()
|
|
153
|
+
const rawMiddlewareData = accumulator
|
|
154
|
+
? (await accumulator.finalize()) || {}
|
|
152
155
|
: (methodConfig.middleware && lastChunk ? methodConfig.middleware(requestArgs, lastChunk) || {}
|
|
153
156
|
: {});
|
|
157
|
+
const middlewareData = _applyContentPolicy(rawMiddlewareData, settings);
|
|
154
158
|
|
|
155
159
|
_sendEvent('after', {
|
|
156
160
|
event_id: eventId,
|
package/tests/index.test.js
CHANGED
|
@@ -26,6 +26,42 @@
|
|
|
26
26
|
}
|
|
27
27
|
return OpenAI;
|
|
28
28
|
}
|
|
29
|
+
if (request === '@mastra/core') {
|
|
30
|
+
function Agent(opts) {
|
|
31
|
+
this._name = opts.name;
|
|
32
|
+
}
|
|
33
|
+
Agent.prototype.generate = async function(_messages, _options) {
|
|
34
|
+
return {
|
|
35
|
+
text: 'Hello from the Mastra fake agent.',
|
|
36
|
+
modelId: 'gpt-4o-mini',
|
|
37
|
+
finishReason: 'stop',
|
|
38
|
+
usage: { promptTokens: 10, completionTokens: 15, totalTokens: 25 },
|
|
39
|
+
request: { body: '{"messages":[{"role":"user","content":"Hello"}]}' },
|
|
40
|
+
response: {
|
|
41
|
+
id: 'fake-res',
|
|
42
|
+
modelId: 'gpt-4o-mini',
|
|
43
|
+
messages: [{ role: 'assistant', content: [{ type: 'text', text: 'Hello.' }] }],
|
|
44
|
+
body: { choices: [{ message: { role: 'assistant', content: 'Hello.' } }] },
|
|
45
|
+
},
|
|
46
|
+
steps: [{ text: 'Hello.', request: { body: '...' }, finishReason: 'stop' }],
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
// stream() returns a StreamTextResult-like object (NOT directly iterable).
|
|
50
|
+
// usage and response are DelayedPromise wrappers matching real Mastra behaviour.
|
|
51
|
+
Agent.prototype.stream = function(_messages, _options) {
|
|
52
|
+
return {
|
|
53
|
+
textStream: (async function* () {
|
|
54
|
+
yield 'Hello'; yield ' Mastra'; yield ' stream.';
|
|
55
|
+
})(),
|
|
56
|
+
usage: { status: { type: 'resolved', value: { promptTokens: 10, completionTokens: 15, totalTokens: 25 } } },
|
|
57
|
+
response: { status: { type: 'resolved', value: { modelId: 'gpt-4o-mini', id: 'fake-stream-res' } } },
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
return { Agent };
|
|
61
|
+
}
|
|
62
|
+
if (request === '@ai-sdk/openai') {
|
|
63
|
+
return { openai: (modelId) => ({ _provider: 'openai', modelId }) };
|
|
64
|
+
}
|
|
29
65
|
if (request === 'weflayr') {
|
|
30
66
|
return _origLoad.call(this, path.resolve(__dirname, '../src/index.js'), parent, isMain);
|
|
31
67
|
}
|
|
@@ -580,3 +616,191 @@ test('snippet propagate_runtime: propagated tag in before and after', async () =
|
|
|
580
616
|
assert.equal(after.tags.customer_id, 'acme-corp');
|
|
581
617
|
});
|
|
582
618
|
|
|
619
|
+
// ---------------------------------------------------------------------------
|
|
620
|
+
// Mastra integration — documentation snippets
|
|
621
|
+
// @mastra/core and @ai-sdk/openai are faked by Module._load at top of file.
|
|
622
|
+
// ---------------------------------------------------------------------------
|
|
623
|
+
|
|
624
|
+
test('snippet mastra_setup: wraps Mastra Agent, sends before+after events on generate()', async () => {
|
|
625
|
+
freshSDK();
|
|
626
|
+
setSnippetEnv();
|
|
627
|
+
|
|
628
|
+
const sent = await captureHttp(async () => {
|
|
629
|
+
// SNIPPET_START: mastra_setup
|
|
630
|
+
const { weflayr_setup, weflayr_instrument } = require('weflayr');
|
|
631
|
+
const { Agent } = require('@mastra/core');
|
|
632
|
+
const { openai } = require('@ai-sdk/openai');
|
|
633
|
+
|
|
634
|
+
weflayr_setup({
|
|
635
|
+
intake_url: process.env.WEFLAYR_INTAKE_URL,
|
|
636
|
+
client_id: process.env.WEFLAYR_CLIENT_ID,
|
|
637
|
+
client_secret: process.env.WEFLAYR_CLIENT_SECRET,
|
|
638
|
+
methods: [{ call: 'generate' }],
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
const agent = weflayr_instrument(
|
|
642
|
+
new Agent({
|
|
643
|
+
name: 'my-agent',
|
|
644
|
+
model: openai('gpt-4o-mini'),
|
|
645
|
+
instructions: 'You are a helpful assistant.',
|
|
646
|
+
})
|
|
647
|
+
);
|
|
648
|
+
// SNIPPET_END: mastra_setup
|
|
649
|
+
|
|
650
|
+
await agent.generate([{ role: 'user', content: 'Hello' }]);
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
assert.ok(sent.some(e => e.event_type === 'before'), 'before event sent');
|
|
654
|
+
assert.ok(sent.some(e => e.event_type === 'after'), 'after event sent');
|
|
655
|
+
assert.equal(sent[0].method, 'generate');
|
|
656
|
+
});
|
|
657
|
+
|
|
658
|
+
test('snippet mastra_tags: per-call tags stripped from generate args, appear in events', async () => {
|
|
659
|
+
const { weflayr_setup, weflayr_instrument } = freshSDK();
|
|
660
|
+
setSnippetEnv();
|
|
661
|
+
|
|
662
|
+
const { Agent } = require('@mastra/core');
|
|
663
|
+
const { openai } = require('@ai-sdk/openai');
|
|
664
|
+
weflayr_setup({ ...TEST_CREDS, methods: [{ call: 'generate' }] });
|
|
665
|
+
const agent = weflayr_instrument(new Agent({ name: 'test-agent', model: openai('gpt-4o-mini'), instructions: '...' }));
|
|
666
|
+
|
|
667
|
+
const sent = await captureHttp(async () => {
|
|
668
|
+
// SNIPPET_START: mastra_tags
|
|
669
|
+
await agent.generate(
|
|
670
|
+
[{ role: 'user', content: 'Hello' }],
|
|
671
|
+
{ __weflayr_tags: { feature: 'chat', customer_id: '42', env: 'prod' } },
|
|
672
|
+
);
|
|
673
|
+
// SNIPPET_END: mastra_tags
|
|
674
|
+
});
|
|
675
|
+
|
|
676
|
+
const before = sent.find(e => e.event_type === 'before');
|
|
677
|
+
assert.ok(before, 'before event sent');
|
|
678
|
+
assert.deepEqual(before.tags, { feature: 'chat', customer_id: '42', env: 'prod' });
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
test('snippet mastra_content_privacy: text/request/steps/response content stripped from events', async () => {
|
|
682
|
+
freshSDK();
|
|
683
|
+
setSnippetEnv();
|
|
684
|
+
|
|
685
|
+
const sent = await captureHttp(async () => {
|
|
686
|
+
// SNIPPET_START: mastra_content_privacy
|
|
687
|
+
const { weflayr_setup, weflayr_instrument } = require('weflayr');
|
|
688
|
+
const { Agent } = require('@mastra/core');
|
|
689
|
+
const { openai } = require('@ai-sdk/openai');
|
|
690
|
+
|
|
691
|
+
weflayr_setup({
|
|
692
|
+
intake_url: process.env.WEFLAYR_INTAKE_URL,
|
|
693
|
+
client_id: process.env.WEFLAYR_CLIENT_ID,
|
|
694
|
+
client_secret: process.env.WEFLAYR_CLIENT_SECRET,
|
|
695
|
+
methods: [{ call: 'generate' }],
|
|
696
|
+
ignore_fields: (data) => {
|
|
697
|
+
delete data.text;
|
|
698
|
+
delete data.request;
|
|
699
|
+
delete data.steps;
|
|
700
|
+
if (data.response) {
|
|
701
|
+
delete data.response.body;
|
|
702
|
+
delete data.response.messages;
|
|
703
|
+
}
|
|
704
|
+
return data;
|
|
705
|
+
},
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
const agent = weflayr_instrument(
|
|
709
|
+
new Agent({
|
|
710
|
+
name: 'my-agent',
|
|
711
|
+
model: openai('gpt-4o-mini'),
|
|
712
|
+
instructions: 'You are a helpful assistant.',
|
|
713
|
+
})
|
|
714
|
+
);
|
|
715
|
+
// SNIPPET_END: mastra_content_privacy
|
|
716
|
+
|
|
717
|
+
await agent.generate([{ role: 'user', content: 'Hello' }]);
|
|
718
|
+
});
|
|
719
|
+
|
|
720
|
+
const after = sent.find(e => e.event_type === 'after');
|
|
721
|
+
assert.ok(after, 'after event sent');
|
|
722
|
+
assert.ok(!after.response || !('text' in after.response), 'text stripped');
|
|
723
|
+
assert.ok(!after.response || !('request' in after.response), 'request stripped');
|
|
724
|
+
assert.ok(!after.response || !('steps' in after.response), 'steps stripped');
|
|
725
|
+
assert.ok(!after.response?.response?.body, 'response.body stripped');
|
|
726
|
+
assert.ok(!after.response?.response?.messages, 'response.messages stripped');
|
|
727
|
+
});
|
|
728
|
+
|
|
729
|
+
test('snippet mastra_stream: stream_start + after emitted, model/usage in after via streamMiddleware', async () => {
|
|
730
|
+
freshSDK();
|
|
731
|
+
setSnippetEnv();
|
|
732
|
+
|
|
733
|
+
const sent = await captureHttp(async () => {
|
|
734
|
+
// SNIPPET_START: mastra_stream
|
|
735
|
+
const { weflayr_setup, weflayr_instrument } = require('weflayr');
|
|
736
|
+
const { Agent } = require('@mastra/core');
|
|
737
|
+
const { openai } = require('@ai-sdk/openai');
|
|
738
|
+
|
|
739
|
+
weflayr_setup({
|
|
740
|
+
intake_url: process.env.WEFLAYR_INTAKE_URL,
|
|
741
|
+
client_id: process.env.WEFLAYR_CLIENT_ID,
|
|
742
|
+
client_secret: process.env.WEFLAYR_CLIENT_SECRET,
|
|
743
|
+
methods: [
|
|
744
|
+
{ call: 'generate' },
|
|
745
|
+
{
|
|
746
|
+
call: 'stream',
|
|
747
|
+
streamPath: 'textStream',
|
|
748
|
+
streamMiddleware: (fullResult) => ({
|
|
749
|
+
onChunk: () => false,
|
|
750
|
+
async finalize() {
|
|
751
|
+
const rawUsage = await fullResult.usage;
|
|
752
|
+
const rawResponse = await fullResult.response;
|
|
753
|
+
const response = rawResponse?.status?.value ?? rawResponse;
|
|
754
|
+
return {
|
|
755
|
+
usage: rawUsage,
|
|
756
|
+
model: response?.modelId ?? null,
|
|
757
|
+
};
|
|
758
|
+
},
|
|
759
|
+
}),
|
|
760
|
+
},
|
|
761
|
+
],
|
|
762
|
+
ignore_fields: (data) => {
|
|
763
|
+
delete data.text;
|
|
764
|
+
delete data.request;
|
|
765
|
+
delete data.steps;
|
|
766
|
+
if (data.response) {
|
|
767
|
+
delete data.response.body;
|
|
768
|
+
delete data.response.messages;
|
|
769
|
+
}
|
|
770
|
+
return data;
|
|
771
|
+
},
|
|
772
|
+
});
|
|
773
|
+
|
|
774
|
+
const agent = weflayr_instrument(
|
|
775
|
+
new Agent({
|
|
776
|
+
name: 'my-agent',
|
|
777
|
+
model: openai('gpt-4o-mini'),
|
|
778
|
+
instructions: 'You are a helpful assistant.',
|
|
779
|
+
})
|
|
780
|
+
);
|
|
781
|
+
|
|
782
|
+
const streamResult = await agent.stream(
|
|
783
|
+
[{ role: 'user', content: 'Hello' }],
|
|
784
|
+
{ __weflayr_tags: { feature: 'chat', customer_id: '42' } },
|
|
785
|
+
);
|
|
786
|
+
|
|
787
|
+
let text = '';
|
|
788
|
+
for await (const chunk of streamResult.textStream) {
|
|
789
|
+
text += chunk;
|
|
790
|
+
}
|
|
791
|
+
// SNIPPET_END: mastra_stream
|
|
792
|
+
});
|
|
793
|
+
|
|
794
|
+
const types = sent.map(e => e.event_type);
|
|
795
|
+
assert.ok(types.includes('before'), 'before event sent');
|
|
796
|
+
assert.ok(types.includes('stream_start'), 'stream_start event sent');
|
|
797
|
+
assert.ok(types.includes('after'), 'after event sent');
|
|
798
|
+
|
|
799
|
+
const after = sent.find(e => e.event_type === 'after');
|
|
800
|
+
assert.equal(after.method, 'stream');
|
|
801
|
+
assert.equal(after.model, 'gpt-4o-mini');
|
|
802
|
+
assert.ok(after.usage, 'usage present in after event');
|
|
803
|
+
assert.equal(after.tags.feature, 'chat');
|
|
804
|
+
assert.equal(after.tags.customer_id, '42');
|
|
805
|
+
});
|
|
806
|
+
|