weflayr 0.6.2 → 0.7.1

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "weflayr",
3
- "version": "0.6.2",
3
+ "version": "0.7.1",
4
4
  "description": "Weflayr Node.js SDK — instrument any LLM client via JS Proxy",
5
5
  "main": "src/index.js",
6
6
  "types": "index.d.ts",
package/src/instrument.js CHANGED
@@ -3,6 +3,8 @@
3
3
  const https = require('https');
4
4
  const http = require('http');
5
5
  const { randomUUID } = require('crypto');
6
+ const { version: SDK_VERSION } = require('../package.json');
7
+ const SDK_LANGUAGE = 'javascript';
6
8
 
7
9
  let _config = null;
8
10
 
@@ -236,6 +238,8 @@ function _sendEvent(eventType, data) {
236
238
  body = JSON.stringify({
237
239
  event_type: eventType,
238
240
  timestamp: new Date().toISOString(),
241
+ sdk_version: SDK_VERSION,
242
+ sdk_language: SDK_LANGUAGE,
239
243
  ...data,
240
244
  });
241
245
  } catch (err) {
@@ -1,5 +1,38 @@
1
1
  'use strict';
2
2
 
3
+ // ---------------------------------------------------------------------------
4
+ // Module doubles — registered before any test code so snippet require() calls
5
+ // work without openai installed and without weflayr in node_modules.
6
+ // ---------------------------------------------------------------------------
7
+ {
8
+ const Module = require('module');
9
+ const path = require('path');
10
+ const _origLoad = Module._load;
11
+ Module._load = function(request, parent, isMain) {
12
+ if (request === 'openai') {
13
+ // The real openai package is required as `const OpenAI = require('openai')`,
14
+ // so the module export IS the constructor (not a namespace object).
15
+ function OpenAI(_opts) {
16
+ return {
17
+ chat: {
18
+ completions: {
19
+ create: async (args) => ({ id: 'mock-res', model: args.model || 'gpt-4o-mini', choices: [] }),
20
+ },
21
+ },
22
+ images: {
23
+ generate: async () => ({ data: [{ b64_json: 'abc123', url: 'https://example.com/img.png' }] }),
24
+ },
25
+ };
26
+ }
27
+ return OpenAI;
28
+ }
29
+ if (request === 'weflayr') {
30
+ return _origLoad.call(this, path.resolve(__dirname, '../src/index.js'), parent, isMain);
31
+ }
32
+ return _origLoad.call(this, request, parent, isMain);
33
+ };
34
+ }
35
+
3
36
  const { test } = require('node:test');
4
37
  const assert = require('assert/strict');
5
38
 
@@ -186,3 +219,211 @@ test('enabled:false — original function still executes correctly', async () =>
186
219
  assert.equal(result.id, 'res-1');
187
220
  });
188
221
 
