@visibe.ai/node 0.1.22 → 0.1.23

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/dist/cjs/index.js CHANGED
@@ -100,6 +100,8 @@ async function patchFramework(framework, client) {
100
100
  try {
101
101
  switch (framework) {
102
102
  case 'openai': {
103
+ if (_originalOpenAI)
104
+ break; // already patched by _syncPatch()
103
105
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
104
106
  const openaiModule = await Promise.resolve().then(() => __importStar(require('openai')));
105
107
  _originalOpenAI = openaiModule.OpenAI ?? openaiModule.default;
@@ -128,6 +130,8 @@ async function patchFramework(framework, client) {
128
130
  break;
129
131
  }
130
132
  case 'anthropic': {
133
+ if (_originalAnthropic)
134
+ break; // already patched by _syncPatch()
131
135
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
132
136
  const anthropicModule = await Promise.resolve().then(() => __importStar(require('@anthropic-ai/sdk')));
133
137
  _originalAnthropic = anthropicModule.Anthropic ?? anthropicModule.default;
@@ -154,6 +158,8 @@ async function patchFramework(framework, client) {
154
158
  break;
155
159
  }
156
160
  case 'bedrock': {
161
+ if (_originalBedrockClient)
162
+ break; // already patched by _syncPatch()
157
163
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
158
164
  const bedrockModule = await Promise.resolve().then(() => __importStar(require('@aws-sdk/client-bedrock-runtime')));
159
165
  _originalBedrockClient = bedrockModule.BedrockRuntimeClient;
@@ -238,6 +244,98 @@ async function _autoPatch(client, frameworks) {
238
244
  }
239
245
  }
240
246
  // ---------------------------------------------------------------------------
247
+ // _syncPatch() — synchronous CJS patching.
248
+ //
249
+ // In CJS builds, require() is synchronous so we can patch framework modules
250
+ // inline inside init() BEFORE it returns. This means instances created right
251
+ // after init() (e.g. `const client = new OpenAI()`) are already using the
252
+ // patched class — no async race condition.
253
+ //
254
+ // In ESM builds, require is undefined — the entire function silently no-ops
255
+ // and _autoPatch() handles instrumentation via dynamic import() instead.
256
+ // ---------------------------------------------------------------------------
257
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
258
+ function _makePatchedClass(Orig, client) {
259
+ return class Patched extends Orig {
260
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
261
+ constructor(...args) {
262
+ super(...args);
263
+ try {
264
+ client.instrument(this);
265
+ }
266
+ catch { /* never crash constructor */ }
267
+ }
268
+ };
269
+ }
270
+ function _syncPatch(client, frameworksToTry) {
271
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
272
+ if (typeof require === 'undefined')
273
+ return;
274
+ for (const fw of frameworksToTry) {
275
+ try {
276
+ switch (fw) {
277
+ case 'openai': {
278
+ if (_originalOpenAI)
279
+ break;
280
+ // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-explicit-any
281
+ const m = require('openai');
282
+ const Orig = m.OpenAI ?? m.default;
283
+ if (!Orig)
284
+ break;
285
+ _originalOpenAI = Orig;
286
+ const Patched = _makePatchedClass(Orig, client);
287
+ try {
288
+ _setProp(m, 'OpenAI', Patched);
289
+ _setProp(m, 'default', Patched);
290
+ }
291
+ catch {
292
+ _originalOpenAI = null;
293
+ }
294
+ break;
295
+ }
296
+ case 'anthropic': {
297
+ if (_originalAnthropic)
298
+ break;
299
+ // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-explicit-any
300
+ const m = require('@anthropic-ai/sdk');
301
+ const Orig = m.Anthropic ?? m.default;
302
+ if (!Orig)
303
+ break;
304
+ _originalAnthropic = Orig;
305
+ const Patched = _makePatchedClass(Orig, client);
306
+ try {
307
+ _setProp(m, 'Anthropic', Patched);
308
+ _setProp(m, 'default', Patched);
309
+ }
310
+ catch {
311
+ _originalAnthropic = null;
312
+ }
313
+ break;
314
+ }
315
+ case 'bedrock': {
316
+ if (_originalBedrockClient)
317
+ break;
318
+ // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-explicit-any
319
+ const m = require('@aws-sdk/client-bedrock-runtime');
320
+ const Orig = m.BedrockRuntimeClient;
321
+ if (!Orig)
322
+ break;
323
+ _originalBedrockClient = Orig;
324
+ const Patched = _makePatchedClass(Orig, client);
325
+ try {
326
+ m.BedrockRuntimeClient = Patched;
327
+ }
328
+ catch {
329
+ _originalBedrockClient = null;
330
+ }
331
+ break;
332
+ }
333
+ }
334
+ }
335
+ catch { /* package not installed — skip */ }
336
+ }
337
+ }
338
+ // ---------------------------------------------------------------------------
241
339
  // init()
242
340
  // ---------------------------------------------------------------------------
243
341
  function init(options) {
@@ -246,10 +344,13 @@ function init(options) {
246
344
  return _globalClient;
247
345
  }
248
346
  _globalClient = new client_1.Visibe(options ?? {});
249
- // Fire async patching — works in both CJS and ESM via dynamic import().
250
- // Patching typically completes within microseconds (cached modules) so there
251
- // is no practical race condition for normal usage patterns.
252
347
  const frameworksToTry = options?.frameworks ?? ALL_FRAMEWORKS;
348
+ // Synchronous CJS path — patches module exports inline so instances created
349
+ // right after init() (e.g. `const client = new OpenAI()`) are instrumented.
350
+ // In ESM, require is undefined and this silently no-ops.
351
+ _syncPatch(_globalClient, frameworksToTry);
352
+ // Async path — handles ESM and framework detection for LangChain/LangGraph
353
+ // (prototype patches don't need to be synchronous).
253
354
  _autoPatch(_globalClient, frameworksToTry).catch(() => { });
254
355
  if (!_shutdownRegistered) {
255
356
  const graceful = async () => { await shutdown(); process.exit(0); };
@@ -152,8 +152,9 @@ class OpenAIIntegration extends base_1.BaseIntegration {
152
152
  throw err;
153
153
  }
154
154
  const model = response.model ?? params.model ?? 'unknown';
155
- const inputTokens = response.usage?.prompt_tokens ?? 0;
156
- const outputTokens = response.usage?.completion_tokens ?? 0;
155
+ // Support both openai v4 (prompt_tokens/completion_tokens) and v5/v6 (input_tokens/output_tokens)
156
+ const inputTokens = response.usage?.prompt_tokens ?? response.usage?.input_tokens ?? 0;
157
+ const outputTokens = response.usage?.completion_tokens ?? response.usage?.output_tokens ?? 0;
157
158
  const cost = (0, utils_1.calculateCost)(model, inputTokens, outputTokens);
158
159
  const choice = response.choices?.[0];
159
160
  const rawContent = choice?.message?.content;
@@ -244,9 +245,10 @@ class OpenAIIntegration extends base_1.BaseIntegration {
244
245
  if (delta)
245
246
  outputText += delta;
246
247
  // Last chunk carries usage when stream_options.include_usage is set
248
+ // Support both openai v4 (prompt_tokens/completion_tokens) and v5/v6 (input_tokens/output_tokens)
247
249
  if (chunk.usage) {
248
- inputTokens = chunk.usage.prompt_tokens ?? 0;
249
- outputTokens = chunk.usage.completion_tokens ?? 0;
250
+ inputTokens = chunk.usage.prompt_tokens ?? chunk.usage.input_tokens ?? 0;
251
+ outputTokens = chunk.usage.completion_tokens ?? chunk.usage.output_tokens ?? 0;
250
252
  }
251
253
  }
252
254
  return result;
package/dist/esm/index.js CHANGED
@@ -61,6 +61,8 @@ async function patchFramework(framework, client) {
61
61
  try {
62
62
  switch (framework) {
63
63
  case 'openai': {
64
+ if (_originalOpenAI)
65
+ break; // already patched by _syncPatch()
64
66
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
65
67
  const openaiModule = await import('openai');
66
68
  _originalOpenAI = openaiModule.OpenAI ?? openaiModule.default;
@@ -89,6 +91,8 @@ async function patchFramework(framework, client) {
89
91
  break;
90
92
  }
91
93
  case 'anthropic': {
94
+ if (_originalAnthropic)
95
+ break; // already patched by _syncPatch()
92
96
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
93
97
  const anthropicModule = await import('@anthropic-ai/sdk');
94
98
  _originalAnthropic = anthropicModule.Anthropic ?? anthropicModule.default;
@@ -115,6 +119,8 @@ async function patchFramework(framework, client) {
115
119
  break;
116
120
  }
117
121
  case 'bedrock': {
122
+ if (_originalBedrockClient)
123
+ break; // already patched by _syncPatch()
118
124
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
119
125
  const bedrockModule = await import('@aws-sdk/client-bedrock-runtime');
120
126
  _originalBedrockClient = bedrockModule.BedrockRuntimeClient;
@@ -199,6 +205,98 @@ async function _autoPatch(client, frameworks) {
199
205
  }
200
206
  }
201
207
  // ---------------------------------------------------------------------------
208
+ // _syncPatch() — synchronous CJS patching.
209
+ //
210
+ // In CJS builds, require() is synchronous so we can patch framework modules
211
+ // inline inside init() BEFORE it returns. This means instances created right
212
+ // after init() (e.g. `const client = new OpenAI()`) are already using the
213
+ // patched class — no async race condition.
214
+ //
215
+ // In ESM builds, require is undefined — the entire function silently no-ops
216
+ // and _autoPatch() handles instrumentation via dynamic import() instead.
217
+ // ---------------------------------------------------------------------------
218
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
219
+ function _makePatchedClass(Orig, client) {
220
+ return class Patched extends Orig {
221
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
222
+ constructor(...args) {
223
+ super(...args);
224
+ try {
225
+ client.instrument(this);
226
+ }
227
+ catch { /* never crash constructor */ }
228
+ }
229
+ };
230
+ }
231
+ function _syncPatch(client, frameworksToTry) {
232
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
233
+ if (typeof require === 'undefined')
234
+ return;
235
+ for (const fw of frameworksToTry) {
236
+ try {
237
+ switch (fw) {
238
+ case 'openai': {
239
+ if (_originalOpenAI)
240
+ break;
241
+ // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-explicit-any
242
+ const m = require('openai');
243
+ const Orig = m.OpenAI ?? m.default;
244
+ if (!Orig)
245
+ break;
246
+ _originalOpenAI = Orig;
247
+ const Patched = _makePatchedClass(Orig, client);
248
+ try {
249
+ _setProp(m, 'OpenAI', Patched);
250
+ _setProp(m, 'default', Patched);
251
+ }
252
+ catch {
253
+ _originalOpenAI = null;
254
+ }
255
+ break;
256
+ }
257
+ case 'anthropic': {
258
+ if (_originalAnthropic)
259
+ break;
260
+ // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-explicit-any
261
+ const m = require('@anthropic-ai/sdk');
262
+ const Orig = m.Anthropic ?? m.default;
263
+ if (!Orig)
264
+ break;
265
+ _originalAnthropic = Orig;
266
+ const Patched = _makePatchedClass(Orig, client);
267
+ try {
268
+ _setProp(m, 'Anthropic', Patched);
269
+ _setProp(m, 'default', Patched);
270
+ }
271
+ catch {
272
+ _originalAnthropic = null;
273
+ }
274
+ break;
275
+ }
276
+ case 'bedrock': {
277
+ if (_originalBedrockClient)
278
+ break;
279
+ // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-explicit-any
280
+ const m = require('@aws-sdk/client-bedrock-runtime');
281
+ const Orig = m.BedrockRuntimeClient;
282
+ if (!Orig)
283
+ break;
284
+ _originalBedrockClient = Orig;
285
+ const Patched = _makePatchedClass(Orig, client);
286
+ try {
287
+ m.BedrockRuntimeClient = Patched;
288
+ }
289
+ catch {
290
+ _originalBedrockClient = null;
291
+ }
292
+ break;
293
+ }
294
+ }
295
+ }
296
+ catch { /* package not installed — skip */ }
297
+ }
298
+ }
299
+ // ---------------------------------------------------------------------------
202
300
  // init()
203
301
  // ---------------------------------------------------------------------------
204
302
  export function init(options) {
@@ -207,10 +305,13 @@ export function init(options) {
207
305
  return _globalClient;
208
306
  }
209
307
  _globalClient = new Visibe(options ?? {});
210
- // Fire async patching — works in both CJS and ESM via dynamic import().
211
- // Patching typically completes within microseconds (cached modules) so there
212
- // is no practical race condition for normal usage patterns.
213
308
  const frameworksToTry = options?.frameworks ?? ALL_FRAMEWORKS;
309
+ // Synchronous CJS path — patches module exports inline so instances created
310
+ // right after init() (e.g. `const client = new OpenAI()`) are instrumented.
311
+ // In ESM, require is undefined and this silently no-ops.
312
+ _syncPatch(_globalClient, frameworksToTry);
313
+ // Async path — handles ESM and framework detection for LangChain/LangGraph
314
+ // (prototype patches don't need to be synchronous).
214
315
  _autoPatch(_globalClient, frameworksToTry).catch(() => { });
215
316
  if (!_shutdownRegistered) {
216
317
  const graceful = async () => { await shutdown(); process.exit(0); };
@@ -148,8 +148,9 @@ export class OpenAIIntegration extends BaseIntegration {
148
148
  throw err;
149
149
  }
150
150
  const model = response.model ?? params.model ?? 'unknown';
151
- const inputTokens = response.usage?.prompt_tokens ?? 0;
152
- const outputTokens = response.usage?.completion_tokens ?? 0;
151
+ // Support both openai v4 (prompt_tokens/completion_tokens) and v5/v6 (input_tokens/output_tokens)
152
+ const inputTokens = response.usage?.prompt_tokens ?? response.usage?.input_tokens ?? 0;
153
+ const outputTokens = response.usage?.completion_tokens ?? response.usage?.output_tokens ?? 0;
153
154
  const cost = calculateCost(model, inputTokens, outputTokens);
154
155
  const choice = response.choices?.[0];
155
156
  const rawContent = choice?.message?.content;
@@ -240,9 +241,10 @@ export class OpenAIIntegration extends BaseIntegration {
240
241
  if (delta)
241
242
  outputText += delta;
242
243
  // Last chunk carries usage when stream_options.include_usage is set
244
+ // Support both openai v4 (prompt_tokens/completion_tokens) and v5/v6 (input_tokens/output_tokens)
243
245
  if (chunk.usage) {
244
- inputTokens = chunk.usage.prompt_tokens ?? 0;
245
- outputTokens = chunk.usage.completion_tokens ?? 0;
246
+ inputTokens = chunk.usage.prompt_tokens ?? chunk.usage.input_tokens ?? 0;
247
+ outputTokens = chunk.usage.completion_tokens ?? chunk.usage.output_tokens ?? 0;
246
248
  }
247
249
  }
248
250
  return result;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@visibe.ai/node",
3
- "version": "0.1.22",
3
+ "version": "0.1.23",
4
4
  "description": "AI Agent Observability — Track OpenAI, LangChain, LangGraph, Bedrock, Vercel AI, Anthropic",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",