@visibe.ai/node 0.1.4

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.
Files changed (41) hide show
  1. package/README.md +330 -0
  2. package/dist/cjs/api.js +92 -0
  3. package/dist/cjs/client.js +242 -0
  4. package/dist/cjs/index.js +216 -0
  5. package/dist/cjs/integrations/anthropic.js +277 -0
  6. package/dist/cjs/integrations/base.js +32 -0
  7. package/dist/cjs/integrations/bedrock.js +442 -0
  8. package/dist/cjs/integrations/group-context.js +10 -0
  9. package/dist/cjs/integrations/langchain.js +274 -0
  10. package/dist/cjs/integrations/langgraph.js +173 -0
  11. package/dist/cjs/integrations/openai.js +447 -0
  12. package/dist/cjs/integrations/vercel-ai.js +261 -0
  13. package/dist/cjs/types/index.js +5 -0
  14. package/dist/cjs/utils.js +122 -0
  15. package/dist/esm/api.js +87 -0
  16. package/dist/esm/client.js +238 -0
  17. package/dist/esm/index.js +209 -0
  18. package/dist/esm/integrations/anthropic.js +272 -0
  19. package/dist/esm/integrations/base.js +28 -0
  20. package/dist/esm/integrations/bedrock.js +438 -0
  21. package/dist/esm/integrations/group-context.js +7 -0
  22. package/dist/esm/integrations/langchain.js +269 -0
  23. package/dist/esm/integrations/langgraph.js +168 -0
  24. package/dist/esm/integrations/openai.js +442 -0
  25. package/dist/esm/integrations/vercel-ai.js +258 -0
  26. package/dist/esm/types/index.js +4 -0
  27. package/dist/esm/utils.js +116 -0
  28. package/dist/types/api.d.ts +27 -0
  29. package/dist/types/client.d.ts +50 -0
  30. package/dist/types/index.d.ts +7 -0
  31. package/dist/types/integrations/anthropic.d.ts +9 -0
  32. package/dist/types/integrations/base.d.ts +17 -0
  33. package/dist/types/integrations/bedrock.d.ts +11 -0
  34. package/dist/types/integrations/group-context.d.ts +12 -0
  35. package/dist/types/integrations/langchain.d.ts +40 -0
  36. package/dist/types/integrations/langgraph.d.ts +13 -0
  37. package/dist/types/integrations/openai.d.ts +11 -0
  38. package/dist/types/integrations/vercel-ai.d.ts +2 -0
  39. package/dist/types/types/index.d.ts +21 -0
  40. package/dist/types/utils.d.ts +23 -0
  41. package/package.json +80 -0