222
+ // ---------------------------------------------------------------------------
223
+ // Documentation snippets
224
+ // All snippet code runs verbatim — Module._load at top of file provides
225
+ // require('openai') and require('weflayr') doubles.
226
+ // update_doc_snippets.py extracts between: // SNIPPET_START: <name> ... // SNIPPET_END: <name>
227
+ // ---------------------------------------------------------------------------
228
+
229
+ // Intercept http.request; returns array of parsed event JSON bodies.
230
+ async function captureHttp(fn) {
231
+ const http = require('http');
232
+ const sent = [];
233
+ const origRequest = http.request;
234
+ http.request = function(_opts, responseCb) {
235
+ const chunks = [];
236
+ const req = {
237
+ write: (d) => chunks.push(typeof d === 'string' ? d : d.toString()),
238
+ end: () => {
239
+ try { sent.push(JSON.parse(chunks.join(''))); } catch { /* skip malformed */ }
240
+ if (responseCb) responseCb({ resume: () => {}, on: () => {}, statusCode: 200 });
241
+ },
242
+ on: () => req,
243
+ };
244
+ return req;
245
+ };
246
+ try { await fn(); } finally { http.request = origRequest; }
247
+ return sent;
248
+ }
249
+
250
+ const SNIPPET_ENV = {
251
+ WEFLAYR_INTAKE_URL: TEST_CREDS.intake_url,
252
+ WEFLAYR_CLIENT_ID: TEST_CREDS.client_id,
253
+ WEFLAYR_CLIENT_SECRET: TEST_CREDS.client_secret,
254
+ OPENAI_API_KEY: 'sk-test',
255
+ };
256
+ function setSnippetEnv() { Object.assign(process.env, SNIPPET_ENV); }
257
+
258
+ test('snippet quickstart_setup: wraps client, sends before+after events', async () => {
259
+ freshSDK();
260
+ setSnippetEnv();
261
+
262
+ const sent = await captureHttp(async () => {
263
+ // SNIPPET_START: quickstart_setup
264
+ const OpenAI = require('openai');
265
+ const { weflayr_setup, weflayr_instrument } = require('weflayr');
266
+
267
+ weflayr_setup({
268
+ intake_url: process.env.WEFLAYR_INTAKE_URL,
269
+ client_id: process.env.WEFLAYR_CLIENT_ID,
270
+ client_secret: process.env.WEFLAYR_CLIENT_SECRET,
271
+ methods: [
272
+ { call: 'chat.completions.create' },
273
+ ],
274
+ });
275
+
276
+ const client = weflayr_instrument(new OpenAI({ apiKey: process.env.OPENAI_API_KEY }));
277
+ // SNIPPET_END: quickstart_setup
278
+
279
+ assert.equal(typeof client.chat.completions.create, 'function');
280
+ await client.chat.completions.create({ model: 'gpt-4o-mini', messages: [] });
281
+ });
282
+
283
+ assert.ok(sent.some(e => e.event_type === 'before'), 'before event sent');
284
+ assert.ok(sent.some(e => e.event_type === 'after'), 'after event sent');
285
+ assert.equal(sent[0].method, 'chat.completions.create');
286
+ });
287
+
288
+ test('snippet quickstart_call: tags stripped from call, tags in telemetry', async () => {
289
+ const { weflayr_setup, weflayr_instrument } = freshSDK();
290
+ setSnippetEnv();
291
+
292
+ let capturedArgs;
293
+ const raw = {
294
+ chat: { completions: { create: async (args) => { capturedArgs = args; return { id: 'res-1', choices: [] }; } } },
295
+ };
296
+ weflayr_setup({ ...TEST_CREDS, methods: [{ call: 'chat.completions.create' }] });
297
+ const client = weflayr_instrument(raw);
298
+
299
+ const sent = await captureHttp(async () => {
300
+ // SNIPPET_START: quickstart_call
301
+ const response = await client.chat.completions.create({
302
+ model: 'gpt-4o-mini',
303
+ messages: [{ role: 'user', content: 'Hello!' }],
304
+ __weflayr_tags: {
305
+ provider: 'openai',
306
+ feature: 'onboarding',
307
+ customer_id: '#3756'
308
+ },
309
+ });
310
+ // response is the standard OpenAI response object — unchanged
311
+ // SNIPPET_END: quickstart_call
312
+
313
+ assert.equal(response.id, 'res-1');
314
+ });
315
+
316
+ assert.ok(!('__weflayr_tags' in capturedArgs), 'tags stripped from underlying call');
317
+ const before = sent.find(e => e.event_type === 'before');
318
+ assert.ok(before, 'before event sent');
319
+ assert.deepEqual(before.tags, { provider: 'openai', feature: 'onboarding', customer_id: '#3756' });
320
+ });
321
+
322
+ test('snippet openai_full_coverage: 7 methods configured, events sent', async () => {
323
+ freshSDK();
324
+ setSnippetEnv();
325
+
326
+ const sent = await captureHttp(async () => {
327
+ // SNIPPET_START: openai_full_coverage
328
+ const { weflayr_setup, weflayr_instrument } = require('weflayr');
329
+
330
+ weflayr_setup({
331
+ intake_url: process.env.WEFLAYR_INTAKE_URL,
332
+ client_id: process.env.WEFLAYR_CLIENT_ID,
333
+ client_secret: process.env.WEFLAYR_CLIENT_SECRET,
334
+ methods: [
335
+ { call: 'chat.completions.create' },
336
+ { call: 'completions.create' },
337
+ { call: 'embeddings.create' },
338
+ { call: 'responses.create' },
339
+ {
340
+ call: 'audio.speech.create',
341
+ middleware: (args) => ({ char_count: args?.input?.length ?? 0 }),
342
+ },
343
+ { call: 'audio.transcriptions.create' },
344
+ { call: 'audio.translations.create' },
345
+ ],
346
+ });
347
+
348
+ const OpenAI = require('openai');
349
+ const client = weflayr_instrument(new OpenAI({ apiKey: process.env.OPENAI_API_KEY }));
350
+ // SNIPPET_END: openai_full_coverage
351
+
352
+ assert.equal(typeof client.chat.completions.create, 'function');
353
+ await client.chat.completions.create({ model: 'gpt-4o-mini', messages: [] });
354
+ });
355
+
356
+ assert.ok(sent.some(e => e.event_type === 'before'), 'before event sent');
357
+ assert.ok(sent.some(e => e.event_type === 'after'), 'after event sent');
358
+ assert.equal(sent[0].method, 'chat.completions.create');
359
+ });
360
+
361
+ test('snippet openai_tagging: tags stripped from call, all 4 tags in telemetry', async () => {
362
+ const { weflayr_setup, weflayr_instrument } = freshSDK();
363
+ setSnippetEnv();
364
+
365
+ let capturedArgs;
366
+ const raw = {
367
+ chat: { completions: { create: async (args) => { capturedArgs = args; return { id: 'res-1' }; } } },
368
+ };
369
+ weflayr_setup({ ...TEST_CREDS, methods: [{ call: 'chat.completions.create' }] });
370
+ const client = weflayr_instrument(raw);
371
+
372
+ const sent = await captureHttp(async () => {
373
+ // SNIPPET_START: openai_tagging
374
+ await client.chat.completions.create({
375
+ model: 'gpt-4o-mini',
376
+ messages: [{ role: 'user', content: 'Hello' }],
377
+ __weflayr_tags: {
378
+ provider: 'openai',
379
+ feature: 'support-chat',
380
+ customer_id: 'acme-corp',
381
+ env: 'production',
382
+ },
383
+ });
384
+ // SNIPPET_END: openai_tagging
385
+ });
386
+
387
+ assert.ok(!('__weflayr_tags' in capturedArgs), 'tags stripped from underlying call');
388
+ assert.equal(capturedArgs.model, 'gpt-4o-mini');
389
+ const before = sent.find(e => e.event_type === 'before');
390
+ assert.ok(before, 'before event sent');
391
+ assert.deepEqual(before.tags, { provider: 'openai', feature: 'support-chat', customer_id: 'acme-corp', env: 'production' });
392
+ });
393
+
394
+ test('snippet openai_image_generation: b64_json stripped from telemetry, url preserved', async () => {
395
+ freshSDK();
396
+ setSnippetEnv();
397
+
398
+ const sent = await captureHttp(async () => {
399
+ // SNIPPET_START: openai_image_generation
400
+ const { weflayr_setup, weflayr_instrument } = require('weflayr');
401
+
402
+ weflayr_setup({
403
+ intake_url: process.env.WEFLAYR_INTAKE_URL,
404
+ client_id: process.env.WEFLAYR_CLIENT_ID,
405
+ client_secret: process.env.WEFLAYR_CLIENT_SECRET,
406
+ ignore_fields: (data) => {
407
+ (data.data ?? []).forEach(img => delete img.b64_json);
408
+ return data;
409
+ },
410
+ methods: [
411
+ { call: 'images.generate' },
412
+ ],
413
+ });
414
+
415
+ const OpenAI = require('openai');
416
+ const client = weflayr_instrument(new OpenAI({ apiKey: process.env.OPENAI_API_KEY }));
417
+ // SNIPPET_END: openai_image_generation
418
+
419
+ await client.images.generate({ model: 'gpt-image-1', prompt: 'a cat' });
420
+ });
421
+
422
+ const after = sent.find(e => e.event_type === 'after');
423
+ assert.ok(after, 'after event sent');
424
+ for (const img of (after.response?.data ?? [])) {
425
+ assert.ok(!('b64_json' in img), 'b64_json stripped from telemetry');
426
+ }
427
+ assert.ok(after.response.data[0].url, 'url preserved in telemetry');
428
+ });
429
+