@visibe.ai/node 0.1.21 → 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;
@@ -190,12 +196,25 @@ async function patchFramework(framework, client) {
190
196
  }
191
197
  case 'langchain': {
192
198
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
193
- const { patchRunnableSequence } = await Promise.resolve().then(() => __importStar(require('./integrations/langchain')));
199
+ const { patchRunnableSequence, patchAgentExecutor } = await Promise.resolve().then(() => __importStar(require('./integrations/langchain')));
194
200
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
195
201
  const lcModule = await Promise.resolve().then(() => __importStar(require('@langchain/core/runnables')));
196
- const result = patchRunnableSequence(lcModule, client);
197
- if (typeof result === 'function')
198
- _lcRestore = result;
202
+ const r1 = patchRunnableSequence(lcModule, client);
203
+ // Also patch AgentExecutor (langchain/agents) if installed — AgentExecutor is NOT a
204
+ // RunnableSequence so without this patch each agent loop iteration creates a separate trace.
205
+ let r2;
206
+ try {
207
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
208
+ const agentsModule = await Promise.resolve().then(() => __importStar(require('langchain/agents')));
209
+ r2 = patchAgentExecutor(agentsModule, client);
210
+ }
211
+ catch { /* langchain package not installed — skip */ }
212
+ _lcRestore = () => {
213
+ if (typeof r1 === 'function')
214
+ r1();
215
+ if (typeof r2 === 'function')
216
+ r2();
217
+ };
199
218
  break;
200
219
  }
201
220
  case 'vercel_ai': {
@@ -225,6 +244,98 @@ async function _autoPatch(client, frameworks) {
225
244
  }
226
245
  }
227
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
+ // ---------------------------------------------------------------------------
228
339
  // init()
229
340
  // ---------------------------------------------------------------------------
230
341
  function init(options) {
@@ -233,10 +344,13 @@ function init(options) {
233
344
  return _globalClient;
234
345
  }
235
346
  _globalClient = new client_1.Visibe(options ?? {});
236
- // Fire async patching — works in both CJS and ESM via dynamic import().
237
- // Patching typically completes within microseconds (cached modules) so there
238
- // is no practical race condition for normal usage patterns.
239
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).
240
354
  _autoPatch(_globalClient, frameworksToTry).catch(() => { });
241
355
  if (!_shutdownRegistered) {
242
356
  const graceful = async () => { await shutdown(); process.exit(0); };
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.LangChainCallback = exports.LANGGRAPH_INTERNAL_NODES = exports.activeLangChainStorage = void 0;
4
4
  exports.patchRunnableSequence = patchRunnableSequence;
5
+ exports.patchAgentExecutor = patchAgentExecutor;
5
6
  const node_async_hooks_1 = require("node:async_hooks");
6
7
  const node_crypto_1 = require("node:crypto");
7
8
  const utils_1 = require("../utils");
@@ -312,6 +313,109 @@ function patchRunnableSequence(lcModule, visibe) {
312
313
  };
313
314
  }
314
315
  // ---------------------------------------------------------------------------
