@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.
- package/README.md +330 -0
- package/dist/cjs/api.js +92 -0
- package/dist/cjs/client.js +242 -0
- package/dist/cjs/index.js +216 -0
- package/dist/cjs/integrations/anthropic.js +277 -0
- package/dist/cjs/integrations/base.js +32 -0
- package/dist/cjs/integrations/bedrock.js +442 -0
- package/dist/cjs/integrations/group-context.js +10 -0
- package/dist/cjs/integrations/langchain.js +274 -0
- package/dist/cjs/integrations/langgraph.js +173 -0
- package/dist/cjs/integrations/openai.js +447 -0
- package/dist/cjs/integrations/vercel-ai.js +261 -0
- package/dist/cjs/types/index.js +5 -0
- package/dist/cjs/utils.js +122 -0
- package/dist/esm/api.js +87 -0
- package/dist/esm/client.js +238 -0
- package/dist/esm/index.js +209 -0
- package/dist/esm/integrations/anthropic.js +272 -0
- package/dist/esm/integrations/base.js +28 -0
- package/dist/esm/integrations/bedrock.js +438 -0
- package/dist/esm/integrations/group-context.js +7 -0
- package/dist/esm/integrations/langchain.js +269 -0
- package/dist/esm/integrations/langgraph.js +168 -0
- package/dist/esm/integrations/openai.js +442 -0
- package/dist/esm/integrations/vercel-ai.js +258 -0
- package/dist/esm/types/index.js +4 -0
- package/dist/esm/utils.js +116 -0
- package/dist/types/api.d.ts +27 -0
- package/dist/types/client.d.ts +50 -0
- package/dist/types/index.d.ts +7 -0
- package/dist/types/integrations/anthropic.d.ts +9 -0
- package/dist/types/integrations/base.d.ts +17 -0
- package/dist/types/integrations/bedrock.d.ts +11 -0
- package/dist/types/integrations/group-context.d.ts +12 -0
- package/dist/types/integrations/langchain.d.ts +40 -0
- package/dist/types/integrations/langgraph.d.ts +13 -0
- package/dist/types/integrations/openai.d.ts +11 -0
- package/dist/types/integrations/vercel-ai.d.ts +2 -0
- package/dist/types/types/index.d.ts +21 -0
- package/dist/types/utils.d.ts +23 -0
- 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();
|