weflayr 0.8.1 → 0.9.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/index.d.ts +13 -0
- package/package.json +1 -1
- package/src/index.js +2 -2
- package/src/instrument.js +14 -3
- package/tests/index.test.js +153 -0
package/index.d.ts
CHANGED
|
@@ -66,6 +66,11 @@ export interface WeflayrSettings {
|
|
|
66
66
|
* Mutually exclusive with `ignore_fields` — setting both blocks all events.
|
|
67
67
|
*/
|
|
68
68
|
allow_fields?: ContentPolicyFn;
|
|
69
|
+
/**
|
|
70
|
+
* Static tags attached to every instrumented call. Can be overridden per-call
|
|
71
|
+
* via `__weflayr_tags` or at runtime via `weflayr_propagate`.
|
|
72
|
+
*/
|
|
73
|
+
default_tags?: Tags;
|
|
69
74
|
/** Methods to instrument on the proxied object. */
|
|
70
75
|
methods?: MethodConfig[];
|
|
71
76
|
}
|
|
@@ -80,6 +85,14 @@ export function weflayr_setup(settings: WeflayrSettings): void;
|
|
|
80
85
|
*/
|
|
81
86
|
export function weflayr_instrument<T>(target: T): T;
|
|
82
87
|
|
|
88
|
+
/**
|
|
89
|
+
* Adds or overrides a single key in the runtime default tags.
|
|
90
|
+
* Equivalent to adding the key to `default_tags` in `weflayr_setup`, but callable at any
|
|
91
|
+
* point during execution (e.g. inside a request handler to propagate a customer ID).
|
|
92
|
+
* Propagated tags are overridden by per-call `__weflayr_tags`.
|
|
93
|
+
*/
|
|
94
|
+
export function weflayr_propagate(key: string, value: string | number | boolean): void;
|
|
95
|
+
|
|
83
96
|
/** Sends a custom event to the intake API from user code. No-ops if `weflayr_setup` hasn't been called. */
|
|
84
97
|
export function send_event(
|
|
85
98
|
eventType: string,
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { configure, weflayr_instrument, send_event } = require('./instrument');
|
|
3
|
+
const { configure, weflayr_instrument, weflayr_propagate, send_event } = require('./instrument');
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
*
|
|
@@ -32,4 +32,4 @@ function flayred(options) {
|
|
|
32
32
|
return options;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
module.exports = { weflayr_setup, weflayr_instrument, send_event, flayred };
|
|
35
|
+
module.exports = { weflayr_setup, weflayr_instrument, weflayr_propagate, send_event, flayred };
|
package/src/instrument.js
CHANGED
|
@@ -7,6 +7,7 @@ const { version: SDK_VERSION } = require('../package.json');
|
|
|
7
7
|
const SDK_LANGUAGE = 'javascript';
|
|
8
8
|
|
|
9
9
|
let _config = null;
|
|
10
|
+
let _propagated = {};
|
|
10
11
|
|
|
11
12
|
function configure(intakeUrl, clientId, clientSecret, settings) {
|
|
12
13
|
if (settings.ignore_fields && settings.allow_fields) {
|
|
@@ -173,12 +174,12 @@ async function* _wrapStream(stream, methodName, tags, requestArgs, settings, sta
|
|
|
173
174
|
}
|
|
174
175
|
|
|
175
176
|
function _extractTags(args) {
|
|
176
|
-
let
|
|
177
|
+
let callTags = {};
|
|
177
178
|
|
|
178
179
|
function strip(obj) {
|
|
179
180
|
if (!obj || typeof obj !== 'object' || Array.isArray(obj)) return;
|
|
180
181
|
if ('__weflayr_tags' in obj) {
|
|
181
|
-
|
|
182
|
+
callTags = obj.__weflayr_tags || {};
|
|
182
183
|
delete obj.__weflayr_tags;
|
|
183
184
|
}
|
|
184
185
|
for (const key of Object.keys(obj)) {
|
|
@@ -191,9 +192,19 @@ function _extractTags(args) {
|
|
|
191
192
|
return arg;
|
|
192
193
|
});
|
|
193
194
|
|
|
195
|
+
const tags = {
|
|
196
|
+
...(_config.settings.default_tags || {}),
|
|
197
|
+
..._propagated,
|
|
198
|
+
...callTags,
|
|
199
|
+
};
|
|
200
|
+
|
|
194
201
|
return { tags, cleanArgs };
|
|
195
202
|
}
|
|
196
203
|
|
|
204
|
+
function weflayr_propagate(key, value) {
|
|
205
|
+
_propagated[key] = value;
|
|
206
|
+
}
|
|
207
|
+
|
|
197
208
|
function _isAsyncIterable(value) {
|
|
198
209
|
return value != null && typeof value[Symbol.asyncIterator] === 'function';
|
|
199
210
|
}
|
|
@@ -297,4 +308,4 @@ function send_event(eventType, data) {
|
|
|
297
308
|
return _sendEvent(eventType, data);
|
|
298
309
|
}
|
|
299
310
|
|
|
300
|
-
module.exports = { configure, weflayr_instrument, send_event };
|
|
311
|
+
module.exports = { configure, weflayr_instrument, weflayr_propagate, send_event };
|
package/tests/index.test.js
CHANGED
|
@@ -268,6 +268,10 @@ test('snippet quickstart_setup: wraps client, sends before+after events', async
|
|
|
268
268
|
intake_url: process.env.WEFLAYR_INTAKE_URL,
|
|
269
269
|
client_id: process.env.WEFLAYR_CLIENT_ID,
|
|
270
270
|
client_secret: process.env.WEFLAYR_CLIENT_SECRET,
|
|
271
|
+
default_tags: {
|
|
272
|
+
app: 'my-app',
|
|
273
|
+
version: '1.0.0',
|
|
274
|
+
},
|
|
271
275
|
methods: [
|
|
272
276
|
{ call: 'chat.completions.create' },
|
|
273
277
|
],
|
|
@@ -283,6 +287,8 @@ test('snippet quickstart_setup: wraps client, sends before+after events', async
|
|
|
283
287
|
assert.ok(sent.some(e => e.event_type === 'before'), 'before event sent');
|
|
284
288
|
assert.ok(sent.some(e => e.event_type === 'after'), 'after event sent');
|
|
285
289
|
assert.equal(sent[0].method, 'chat.completions.create');
|
|
290
|
+
assert.equal(sent[0].tags.app, 'my-app');
|
|
291
|
+
assert.equal(sent[0].tags.version, '1.0.0');
|
|
286
292
|
});
|
|
287
293
|
|
|
288
294
|
test('snippet quickstart_call: tags stripped from call, tags in telemetry', async () => {
|
|
@@ -427,3 +433,150 @@ test('snippet openai_image_generation: b64_json stripped from telemetry, url pre
|
|
|
427
433
|
assert.ok(after.response.data[0].url, 'url preserved in telemetry');
|
|
428
434
|
});
|
|
429
435
|
|
|
436
|
+
// ---------------------------------------------------------------------------
|
|
437
|
+
// default_tags and weflayr_propagate — behavioural tests
|
|
438
|
+
// ---------------------------------------------------------------------------
|
|
439
|
+
|
|
440
|
+
test('default_tags appear in before and after events', async () => {
|
|
441
|
+
const { weflayr_setup, weflayr_instrument } = freshSDK();
|
|
442
|
+
|
|
443
|
+
weflayr_setup({
|
|
444
|
+
...TEST_CREDS,
|
|
445
|
+
default_tags: { app: 'my-app', version: '1.2.0' },
|
|
446
|
+
methods: [{ call: 'chat.completions.create' }],
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
const client = weflayr_instrument(fakeClient());
|
|
450
|
+
const sent = await captureHttp(async () => {
|
|
451
|
+
await client.chat.completions.create({ model: 'gpt-4o-mini', messages: [] });
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
const before = sent.find(e => e.event_type === 'before');
|
|
455
|
+
assert.equal(before.tags.app, 'my-app');
|
|
456
|
+
assert.equal(before.tags.version, '1.2.0');
|
|
457
|
+
const after = sent.find(e => e.event_type === 'after');
|
|
458
|
+
assert.equal(after.tags.app, 'my-app');
|
|
459
|
+
assert.equal(after.tags.version, '1.2.0');
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
test('weflayr_propagate adds a tag to before and after events', async () => {
|
|
463
|
+
const { weflayr_setup, weflayr_instrument, weflayr_propagate } = freshSDK();
|
|
464
|
+
|
|
465
|
+
weflayr_setup({
|
|
466
|
+
...TEST_CREDS,
|
|
467
|
+
methods: [{ call: 'chat.completions.create' }],
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
weflayr_propagate('customer_id', 'acme-corp');
|
|
471
|
+
|
|
472
|
+
const client = weflayr_instrument(fakeClient());
|
|
473
|
+
const sent = await captureHttp(async () => {
|
|
474
|
+
await client.chat.completions.create({ model: 'gpt-4o-mini', messages: [] });
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
const before = sent.find(e => e.event_type === 'before');
|
|
478
|
+
assert.equal(before.tags.customer_id, 'acme-corp');
|
|
479
|
+
const after = sent.find(e => e.event_type === 'after');
|
|
480
|
+
assert.equal(after.tags.customer_id, 'acme-corp');
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
test('tag priority: __weflayr_tags > weflayr_propagate > default_tags', async () => {
|
|
484
|
+
const { weflayr_setup, weflayr_instrument, weflayr_propagate } = freshSDK();
|
|
485
|
+
|
|
486
|
+
weflayr_setup({
|
|
487
|
+
...TEST_CREDS,
|
|
488
|
+
default_tags: { env: 'production', app: 'my-app' },
|
|
489
|
+
methods: [{ call: 'chat.completions.create' }],
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
weflayr_propagate('env', 'staging'); // overrides default_tags.env
|
|
493
|
+
|
|
494
|
+
const client = weflayr_instrument(fakeClient());
|
|
495
|
+
const sent = await captureHttp(async () => {
|
|
496
|
+
await client.chat.completions.create({
|
|
497
|
+
model: 'gpt-4o-mini',
|
|
498
|
+
messages: [],
|
|
499
|
+
__weflayr_tags: { env: 'test' }, // overrides both
|
|
500
|
+
});
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
const before = sent.find(e => e.event_type === 'before');
|
|
504
|
+
assert.equal(before.tags.env, 'test'); // per-call wins
|
|
505
|
+
assert.equal(before.tags.app, 'my-app'); // from default_tags, untouched
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
// ---------------------------------------------------------------------------
|
|
509
|
+
// Propagate — documentation snippets
|
|
510
|
+
// ---------------------------------------------------------------------------
|
|
511
|
+
|
|
512
|
+
test('snippet propagate_default_tags: default_tags in every event', async () => {
|
|
513
|
+
freshSDK();
|
|
514
|
+
setSnippetEnv();
|
|
515
|
+
|
|
516
|
+
const sent = await captureHttp(async () => {
|
|
517
|
+
// SNIPPET_START: propagate_default_tags
|
|
518
|
+
const { weflayr_setup, weflayr_instrument } = require('weflayr');
|
|
519
|
+
|
|
520
|
+
weflayr_setup({
|
|
521
|
+
intake_url: process.env.WEFLAYR_INTAKE_URL,
|
|
522
|
+
client_id: process.env.WEFLAYR_CLIENT_ID,
|
|
523
|
+
client_secret: process.env.WEFLAYR_CLIENT_SECRET,
|
|
524
|
+
default_tags: {
|
|
525
|
+
app: 'my-app',
|
|
526
|
+
version: '1.2.0',
|
|
527
|
+
env: 'production',
|
|
528
|
+
},
|
|
529
|
+
methods: [{ call: 'chat.completions.create' }],
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
const OpenAI = require('openai');
|
|
533
|
+
const client = weflayr_instrument(new OpenAI({ apiKey: process.env.OPENAI_API_KEY }));
|
|
534
|
+
// SNIPPET_END: propagate_default_tags
|
|
535
|
+
|
|
536
|
+
await client.chat.completions.create({ model: 'gpt-4o-mini', messages: [] });
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
const before = sent.find(e => e.event_type === 'before');
|
|
540
|
+
assert.ok(before, 'before event sent');
|
|
541
|
+
assert.equal(before.tags.app, 'my-app');
|
|
542
|
+
assert.equal(before.tags.version, '1.2.0');
|
|
543
|
+
assert.equal(before.tags.env, 'production');
|
|
544
|
+
const after = sent.find(e => e.event_type === 'after');
|
|
545
|
+
assert.ok(after, 'after event sent');
|
|
546
|
+
assert.equal(after.tags.app, 'my-app');
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
test('snippet propagate_runtime: propagated tag in before and after', async () => {
|
|
550
|
+
const { weflayr_setup, weflayr_instrument, weflayr_propagate } = freshSDK();
|
|
551
|
+
setSnippetEnv();
|
|
552
|
+
|
|
553
|
+
let capturedArgs;
|
|
554
|
+
const raw = {
|
|
555
|
+
chat: { completions: { create: async (args) => { capturedArgs = args; return { id: 'res-1' }; } } },
|
|
556
|
+
};
|
|
557
|
+
weflayr_setup({ ...TEST_CREDS, methods: [{ call: 'chat.completions.create' }] });
|
|
558
|
+
const client = weflayr_instrument(raw);
|
|
559
|
+
|
|
560
|
+
const sent = await captureHttp(async () => {
|
|
561
|
+
// SNIPPET_START: propagate_runtime
|
|
562
|
+
const { weflayr_propagate } = require('weflayr');
|
|
563
|
+
|
|
564
|
+
// In a request handler — tags every LLM call made during this execution
|
|
565
|
+
weflayr_propagate('customer_id', 'acme-corp');
|
|
566
|
+
|
|
567
|
+
await client.chat.completions.create({
|
|
568
|
+
model: 'gpt-4o-mini',
|
|
569
|
+
messages: [{ role: 'user', content: 'Hello!' }],
|
|
570
|
+
});
|
|
571
|
+
// SNIPPET_END: propagate_runtime
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
assert.ok(!('__weflayr_tags' in capturedArgs), 'no tags leaked to provider');
|
|
575
|
+
const before = sent.find(e => e.event_type === 'before');
|
|
576
|
+
assert.ok(before, 'before event sent');
|
|
577
|
+
assert.equal(before.tags.customer_id, 'acme-corp');
|
|
578
|
+
const after = sent.find(e => e.event_type === 'after');
|
|
579
|
+
assert.ok(after, 'after event sent');
|
|
580
|
+
assert.equal(after.tags.customer_id, 'acme-corp');
|
|
581
|
+
});
|
|
582
|
+
|