@@ -0,0 +1,438 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { BaseIntegration } from './base';
3
+ import { activeGroupTraceStorage } from './group-context';
4
+ import { calculateCost, detectProvider } from '../utils';
5
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
6
+ function parseInvokeAnthropic(req, res) {
7
+ return {
8
+ prompt: req.messages?.at(-1)?.content?.[0]?.text ?? '',
9
+ outputText: res.content?.[0]?.text ?? '',
10
+ inputTokens: res.usage?.input_tokens ?? 0,
11
+ outputTokens: res.usage?.output_tokens ?? 0,
12
+ };
13
+ }
14
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
15
+ function parseInvokeMeta(req, res) {
16
+ return {
17
+ prompt: req.prompt ?? '',
18
+ outputText: res.generation ?? '',
19
+ inputTokens: res.prompt_token_count ?? 0,
20
+ outputTokens: res.generation_token_count ?? 0,
21
+ };
22
+ }
23
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
24
+ function parseInvokeMistral(req, res) {
25
+ // Mistral via InvokeModelCommand does not return token counts.
26
+ const text = res.outputs?.[0]?.text ?? res.choices?.[0]?.message?.content ?? '';
27
+ return { prompt: req.prompt ?? '', outputText: text, inputTokens: 0, outputTokens: 0 };
28
+ }
29
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
30
+ function parseInvokeTitan(req, res) {
31
+ return {
32
+ prompt: req.inputText ?? '',
33
+ outputText: res.results?.[0]?.outputText ?? '',
34
+ inputTokens: res.inputTextTokenCount ?? 0,
35
+ outputTokens: res.results?.[0]?.tokenCount ?? 0,
36
+ };
37
+ }
38
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
39
+ function parseInvokeNova(req, res) {
40
+ return {
41
+ prompt: req.messages?.at(-1)?.content?.[0]?.text ?? '',
42
+ outputText: res.output?.message?.content?.[0]?.text ?? '',
43
+ inputTokens: res.usage?.inputTokens ?? 0,
44
+ outputTokens: res.usage?.outputTokens ?? 0,
45
+ };
46
+ }
47
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
48
+ function parseInvokeCohere(req, res) {
49
+ const text = res.text ?? res.generations?.[0]?.text ?? '';
50
+ const billedUnits = res.meta?.billed_units;
51
+ return {
52
+ prompt: req.prompt ?? req.message ?? '',
53
+ outputText: text,
54
+ inputTokens: billedUnits?.input_tokens ?? 0,
55
+ outputTokens: billedUnits?.output_tokens ?? 0,
56
+ };
57
+ }
58
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
59
+ function parseInvokeAI21(req, res) {
60
+ return {
61
+ prompt: req.messages?.at(-1)?.content ?? '',
62
+ outputText: res.choices?.[0]?.message?.content ?? '',
63
+ inputTokens: res.usage?.prompt_tokens ?? 0,
64
+ outputTokens: res.usage?.completion_tokens ?? 0,
65
+ };
66
+ }
67
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
68
+ function parseInvokeUnknown(_req, _res) {
69
+ return { prompt: '', outputText: '', inputTokens: 0, outputTokens: 0 };
70
+ }
71
+ function getInvokeParser(modelId) {
72
+ if (modelId.startsWith('anthropic.'))
73
+ return parseInvokeAnthropic;
74
+ if (modelId.startsWith('meta.'))
75
+ return parseInvokeMeta;
76
+ if (modelId.startsWith('mistral.'))
77
+ return parseInvokeMistral;
78
+ if (modelId.startsWith('amazon.titan'))
79
+ return parseInvokeTitan;
80
+ if (modelId.startsWith('amazon.nova'))
81
+ return parseInvokeNova;
82
+ if (modelId.startsWith('cohere.'))
83
+ return parseInvokeCohere;
84
+ if (modelId.startsWith('ai21.'))
85
+ return parseInvokeAI21;
86
+ return parseInvokeUnknown;
87
+ }
88
+ // ---------------------------------------------------------------------------
89
+ // BedrockIntegration
90
+ // ---------------------------------------------------------------------------
91
+ export class BedrockIntegration extends BaseIntegration {
92
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
93
+ patchClient(client, agentName) {
94
+ const originalSend = client.send.bind(client);
95
+ // We need the command constructors to do instanceof checks.
96
+ // They are imported lazily so this file can be loaded without the package installed.
97
+ let ConverseCommand;
98
+ let ConverseStreamCommand;
99
+ let InvokeModelCommand;
100
+ let InvokeModelWithResponseStreamCommand;
101
+ try {
102
+ const mod = require('@aws-sdk/client-bedrock-runtime');
103
+ ConverseCommand = mod.ConverseCommand;
104
+ ConverseStreamCommand = mod.ConverseStreamCommand;
105
+ InvokeModelCommand = mod.InvokeModelCommand;
106
+ InvokeModelWithResponseStreamCommand = mod.InvokeModelWithResponseStreamCommand;
107
+ }
108
+ catch {
109
+ // Package not installed — return a no-op restore.
110
+ return () => { };
111
+ }
112
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
113
+ client.send = async (command, ...rest) => {
114
+ if (ConverseCommand && command instanceof ConverseCommand) {
115
+ return this._traceConverse(originalSend, command, rest, agentName);
116
+ }
117
+ if (InvokeModelCommand && command instanceof InvokeModelCommand) {
118
+ return this._traceInvokeModel(originalSend, command, rest, agentName);
119
+ }
120
+ if (ConverseStreamCommand && command instanceof ConverseStreamCommand) {
121
+ return this._traceConverseStream(originalSend, command, rest, agentName);
122
+ }
123
+ if (InvokeModelWithResponseStreamCommand &&
124
+ command instanceof InvokeModelWithResponseStreamCommand) {
125
+ // Streaming invoke — pass through for now; non-streaming covers most use cases.
126
+ return originalSend(command, ...rest);
127
+ }
128
+ return originalSend(command, ...rest);
129
+ };
130
+ return () => { client.send = originalSend; };
131
+ }
132
+ // ---------------------------------------------------------------------------
133
+ // ConverseCommand
134
+ // ---------------------------------------------------------------------------
135
+ async _traceConverse(
136
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
137
+ original,
138
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
139
+ command,
140
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
141
+ rest, agentName) {
142
+ const groupCtx = activeGroupTraceStorage.getStore();
143
+ const traceId = groupCtx?.traceId ?? randomUUID();
144
+ const startedAt = new Date().toISOString();
145
+ const startMs = Date.now();
146
+ const input = command.input;
147
+ if (!groupCtx) {
148
+ await this.visibe.apiClient.createTrace({
149
+ trace_id: traceId,
150
+ name: agentName,
151
+ framework: 'bedrock',
152
+ started_at: startedAt,
153
+ ...(this.visibe.sessionId ? { session_id: this.visibe.sessionId } : {}),
154
+ });
155
+ }
156
+ const spanId = this.nextSpanId();
157
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
158
+ let response;
159
+ let spanStatus = 'success';
160
+ try {
161
+ response = await original(command, ...rest);
162
+ }
163
+ catch (err) {
164
+ spanStatus = 'failed';
165
+ this.visibe.batcher.add(traceId, this.visibe.buildErrorSpan({
166
+ spanId: this.nextSpanId(),
167
+ errorType: err?.constructor?.name ?? 'Error',
168
+ errorMessage: err?.message ?? String(err),
169
+ }));
170
+ if (!groupCtx) {
171
+ this.visibe.batcher.flush();
172
+ await this.visibe.apiClient.completeTrace(traceId, {
173
+ status: 'failed', ended_at: new Date().toISOString(), duration_ms: Date.now() - startMs,
174
+ });
175
+ }
176
+ throw err;
177
+ }
178
+ const model = input.modelId ?? 'unknown';
179
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
180
+ const prompt = input.messages?.at(-1)?.content?.find((b) => b.text)?.text ?? '';
181
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
182
+ const output = response.output?.message?.content?.find((b) => b.text)?.text ?? '';
183
+ const inputTokens = response.usage?.inputTokens ?? 0;
184
+ const outputTokens = response.usage?.outputTokens ?? 0;
185
+ const cost = calculateCost(model, inputTokens, outputTokens);
186
+ this.visibe.batcher.add(traceId, this.visibe.buildLLMSpan({
187
+ spanId,
188
+ agentName,
189
+ model,
190
+ status: spanStatus,
191
+ inputTokens,
192
+ outputTokens,
193
+ inputText: prompt,
194
+ outputText: output,
195
+ durationMs: Date.now() - startMs,
196
+ }));
197
+ // Notify the group tracker (if inside track()) about this LLM span.
198
+ groupCtx?.onLLMSpan(inputTokens, outputTokens, cost);
199
+ if (!groupCtx) {
200
+ this.visibe.batcher.flush();
201
+ const sent = await this.visibe.apiClient.completeTrace(traceId, {
202
+ status: 'completed',
203
+ ended_at: new Date().toISOString(),
204
+ duration_ms: Date.now() - startMs,
205
+ llm_call_count: 1,
206
+ prompt,
207
+ model,
208
+ total_cost: cost,
209
+ total_tokens: inputTokens + outputTokens,
210
+ total_input_tokens: inputTokens,
211
+ total_output_tokens: outputTokens,
212
+ });
213
+ _printSummary(agentName, model, inputTokens, outputTokens, cost, Date.now() - startMs, sent);
214
+ }
215
+ return response;
216
+ }
217
+ // ---------------------------------------------------------------------------
218
+ // InvokeModelCommand — raw JSON body, provider-specific parsing
219
+ // ---------------------------------------------------------------------------
220
+ async _traceInvokeModel(
221
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
222
+ original,
223
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
224
+ command,
225
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
226
+ rest, agentName) {
227
+ const groupCtx = activeGroupTraceStorage.getStore();
228
+ const traceId = groupCtx?.traceId ?? randomUUID();
229
+ const startedAt = new Date().toISOString();
230
+ const startMs = Date.now();
231
+ if (!groupCtx) {
232
+ await this.visibe.apiClient.createTrace({
233
+ trace_id: traceId,
234
+ name: agentName,
235
+ framework: 'bedrock',
236
+ started_at: startedAt,
237
+ ...(this.visibe.sessionId ? { session_id: this.visibe.sessionId } : {}),
238
+ });
239
+ }
240
+ const modelId = command.input?.modelId ?? 'unknown';
241
+ const spanId = this.nextSpanId();
242
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
243
+ let response;
244
+ let spanStatus = 'success';
245
+ // Decode the request body (Uint8Array or Buffer).
246
+ let reqBody = {};
247
+ try {
248
+ const bodyStr = new TextDecoder().decode(command.input?.body);
249
+ reqBody = JSON.parse(bodyStr);
250
+ }
251
+ catch { /* malformed body — continue with empty */ }
252
+ try {
253
+ response = await original(command, ...rest);
254
+ }
255
+ catch (err) {
256
+ spanStatus = 'failed';
257
+ this.visibe.batcher.add(traceId, this.visibe.buildErrorSpan({
258
+ spanId: this.nextSpanId(),
259
+ errorType: err?.constructor?.name ?? 'Error',
260
+ errorMessage: err?.message ?? String(err),
261
+ }));
262
+ if (!groupCtx) {
263
+ this.visibe.batcher.flush();
264
+ await this.visibe.apiClient.completeTrace(traceId, {
265
+ status: 'failed', ended_at: new Date().toISOString(), duration_ms: Date.now() - startMs,
266
+ });
267
+ }
268
+ throw err;
269
+ }
270
+ // The JS SDK already buffers the response body as Uint8Array — no streaming needed.
271
+ let resBody = {};
272
+ try {
273
+ const bodyStr = new TextDecoder().decode(response.body);
274
+ resBody = JSON.parse(bodyStr);
275
+ }
276
+ catch { /* malformed body — continue with empty */ }
277
+ const parse = getInvokeParser(modelId);
278
+ const { prompt, outputText, inputTokens, outputTokens } = parse(reqBody, resBody);
279
+ const cost = calculateCost(modelId, inputTokens, outputTokens);
280
+ this.visibe.batcher.add(traceId, this.visibe.buildLLMSpan({
281
+ spanId,
282
+ agentName,
283
+ model: modelId,
284
+ status: spanStatus,
285
+ inputTokens,
286
+ outputTokens,
287
+ inputText: prompt,
288
+ outputText,
289
+ durationMs: Date.now() - startMs,
290
+ }));
291
+ // Notify the group tracker (if inside track()) about this LLM span.
292
+ groupCtx?.onLLMSpan(inputTokens, outputTokens, cost);
293
+ if (!groupCtx) {
294
+ this.visibe.batcher.flush();
295
+ const sent = await this.visibe.apiClient.completeTrace(traceId, {
296
+ status: 'completed',
297
+ ended_at: new Date().toISOString(),
298
+ duration_ms: Date.now() - startMs,
299
+ llm_call_count: 1,
300
+ prompt,
301
+ model: modelId,
302
+ total_cost: cost,
303
+ total_tokens: inputTokens + outputTokens,
304
+ total_input_tokens: inputTokens,
305
+ total_output_tokens: outputTokens,
306
+ });
307
+ _printSummary(agentName, modelId, inputTokens, outputTokens, cost, Date.now() - startMs, sent);
308
+ }
309
+ return response;
310
+ }
311
+ // ---------------------------------------------------------------------------
312
+ // ConverseStreamCommand — stream, accumulate, then finalize
313
+ // ---------------------------------------------------------------------------
314
+ async _traceConverseStream(
315
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
316
+ original,
317
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
318
+ command,
319
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
320
+ rest, agentName) {
321
+ const groupCtx = activeGroupTraceStorage.getStore();
322
+ const traceId = groupCtx?.traceId ?? randomUUID();
323
+ const startedAt = new Date().toISOString();
324
+ const startMs = Date.now();
325
+ const model = command.input?.modelId ?? 'unknown';
326
+ const spanId = this.nextSpanId();
327
+ if (!groupCtx) {
328
+ await this.visibe.apiClient.createTrace({
329
+ trace_id: traceId,
330
+ name: agentName,
331
+ framework: 'bedrock',
332
+ started_at: startedAt,
333
+ ...(this.visibe.sessionId ? { session_id: this.visibe.sessionId } : {}),
334
+ });
335
+ }
336
+ const response = await original(command, ...rest);
337
+ // The Converse stream emits a `metadata` event with usage totals.
338
+ let inputTokens = 0, outputTokens = 0;
339
+ let outputText = '';
340
+ const stream = response.stream;
341
+ if (stream && typeof stream[Symbol.asyncIterator] === 'function') {
342
+ const visibe = this.visibe;
343
+ const batcher = this.visibe.batcher;
344
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
345
+ const origIter = stream[Symbol.asyncIterator].bind(stream);
346
+ let finalized = false;
347
+ const finalize = async (status) => {
348
+ if (finalized)
349
+ return;
350
+ finalized = true;
351
+ const cost = calculateCost(model, inputTokens, outputTokens);
352
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
353
+ const prompt = command.input?.messages?.at(-1)?.content?.find((b) => b.text)?.text ?? '';
354
+ batcher.add(traceId, visibe.buildLLMSpan({
355
+ spanId, agentName, model, status,
356
+ inputTokens, outputTokens,
357
+ inputText: prompt,
358
+ outputText,
359
+ durationMs: Date.now() - startMs,
360
+ }));
361
+ // Notify the group tracker (if inside track()) about this LLM span.
362
+ groupCtx?.onLLMSpan(inputTokens, outputTokens, cost);
363
+ if (!groupCtx) {
364
+ batcher.flush();
365
+ const sent = await visibe.apiClient.completeTrace(traceId, {
366
+ status: status === 'success' ? 'completed' : 'failed',
367
+ ended_at: new Date().toISOString(),
368
+ duration_ms: Date.now() - startMs,
369
+ llm_call_count: 1,
370
+ prompt,
371
+ model,
372
+ total_cost: cost,
373
+ total_tokens: inputTokens + outputTokens,
374
+ total_input_tokens: inputTokens,
375
+ total_output_tokens: outputTokens,
376
+ });
377
+ _printSummary(agentName, model, inputTokens, outputTokens, cost, Date.now() - startMs, sent);
378
+ }
379
+ };
380
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
381
+ stream[Symbol.asyncIterator] = () => {
382
+ const it = origIter();
383
+ return {
384
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
385
+ async next() {
386
+ try {
387
+ const result = await it.next();
388
+ if (!result.done) {
389
+ const chunk = result.value;
390
+ // Accumulate text deltas
391
+ const textDelta = chunk?.contentBlockDelta?.delta?.text;
392
+ if (textDelta)
393
+ outputText += textDelta;
394
+ // Metadata chunk carries usage
395
+ if (chunk?.metadata?.usage) {
396
+ inputTokens = chunk.metadata.usage.inputTokens ?? 0;
397
+ outputTokens = chunk.metadata.usage.outputTokens ?? 0;
398
+ }
399
+ }
400
+ else {
401
+ await finalize('success');
402
+ }
403
+ return result;
404
+ }
405
+ catch (err) {
406
+ await finalize('failed');
407
+ throw err;
408
+ }
409
+ },
410
+ async return(value) {
411
+ await finalize('success');
412
+ return it.return ? it.return(value) : { done: true, value };
413
+ },
414
+ };
415
+ };
416
+ }
417
+ return response;
418
+ }
419
+ }
420
+ // ---------------------------------------------------------------------------
421
+ // Module-level factory
422
+ // ---------------------------------------------------------------------------
423
+ export function patchBedrockClient(
424
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
425
+ client, agentName, visibe) {
426
+ const integration = new BedrockIntegration(visibe);
427
+ return integration.patchClient(client, agentName);
428
+ }
429
+ // ---------------------------------------------------------------------------
430
+ // Private helpers
431
+ // ---------------------------------------------------------------------------
432
+ function _printSummary(name, model, inputTokens, outputTokens, cost, durationMs, sent) {
433
+ const tokens = (inputTokens + outputTokens).toLocaleString();
434
+ const sentStr = sent ? 'OK' : 'FAILED';
435
+ console.log(`[Visibe] Trace: ${name} | 1 LLM calls | ${tokens} tokens | $${cost.toFixed(6)} | ${(durationMs / 1000).toFixed(1)}s | 0 tool calls | status: completed | model: ${model} | sent: ${sentStr}`);
436
+ }
437
+ // Re-export detectProvider so it's available for tests against this module.
438
+ export { detectProvider };
@@ -0,0 +1,7 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks';
2
+ /**
3
+ * Set by Visibe.track() around the user's fn().
4
+ * All integrations read from this store to route spans into the shared group trace
5
+ * and report token/cost totals back to the track() accumulator.
6
+ */
7
+ export const activeGroupTraceStorage = new AsyncLocalStorage();