316
+ // patchAgentExecutor — patches AgentExecutor from `langchain/agents` so the
317
+ // entire multi-step agent loop is captured as ONE trace.
318
+ //
319
+ // AgentExecutor is NOT a RunnableSequence — it extends Runnable directly and
320
+ // calls the inner agent RunnableSequence in a loop. Without this patch each
321
+ // iteration fires patchRunnableSequence and creates a separate trace.
322
+ // ---------------------------------------------------------------------------
323
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
324
+ function patchAgentExecutor(agentsModule, visibe) {
325
+ const AgentExecutor = agentsModule?.AgentExecutor;
326
+ if (!AgentExecutor)
327
+ return () => { };
328
+ const originalInvoke = AgentExecutor.prototype.invoke;
329
+ const originalStream = AgentExecutor.prototype.stream;
330
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
331
+ AgentExecutor.prototype.invoke = async function (input, config) {
332
+ if (exports.activeLangChainStorage.getStore() !== undefined) {
333
+ return originalInvoke.call(this, input, config);
334
+ }
335
+ const traceId = (0, node_crypto_1.randomUUID)();
336
+ const startedAt = new Date().toISOString();
337
+ const startMs = Date.now();
338
+ const agentName = this.name ?? 'langchain';
339
+ await visibe.apiClient.createTrace({
340
+ trace_id: traceId,
341
+ name: agentName,
342
+ framework: 'langchain',
343
+ started_at: startedAt,
344
+ ...(visibe.sessionId ? { session_id: visibe.sessionId } : {}),
345
+ });
346
+ const cb = new LangChainCallback({ visibe, traceId, agentName });
347
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
348
+ let result;
349
+ let status = 'completed';
350
+ try {
351
+ result = await exports.activeLangChainStorage.run(cb, () => originalInvoke.call(this, input, _mergeCallbacks(config, cb)));
352
+ }
353
+ catch (err) {
354
+ status = 'failed';
355
+ throw err;
356
+ }
357
+ finally {
358
+ visibe.batcher.flush();
359
+ await visibe.apiClient.completeTrace(traceId, {
360
+ status,
361
+ ended_at: new Date().toISOString(),
362
+ duration_ms: Date.now() - startMs,
363
+ llm_call_count: cb.llmCallCount,
364
+ total_cost: cb.totalCost,
365
+ total_tokens: cb.totalInputTokens + cb.totalOutputTokens,
366
+ total_input_tokens: cb.totalInputTokens,
367
+ total_output_tokens: cb.totalOutputTokens,
368
+ });
369
+ }
370
+ return result;
371
+ };
372
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
373
+ AgentExecutor.prototype.stream = async function* (input, config) {
374
+ if (exports.activeLangChainStorage.getStore() !== undefined) {
375
+ yield* (await originalStream.call(this, input, config));
376
+ return;
377
+ }
378
+ const traceId = (0, node_crypto_1.randomUUID)();
379
+ const startedAt = new Date().toISOString();
380
+ const startMs = Date.now();
381
+ const agentName = this.name ?? 'langchain';
382
+ await visibe.apiClient.createTrace({
383
+ trace_id: traceId,
384
+ name: agentName,
385
+ framework: 'langchain',
386
+ started_at: startedAt,
387
+ ...(visibe.sessionId ? { session_id: visibe.sessionId } : {}),
388
+ });
389
+ const cb = new LangChainCallback({ visibe, traceId, agentName });
390
+ let status = 'completed';
391
+ try {
392
+ const gen = await exports.activeLangChainStorage.run(cb, () => originalStream.call(this, input, _mergeCallbacks(config, cb)));
393
+ yield* gen;
394
+ }
395
+ catch (err) {
396
+ status = 'failed';
397
+ throw err;
398
+ }
399
+ finally {
400
+ visibe.batcher.flush();
401
+ await visibe.apiClient.completeTrace(traceId, {
402
+ status,
403
+ ended_at: new Date().toISOString(),
404
+ duration_ms: Date.now() - startMs,
405
+ llm_call_count: cb.llmCallCount,
406
+ total_cost: cb.totalCost,
407
+ total_tokens: cb.totalInputTokens + cb.totalOutputTokens,
408
+ total_input_tokens: cb.totalInputTokens,
409
+ total_output_tokens: cb.totalOutputTokens,
410
+ });
411
+ }
412
+ };
413
+ return () => {
414
+ AgentExecutor.prototype.invoke = originalInvoke;
415
+ AgentExecutor.prototype.stream = originalStream;
416
+ };
417
+ }
418
+ // ---------------------------------------------------------------------------
315
419
  // Private helpers
316
420
  // ---------------------------------------------------------------------------
317
421
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -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;
@@ -151,12 +157,25 @@ async function patchFramework(framework, client) {
151
157
  }
152
158
  case 'langchain': {
153
159
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
154
- const { patchRunnableSequence } = await import('./integrations/langchain.js');
160
+ const { patchRunnableSequence, patchAgentExecutor } = await import('./integrations/langchain.js');
155
161
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
156
162
  const lcModule = await import('@langchain/core/runnables');
157
- const result = patchRunnableSequence(lcModule, client);
158
- if (typeof result === 'function')
159
- _lcRestore = result;
163
+ const r1 = patchRunnableSequence(lcModule, client);
164
+ // Also patch AgentExecutor (langchain/agents) if installed — AgentExecutor is NOT a
165
+ // RunnableSequence so without this patch each agent loop iteration creates a separate trace.
166
+ let r2;
167
+ try {
168
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
169
+ const agentsModule = await import('langchain/agents');
170
+ r2 = patchAgentExecutor(agentsModule, client);
171
+ }
172
+ catch { /* langchain package not installed — skip */ }
173
+ _lcRestore = () => {
174
+ if (typeof r1 === 'function')
175
+ r1();
176
+ if (typeof r2 === 'function')
177
+ r2();
178
+ };
160
179
  break;
161
180
  }
162
181
  case 'vercel_ai': {
@@ -186,6 +205,98 @@ async function _autoPatch(client, frameworks) {
186
205
  }
187
206
  }
188
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
+ // ---------------------------------------------------------------------------
189
300
  // init()
190
301
  // ---------------------------------------------------------------------------
191
302
  export function init(options) {
@@ -194,10 +305,13 @@ export function init(options) {
194
305
  return _globalClient;
195
306
  }
196
307
  _globalClient = new Visibe(options ?? {});
197
- // Fire async patching — works in both CJS and ESM via dynamic import().
198
- // Patching typically completes within microseconds (cached modules) so there
199
- // is no practical race condition for normal usage patterns.
200
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).
201
315
  _autoPatch(_globalClient, frameworksToTry).catch(() => { });
202
316
  if (!_shutdownRegistered) {
203
317
  const graceful = async () => { await shutdown(); process.exit(0); };
@@ -307,6 +307,109 @@ export function patchRunnableSequence(lcModule, visibe) {
307
307
  };
308
308
  }
309
309
  // ---------------------------------------------------------------------------
310
+ // patchAgentExecutor — patches AgentExecutor from `langchain/agents` so the
311
+ // entire multi-step agent loop is captured as ONE trace.
312
+ //
313
+ // AgentExecutor is NOT a RunnableSequence — it extends Runnable directly and
314
+ // calls the inner agent RunnableSequence in a loop. Without this patch each
315
+ // iteration fires patchRunnableSequence and creates a separate trace.
316
+ // ---------------------------------------------------------------------------
317
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
318
+ export function patchAgentExecutor(agentsModule, visibe) {
319
+ const AgentExecutor = agentsModule?.AgentExecutor;
320
+ if (!AgentExecutor)
321
+ return () => { };
322
+ const originalInvoke = AgentExecutor.prototype.invoke;
323
+ const originalStream = AgentExecutor.prototype.stream;
324
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
325
+ AgentExecutor.prototype.invoke = async function (input, config) {
326
+ if (activeLangChainStorage.getStore() !== undefined) {
327
+ return originalInvoke.call(this, input, config);
328
+ }
329
+ const traceId = randomUUID();
330
+ const startedAt = new Date().toISOString();
331
+ const startMs = Date.now();
332
+ const agentName = this.name ?? 'langchain';
333
+ await visibe.apiClient.createTrace({
334
+ trace_id: traceId,
335
+ name: agentName,
336
+ framework: 'langchain',
337
+ started_at: startedAt,
338
+ ...(visibe.sessionId ? { session_id: visibe.sessionId } : {}),
339
+ });
340
+ const cb = new LangChainCallback({ visibe, traceId, agentName });
341
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
342
+ let result;
343
+ let status = 'completed';
344
+ try {
345
+ result = await activeLangChainStorage.run(cb, () => originalInvoke.call(this, input, _mergeCallbacks(config, cb)));
346
+ }
347
+ catch (err) {
348
+ status = 'failed';
349
+ throw err;
350
+ }
351
+ finally {
352
+ visibe.batcher.flush();
353
+ await visibe.apiClient.completeTrace(traceId, {
354
+ status,
355
+ ended_at: new Date().toISOString(),
356
+ duration_ms: Date.now() - startMs,
357
+ llm_call_count: cb.llmCallCount,
358
+ total_cost: cb.totalCost,
359
+ total_tokens: cb.totalInputTokens + cb.totalOutputTokens,
360
+ total_input_tokens: cb.totalInputTokens,
361
+ total_output_tokens: cb.totalOutputTokens,
362
+ });
363
+ }
364
+ return result;
365
+ };
366
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
367
+ AgentExecutor.prototype.stream = async function* (input, config) {
368
+ if (activeLangChainStorage.getStore() !== undefined) {
369
+ yield* (await originalStream.call(this, input, config));
370
+ return;
371
+ }
372
+ const traceId = randomUUID();
373
+ const startedAt = new Date().toISOString();
374
+ const startMs = Date.now();
375
+ const agentName = this.name ?? 'langchain';
376
+ await visibe.apiClient.createTrace({
377
+ trace_id: traceId,
378
+ name: agentName,
379
+ framework: 'langchain',
380
+ started_at: startedAt,
381
+ ...(visibe.sessionId ? { session_id: visibe.sessionId } : {}),
382
+ });
383
+ const cb = new LangChainCallback({ visibe, traceId, agentName });
384
+ let status = 'completed';
385
+ try {
386
+ const gen = await activeLangChainStorage.run(cb, () => originalStream.call(this, input, _mergeCallbacks(config, cb)));
387
+ yield* gen;
388
+ }
389
+ catch (err) {
390
+ status = 'failed';
391
+ throw err;
392
+ }
393
+ finally {
394
+ visibe.batcher.flush();
395
+ await visibe.apiClient.completeTrace(traceId, {
396
+ status,
397
+ ended_at: new Date().toISOString(),
398
+ duration_ms: Date.now() - startMs,
399
+ llm_call_count: cb.llmCallCount,
400
+ total_cost: cb.totalCost,
401
+ total_tokens: cb.totalInputTokens + cb.totalOutputTokens,
402
+ total_input_tokens: cb.totalInputTokens,
403
+ total_output_tokens: cb.totalOutputTokens,
404
+ });
405
+ }
406
+ };
407
+ return () => {
408
+ AgentExecutor.prototype.invoke = originalInvoke;
409
+ AgentExecutor.prototype.stream = originalStream;
410
+ };
411
+ }
412
+ // ---------------------------------------------------------------------------
310
413
  // Private helpers
311
414
  // ---------------------------------------------------------------------------
312
415
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -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;
@@ -49,3 +49,4 @@ export declare class LangChainCallback {
49
49
  _onToolSpan?: () => void;
50
50
  }
51
51
  export declare function patchRunnableSequence(lcModule: any, visibe: Visibe): () => void;
52
+ export declare function patchAgentExecutor(agentsModule: any, visibe: Visibe): () => void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@visibe.ai/node",
3
- "version": "0.1.21",
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",