@sentrial/sdk 0.4.0 → 0.4.3
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/index.cjs +1556 -773
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +349 -142
- package/dist/index.d.ts +349 -142
- package/dist/index.js +1552 -772
- package/dist/index.js.map +1 -1
- package/package.json +6 -1
package/dist/index.js
CHANGED
|
@@ -68,7 +68,22 @@ var ValidationError = class extends SentrialError {
|
|
|
68
68
|
};
|
|
69
69
|
|
|
70
70
|
// src/redact.ts
|
|
71
|
-
|
|
71
|
+
var _createHash;
|
|
72
|
+
try {
|
|
73
|
+
const mod = eval("require")("crypto");
|
|
74
|
+
if (mod?.createHash) {
|
|
75
|
+
_createHash = mod.createHash;
|
|
76
|
+
}
|
|
77
|
+
} catch {
|
|
78
|
+
}
|
|
79
|
+
function getCreateHash() {
|
|
80
|
+
if (!_createHash) {
|
|
81
|
+
throw new Error(
|
|
82
|
+
'Sentrial PII hash mode requires Node.js crypto module. Use mode "label" or "remove" in browser/edge environments.'
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
return _createHash;
|
|
86
|
+
}
|
|
72
87
|
var DEFAULT_FIELDS = [
|
|
73
88
|
"userInput",
|
|
74
89
|
"assistantOutput",
|
|
@@ -95,7 +110,7 @@ var BUILTIN_PATTERNS = {
|
|
|
95
110
|
ipAddresses: { pattern: IP_ADDRESS_PATTERN, label: "IP_ADDRESS" }
|
|
96
111
|
};
|
|
97
112
|
function hashValue(value) {
|
|
98
|
-
return
|
|
113
|
+
return getCreateHash()("sha256").update(value).digest("hex").slice(0, 6);
|
|
99
114
|
}
|
|
100
115
|
function replaceMatch(match, label, mode) {
|
|
101
116
|
switch (mode) {
|
|
@@ -157,6 +172,59 @@ function redactPayload(payload, config) {
|
|
|
157
172
|
return result;
|
|
158
173
|
}
|
|
159
174
|
|
|
175
|
+
// src/async-context.ts
|
|
176
|
+
var _AsyncLocalStorage = null;
|
|
177
|
+
try {
|
|
178
|
+
const mod = eval("require")("node:async_hooks");
|
|
179
|
+
if (mod?.AsyncLocalStorage) {
|
|
180
|
+
_AsyncLocalStorage = mod.AsyncLocalStorage;
|
|
181
|
+
}
|
|
182
|
+
} catch {
|
|
183
|
+
}
|
|
184
|
+
var NodeContextVar = class {
|
|
185
|
+
_storage;
|
|
186
|
+
_defaultValue;
|
|
187
|
+
constructor(defaultValue) {
|
|
188
|
+
this._storage = new _AsyncLocalStorage();
|
|
189
|
+
this._defaultValue = defaultValue;
|
|
190
|
+
}
|
|
191
|
+
get() {
|
|
192
|
+
const store = this._storage.getStore();
|
|
193
|
+
return store !== void 0 ? store : this._defaultValue;
|
|
194
|
+
}
|
|
195
|
+
set(value) {
|
|
196
|
+
const previous = this.get();
|
|
197
|
+
this._storage.enterWith(value);
|
|
198
|
+
return { _previous: previous };
|
|
199
|
+
}
|
|
200
|
+
reset(token) {
|
|
201
|
+
this._storage.enterWith(token._previous);
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
var SimpleContextVar = class {
|
|
205
|
+
_value;
|
|
206
|
+
constructor(defaultValue) {
|
|
207
|
+
this._value = defaultValue;
|
|
208
|
+
}
|
|
209
|
+
get() {
|
|
210
|
+
return this._value;
|
|
211
|
+
}
|
|
212
|
+
set(value) {
|
|
213
|
+
const previous = this._value;
|
|
214
|
+
this._value = value;
|
|
215
|
+
return { _previous: previous };
|
|
216
|
+
}
|
|
217
|
+
reset(token) {
|
|
218
|
+
this._value = token._previous;
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
function createContextVar(defaultValue) {
|
|
222
|
+
if (_AsyncLocalStorage) {
|
|
223
|
+
return new NodeContextVar(defaultValue);
|
|
224
|
+
}
|
|
225
|
+
return new SimpleContextVar(defaultValue);
|
|
226
|
+
}
|
|
227
|
+
|
|
160
228
|
// src/cost.ts
|
|
161
229
|
var OPENAI_PRICING = {
|
|
162
230
|
"gpt-5.2": { input: 5, output: 15 },
|
|
@@ -208,7 +276,8 @@ var GOOGLE_PRICING = {
|
|
|
208
276
|
"gemini-1.0-pro": { input: 0.5, output: 1.5 }
|
|
209
277
|
};
|
|
210
278
|
function findModelKey(model, pricing) {
|
|
211
|
-
|
|
279
|
+
const keys = Object.keys(pricing).sort((a, b) => b.length - a.length);
|
|
280
|
+
for (const key of keys) {
|
|
212
281
|
if (model.startsWith(key)) {
|
|
213
282
|
return key;
|
|
214
283
|
}
|
|
@@ -236,182 +305,832 @@ function calculateGoogleCost(params) {
|
|
|
236
305
|
return calculateCost(inputTokens, outputTokens, GOOGLE_PRICING[modelKey]);
|
|
237
306
|
}
|
|
238
307
|
|
|
239
|
-
// src/
|
|
240
|
-
var
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
// src/client.ts
|
|
249
|
-
var DEFAULT_API_URL = "https://api.sentrial.com";
|
|
250
|
-
var MAX_RETRIES = 3;
|
|
251
|
-
var INITIAL_BACKOFF_MS = 500;
|
|
252
|
-
var MAX_BACKOFF_MS = 8e3;
|
|
253
|
-
var BACKOFF_MULTIPLIER = 2;
|
|
254
|
-
var RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([408, 429, 500, 502, 503, 504]);
|
|
255
|
-
var REQUEST_TIMEOUT_MS = 1e4;
|
|
256
|
-
var SentrialClient = class {
|
|
257
|
-
apiUrl;
|
|
258
|
-
apiKey;
|
|
259
|
-
failSilently;
|
|
260
|
-
piiConfig;
|
|
261
|
-
piiConfigNeedsHydration = false;
|
|
262
|
-
piiHydrationPromise;
|
|
263
|
-
currentState = {};
|
|
264
|
-
constructor(config = {}) {
|
|
265
|
-
this.apiUrl = (config.apiUrl ?? (typeof process !== "undefined" ? process.env?.SENTRIAL_API_URL : void 0) ?? DEFAULT_API_URL).replace(/\/$/, "");
|
|
266
|
-
this.apiKey = config.apiKey ?? (typeof process !== "undefined" ? process.env?.SENTRIAL_API_KEY : void 0);
|
|
267
|
-
this.failSilently = config.failSilently ?? true;
|
|
268
|
-
if (config.pii === true) {
|
|
269
|
-
this.piiConfig = { enabled: true };
|
|
270
|
-
this.piiConfigNeedsHydration = true;
|
|
271
|
-
} else if (config.pii && typeof config.pii === "object") {
|
|
272
|
-
this.piiConfig = config.pii;
|
|
273
|
-
this.piiConfigNeedsHydration = false;
|
|
274
|
-
}
|
|
308
|
+
// src/wrappers.ts
|
|
309
|
+
var _currentSessionId = createContextVar(null);
|
|
310
|
+
var _currentClient = createContextVar(null);
|
|
311
|
+
var _defaultClient = null;
|
|
312
|
+
function setSessionContext(sessionId, client) {
|
|
313
|
+
_currentSessionId.set(sessionId);
|
|
314
|
+
if (client) {
|
|
315
|
+
_currentClient.set(client);
|
|
275
316
|
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
}
|
|
306
|
-
if (response.ok) {
|
|
307
|
-
const data = await response.json();
|
|
308
|
-
if (data.config) {
|
|
309
|
-
this.piiConfig = {
|
|
310
|
-
enabled: data.config.enabled,
|
|
311
|
-
mode: data.config.mode,
|
|
312
|
-
fields: data.config.fields,
|
|
313
|
-
builtinPatterns: data.config.builtinPatterns,
|
|
314
|
-
customPatterns: (data.config.customPatterns || []).map(
|
|
315
|
-
(cp) => ({
|
|
316
|
-
pattern: new RegExp(cp.pattern, "g"),
|
|
317
|
-
label: cp.label
|
|
318
|
-
})
|
|
319
|
-
),
|
|
320
|
-
enhancedDetection: data.config.enhancedDetection
|
|
321
|
-
};
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
} catch {
|
|
325
|
-
}
|
|
326
|
-
this.piiConfigNeedsHydration = false;
|
|
327
|
-
})();
|
|
328
|
-
await this.piiHydrationPromise;
|
|
317
|
+
}
|
|
318
|
+
function clearSessionContext() {
|
|
319
|
+
_currentSessionId.set(null);
|
|
320
|
+
_currentClient.set(null);
|
|
321
|
+
}
|
|
322
|
+
function getSessionContext() {
|
|
323
|
+
return _currentSessionId.get();
|
|
324
|
+
}
|
|
325
|
+
function setDefaultClient(client) {
|
|
326
|
+
_defaultClient = client;
|
|
327
|
+
}
|
|
328
|
+
function _setSessionContextWithTokens(sessionId, client) {
|
|
329
|
+
const _sessionToken = _currentSessionId.set(sessionId);
|
|
330
|
+
const _clientToken = client ? _currentClient.set(client) : _currentClient.set(_currentClient.get());
|
|
331
|
+
return { _sessionToken, _clientToken };
|
|
332
|
+
}
|
|
333
|
+
function _restoreSessionContext(tokens) {
|
|
334
|
+
_currentSessionId.reset(tokens._sessionToken);
|
|
335
|
+
_currentClient.reset(tokens._clientToken);
|
|
336
|
+
}
|
|
337
|
+
function getTrackingClient() {
|
|
338
|
+
return _currentClient.get() ?? _defaultClient;
|
|
339
|
+
}
|
|
340
|
+
function wrapOpenAI(client, options = {}) {
|
|
341
|
+
const { trackWithoutSession = false } = options;
|
|
342
|
+
const chat = client.chat;
|
|
343
|
+
if (!chat?.completions?.create) {
|
|
344
|
+
console.warn("Sentrial: OpenAI client does not have chat.completions.create");
|
|
345
|
+
return client;
|
|
329
346
|
}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
if (
|
|
338
|
-
|
|
347
|
+
const originalCreate = chat.completions.create.bind(chat.completions);
|
|
348
|
+
chat.completions.create = async function(...args) {
|
|
349
|
+
const startTime = Date.now();
|
|
350
|
+
const params = args[0] ?? {};
|
|
351
|
+
const messages = params.messages ?? [];
|
|
352
|
+
const model = params.model ?? "unknown";
|
|
353
|
+
const isStreaming = params.stream === true;
|
|
354
|
+
if (isStreaming && !params.stream_options?.include_usage) {
|
|
355
|
+
args[0] = { ...params, stream_options: { ...params.stream_options, include_usage: true } };
|
|
339
356
|
}
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
357
|
+
try {
|
|
358
|
+
const response = await originalCreate(...args);
|
|
359
|
+
if (isStreaming) {
|
|
360
|
+
return wrapOpenAIStream(response, { startTime, messages, model, trackWithoutSession });
|
|
361
|
+
}
|
|
362
|
+
const durationMs = Date.now() - startTime;
|
|
363
|
+
const promptTokens = response.usage?.prompt_tokens ?? 0;
|
|
364
|
+
const completionTokens = response.usage?.completion_tokens ?? 0;
|
|
365
|
+
const totalTokens = response.usage?.total_tokens ?? 0;
|
|
366
|
+
let outputContent = "";
|
|
367
|
+
const toolCalls = [];
|
|
368
|
+
const msg = response.choices?.[0]?.message;
|
|
369
|
+
if (msg?.content) {
|
|
370
|
+
outputContent = msg.content;
|
|
371
|
+
}
|
|
372
|
+
if (msg?.tool_calls) {
|
|
373
|
+
for (const tc of msg.tool_calls) {
|
|
374
|
+
toolCalls.push({
|
|
375
|
+
name: tc.function?.name ?? "unknown",
|
|
376
|
+
arguments: tc.function?.arguments ?? "{}"
|
|
360
377
|
});
|
|
361
|
-
} finally {
|
|
362
|
-
clearTimeout(timeoutId);
|
|
363
|
-
}
|
|
364
|
-
if (RETRYABLE_STATUS_CODES.has(response.status) && attempt < MAX_RETRIES) {
|
|
365
|
-
await this.sleep(backoff);
|
|
366
|
-
backoff = Math.min(backoff * BACKOFF_MULTIPLIER, MAX_BACKOFF_MS);
|
|
367
|
-
continue;
|
|
368
|
-
}
|
|
369
|
-
if (!response.ok) {
|
|
370
|
-
const errorBody = await response.text();
|
|
371
|
-
let errorData = {};
|
|
372
|
-
try {
|
|
373
|
-
errorData = JSON.parse(errorBody);
|
|
374
|
-
} catch {
|
|
375
|
-
}
|
|
376
|
-
const error = new ApiError(
|
|
377
|
-
errorData.error?.message || `HTTP ${response.status}: ${response.statusText}`,
|
|
378
|
-
response.status,
|
|
379
|
-
errorData.error?.code
|
|
380
|
-
);
|
|
381
|
-
if (this.failSilently) {
|
|
382
|
-
console.warn(`Sentrial: Request failed (${method} ${url}):`, error.message);
|
|
383
|
-
return null;
|
|
384
|
-
}
|
|
385
|
-
throw error;
|
|
386
|
-
}
|
|
387
|
-
return await response.json();
|
|
388
|
-
} catch (error) {
|
|
389
|
-
if (error instanceof ApiError) {
|
|
390
|
-
throw error;
|
|
391
|
-
}
|
|
392
|
-
lastError = error instanceof Error ? error : new Error(String(error));
|
|
393
|
-
if (attempt < MAX_RETRIES) {
|
|
394
|
-
await this.sleep(backoff);
|
|
395
|
-
backoff = Math.min(backoff * BACKOFF_MULTIPLIER, MAX_BACKOFF_MS);
|
|
396
|
-
continue;
|
|
397
378
|
}
|
|
398
379
|
}
|
|
380
|
+
const cost = calculateOpenAICost({ model, inputTokens: promptTokens, outputTokens: completionTokens });
|
|
381
|
+
trackLLMCall({
|
|
382
|
+
provider: "openai",
|
|
383
|
+
model,
|
|
384
|
+
messages,
|
|
385
|
+
output: outputContent,
|
|
386
|
+
toolCalls,
|
|
387
|
+
promptTokens,
|
|
388
|
+
completionTokens,
|
|
389
|
+
totalTokens,
|
|
390
|
+
cost,
|
|
391
|
+
durationMs,
|
|
392
|
+
trackWithoutSession
|
|
393
|
+
});
|
|
394
|
+
return response;
|
|
395
|
+
} catch (error) {
|
|
396
|
+
const durationMs = Date.now() - startTime;
|
|
397
|
+
trackLLMError({
|
|
398
|
+
provider: "openai",
|
|
399
|
+
model,
|
|
400
|
+
messages,
|
|
401
|
+
error,
|
|
402
|
+
durationMs,
|
|
403
|
+
trackWithoutSession
|
|
404
|
+
});
|
|
405
|
+
throw error;
|
|
399
406
|
}
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
}
|
|
410
|
-
sleep(ms) {
|
|
411
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
407
|
+
};
|
|
408
|
+
return client;
|
|
409
|
+
}
|
|
410
|
+
function wrapAnthropic(client, options = {}) {
|
|
411
|
+
const { trackWithoutSession = false } = options;
|
|
412
|
+
const messages = client.messages;
|
|
413
|
+
if (!messages?.create) {
|
|
414
|
+
console.warn("Sentrial: Anthropic client does not have messages.create");
|
|
415
|
+
return client;
|
|
412
416
|
}
|
|
413
|
-
|
|
414
|
-
|
|
417
|
+
const originalCreate = messages.create.bind(messages);
|
|
418
|
+
messages.create = async function(...args) {
|
|
419
|
+
const startTime = Date.now();
|
|
420
|
+
const params = args[0] ?? {};
|
|
421
|
+
const inputMessages = params.messages ?? [];
|
|
422
|
+
const model = params.model ?? "unknown";
|
|
423
|
+
const system = params.system ?? "";
|
|
424
|
+
const isStreaming = params.stream === true;
|
|
425
|
+
try {
|
|
426
|
+
const response = await originalCreate(...args);
|
|
427
|
+
if (isStreaming) {
|
|
428
|
+
return wrapAnthropicStream(response, {
|
|
429
|
+
startTime,
|
|
430
|
+
messages: inputMessages,
|
|
431
|
+
model,
|
|
432
|
+
system,
|
|
433
|
+
trackWithoutSession
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
const durationMs = Date.now() - startTime;
|
|
437
|
+
const promptTokens = response.usage?.input_tokens ?? 0;
|
|
438
|
+
const completionTokens = response.usage?.output_tokens ?? 0;
|
|
439
|
+
const totalTokens = promptTokens + completionTokens;
|
|
440
|
+
let outputContent = "";
|
|
441
|
+
const toolCalls = [];
|
|
442
|
+
if (response.content) {
|
|
443
|
+
for (const block of response.content) {
|
|
444
|
+
if (block.type === "text") {
|
|
445
|
+
outputContent += block.text;
|
|
446
|
+
} else if (block.type === "tool_use") {
|
|
447
|
+
toolCalls.push({
|
|
448
|
+
name: block.name ?? "unknown",
|
|
449
|
+
arguments: JSON.stringify(block.input ?? {})
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
const cost = calculateAnthropicCost({ model, inputTokens: promptTokens, outputTokens: completionTokens });
|
|
455
|
+
const fullMessages = system ? [{ role: "system", content: system }, ...inputMessages] : inputMessages;
|
|
456
|
+
trackLLMCall({
|
|
457
|
+
provider: "anthropic",
|
|
458
|
+
model,
|
|
459
|
+
messages: fullMessages,
|
|
460
|
+
output: outputContent,
|
|
461
|
+
toolCalls,
|
|
462
|
+
promptTokens,
|
|
463
|
+
completionTokens,
|
|
464
|
+
totalTokens,
|
|
465
|
+
cost,
|
|
466
|
+
durationMs,
|
|
467
|
+
trackWithoutSession
|
|
468
|
+
});
|
|
469
|
+
return response;
|
|
470
|
+
} catch (error) {
|
|
471
|
+
const durationMs = Date.now() - startTime;
|
|
472
|
+
trackLLMError({
|
|
473
|
+
provider: "anthropic",
|
|
474
|
+
model,
|
|
475
|
+
messages: inputMessages,
|
|
476
|
+
error,
|
|
477
|
+
durationMs,
|
|
478
|
+
trackWithoutSession
|
|
479
|
+
});
|
|
480
|
+
throw error;
|
|
481
|
+
}
|
|
482
|
+
};
|
|
483
|
+
return client;
|
|
484
|
+
}
|
|
485
|
+
function wrapGoogle(model, options = {}) {
|
|
486
|
+
const { trackWithoutSession = false } = options;
|
|
487
|
+
const originalGenerate = model.generateContent;
|
|
488
|
+
if (!originalGenerate) {
|
|
489
|
+
console.warn("Sentrial: Google model does not have generateContent");
|
|
490
|
+
return model;
|
|
491
|
+
}
|
|
492
|
+
model.generateContent = async function(...args) {
|
|
493
|
+
const startTime = Date.now();
|
|
494
|
+
const contents = args[0];
|
|
495
|
+
const modelName = model.model ?? "gemini-unknown";
|
|
496
|
+
const messages = googleContentsToMessages(contents);
|
|
497
|
+
try {
|
|
498
|
+
const response = await originalGenerate.apply(model, args);
|
|
499
|
+
const durationMs = Date.now() - startTime;
|
|
500
|
+
let promptTokens = 0;
|
|
501
|
+
let completionTokens = 0;
|
|
502
|
+
const usageMeta = response.response?.usageMetadata ?? response.usageMetadata;
|
|
503
|
+
if (usageMeta) {
|
|
504
|
+
promptTokens = usageMeta.promptTokenCount ?? 0;
|
|
505
|
+
completionTokens = usageMeta.candidatesTokenCount ?? 0;
|
|
506
|
+
}
|
|
507
|
+
const totalTokens = promptTokens + completionTokens;
|
|
508
|
+
let outputContent = "";
|
|
509
|
+
try {
|
|
510
|
+
outputContent = response.response?.text?.() ?? response.text?.() ?? "";
|
|
511
|
+
} catch {
|
|
512
|
+
}
|
|
513
|
+
const cost = calculateGoogleCost({ model: modelName, inputTokens: promptTokens, outputTokens: completionTokens });
|
|
514
|
+
trackLLMCall({
|
|
515
|
+
provider: "google",
|
|
516
|
+
model: modelName,
|
|
517
|
+
messages,
|
|
518
|
+
output: outputContent,
|
|
519
|
+
promptTokens,
|
|
520
|
+
completionTokens,
|
|
521
|
+
totalTokens,
|
|
522
|
+
cost,
|
|
523
|
+
durationMs,
|
|
524
|
+
trackWithoutSession
|
|
525
|
+
});
|
|
526
|
+
return response;
|
|
527
|
+
} catch (error) {
|
|
528
|
+
const durationMs = Date.now() - startTime;
|
|
529
|
+
trackLLMError({
|
|
530
|
+
provider: "google",
|
|
531
|
+
model: modelName,
|
|
532
|
+
messages,
|
|
533
|
+
error,
|
|
534
|
+
durationMs,
|
|
535
|
+
trackWithoutSession
|
|
536
|
+
});
|
|
537
|
+
throw error;
|
|
538
|
+
}
|
|
539
|
+
};
|
|
540
|
+
return model;
|
|
541
|
+
}
|
|
542
|
+
function googleContentsToMessages(contents) {
|
|
543
|
+
if (typeof contents === "string") {
|
|
544
|
+
return [{ role: "user", content: contents }];
|
|
545
|
+
}
|
|
546
|
+
if (Array.isArray(contents)) {
|
|
547
|
+
return contents.map((item) => {
|
|
548
|
+
if (typeof item === "string") {
|
|
549
|
+
return { role: "user", content: item };
|
|
550
|
+
}
|
|
551
|
+
if (item && typeof item === "object") {
|
|
552
|
+
return { role: item.role ?? "user", content: String(item.content ?? item) };
|
|
553
|
+
}
|
|
554
|
+
return { role: "user", content: String(item) };
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
return [{ role: "user", content: String(contents) }];
|
|
558
|
+
}
|
|
559
|
+
function wrapLLM(client, provider) {
|
|
560
|
+
if (provider === "openai" || client.chat?.completions?.create) {
|
|
561
|
+
return wrapOpenAI(client);
|
|
562
|
+
}
|
|
563
|
+
if (provider === "anthropic" || client.messages?.create) {
|
|
564
|
+
return wrapAnthropic(client);
|
|
565
|
+
}
|
|
566
|
+
if (provider === "google" || client.generateContent) {
|
|
567
|
+
return wrapGoogle(client);
|
|
568
|
+
}
|
|
569
|
+
console.warn("Sentrial: Unknown LLM client type. No auto-tracking applied.");
|
|
570
|
+
return client;
|
|
571
|
+
}
|
|
572
|
+
function wrapOpenAIStream(stream, ctx) {
|
|
573
|
+
let fullContent = "";
|
|
574
|
+
let usage = null;
|
|
575
|
+
const toolCallMap = /* @__PURE__ */ new Map();
|
|
576
|
+
let tracked = false;
|
|
577
|
+
const originalIterator = stream[Symbol.asyncIterator]?.bind(stream);
|
|
578
|
+
if (!originalIterator) return stream;
|
|
579
|
+
const trackResult = () => {
|
|
580
|
+
if (tracked) return;
|
|
581
|
+
tracked = true;
|
|
582
|
+
const durationMs = Date.now() - ctx.startTime;
|
|
583
|
+
const promptTokens = usage?.prompt_tokens ?? 0;
|
|
584
|
+
const completionTokens = usage?.completion_tokens ?? 0;
|
|
585
|
+
const totalTokens = usage?.total_tokens ?? promptTokens + completionTokens;
|
|
586
|
+
const cost = calculateOpenAICost({ model: ctx.model, inputTokens: promptTokens, outputTokens: completionTokens });
|
|
587
|
+
trackLLMCall({
|
|
588
|
+
provider: "openai",
|
|
589
|
+
model: ctx.model,
|
|
590
|
+
messages: ctx.messages,
|
|
591
|
+
output: fullContent,
|
|
592
|
+
toolCalls: Array.from(toolCallMap.values()),
|
|
593
|
+
promptTokens,
|
|
594
|
+
completionTokens,
|
|
595
|
+
totalTokens,
|
|
596
|
+
cost,
|
|
597
|
+
durationMs,
|
|
598
|
+
trackWithoutSession: ctx.trackWithoutSession
|
|
599
|
+
});
|
|
600
|
+
};
|
|
601
|
+
return new Proxy(stream, {
|
|
602
|
+
get(target, prop, receiver) {
|
|
603
|
+
if (prop === Symbol.asyncIterator) {
|
|
604
|
+
return function() {
|
|
605
|
+
const iter = originalIterator();
|
|
606
|
+
return {
|
|
607
|
+
async next() {
|
|
608
|
+
const result = await iter.next();
|
|
609
|
+
if (!result.done) {
|
|
610
|
+
const chunk = result.value;
|
|
611
|
+
const delta = chunk.choices?.[0]?.delta;
|
|
612
|
+
if (delta?.content) fullContent += delta.content;
|
|
613
|
+
if (delta?.tool_calls) {
|
|
614
|
+
for (const tc of delta.tool_calls) {
|
|
615
|
+
const idx = tc.index ?? 0;
|
|
616
|
+
const existing = toolCallMap.get(idx) ?? { name: "", arguments: "" };
|
|
617
|
+
if (tc.function?.name) existing.name = tc.function.name;
|
|
618
|
+
if (tc.function?.arguments) existing.arguments += tc.function.arguments;
|
|
619
|
+
toolCallMap.set(idx, existing);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
if (chunk.usage) usage = chunk.usage;
|
|
623
|
+
} else {
|
|
624
|
+
trackResult();
|
|
625
|
+
}
|
|
626
|
+
return result;
|
|
627
|
+
},
|
|
628
|
+
async return(value) {
|
|
629
|
+
trackResult();
|
|
630
|
+
return iter.return?.(value) ?? { done: true, value: void 0 };
|
|
631
|
+
},
|
|
632
|
+
async throw(error) {
|
|
633
|
+
return iter.throw?.(error) ?? { done: true, value: void 0 };
|
|
634
|
+
}
|
|
635
|
+
};
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
return Reflect.get(target, prop, receiver);
|
|
639
|
+
}
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
function wrapAnthropicStream(stream, ctx) {
|
|
643
|
+
let fullContent = "";
|
|
644
|
+
let inputTokens = 0;
|
|
645
|
+
let outputTokens = 0;
|
|
646
|
+
const toolCallsById = /* @__PURE__ */ new Map();
|
|
647
|
+
let currentBlockIdx = -1;
|
|
648
|
+
let tracked = false;
|
|
649
|
+
const originalIterator = stream[Symbol.asyncIterator]?.bind(stream);
|
|
650
|
+
if (!originalIterator) return stream;
|
|
651
|
+
const trackResult = () => {
|
|
652
|
+
if (tracked) return;
|
|
653
|
+
tracked = true;
|
|
654
|
+
const durationMs = Date.now() - ctx.startTime;
|
|
655
|
+
const totalTokens = inputTokens + outputTokens;
|
|
656
|
+
const cost = calculateAnthropicCost({ model: ctx.model, inputTokens, outputTokens });
|
|
657
|
+
const fullMessages = ctx.system ? [{ role: "system", content: ctx.system }, ...ctx.messages] : ctx.messages;
|
|
658
|
+
trackLLMCall({
|
|
659
|
+
provider: "anthropic",
|
|
660
|
+
model: ctx.model,
|
|
661
|
+
messages: fullMessages,
|
|
662
|
+
output: fullContent,
|
|
663
|
+
toolCalls: Array.from(toolCallsById.values()),
|
|
664
|
+
promptTokens: inputTokens,
|
|
665
|
+
completionTokens: outputTokens,
|
|
666
|
+
totalTokens,
|
|
667
|
+
cost,
|
|
668
|
+
durationMs,
|
|
669
|
+
trackWithoutSession: ctx.trackWithoutSession
|
|
670
|
+
});
|
|
671
|
+
};
|
|
672
|
+
return new Proxy(stream, {
|
|
673
|
+
get(target, prop, receiver) {
|
|
674
|
+
if (prop === Symbol.asyncIterator) {
|
|
675
|
+
return function() {
|
|
676
|
+
const iter = originalIterator();
|
|
677
|
+
return {
|
|
678
|
+
async next() {
|
|
679
|
+
const result = await iter.next();
|
|
680
|
+
if (!result.done) {
|
|
681
|
+
const event = result.value;
|
|
682
|
+
if (event.type === "content_block_start") {
|
|
683
|
+
currentBlockIdx = event.index ?? currentBlockIdx + 1;
|
|
684
|
+
if (event.content_block?.type === "tool_use") {
|
|
685
|
+
toolCallsById.set(currentBlockIdx, {
|
|
686
|
+
name: event.content_block.name ?? "unknown",
|
|
687
|
+
arguments: ""
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
if (event.type === "content_block_delta") {
|
|
692
|
+
if (event.delta?.text) {
|
|
693
|
+
fullContent += event.delta.text;
|
|
694
|
+
}
|
|
695
|
+
if (event.delta?.type === "input_json_delta" && event.delta?.partial_json) {
|
|
696
|
+
const idx = event.index ?? currentBlockIdx;
|
|
697
|
+
const existing = toolCallsById.get(idx);
|
|
698
|
+
if (existing) {
|
|
699
|
+
existing.arguments += event.delta.partial_json;
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
if (event.type === "message_start" && event.message?.usage) {
|
|
704
|
+
inputTokens = event.message.usage.input_tokens ?? 0;
|
|
705
|
+
}
|
|
706
|
+
if (event.type === "message_delta" && event.usage) {
|
|
707
|
+
outputTokens = event.usage.output_tokens ?? 0;
|
|
708
|
+
}
|
|
709
|
+
} else {
|
|
710
|
+
trackResult();
|
|
711
|
+
}
|
|
712
|
+
return result;
|
|
713
|
+
},
|
|
714
|
+
async return(value) {
|
|
715
|
+
trackResult();
|
|
716
|
+
return iter.return?.(value) ?? { done: true, value: void 0 };
|
|
717
|
+
},
|
|
718
|
+
async throw(error) {
|
|
719
|
+
return iter.throw?.(error) ?? { done: true, value: void 0 };
|
|
720
|
+
}
|
|
721
|
+
};
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
return Reflect.get(target, prop, receiver);
|
|
725
|
+
}
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
function trackLLMCall(params) {
|
|
729
|
+
const client = getTrackingClient();
|
|
730
|
+
if (!client) return;
|
|
731
|
+
const sessionId = _currentSessionId.get();
|
|
732
|
+
if (!sessionId && !params.trackWithoutSession) {
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
const toolOutput = {
|
|
736
|
+
content: params.output,
|
|
737
|
+
tokens: {
|
|
738
|
+
prompt: params.promptTokens,
|
|
739
|
+
completion: params.completionTokens,
|
|
740
|
+
total: params.totalTokens
|
|
741
|
+
},
|
|
742
|
+
cost_usd: params.cost
|
|
743
|
+
};
|
|
744
|
+
if (params.toolCalls && params.toolCalls.length > 0) {
|
|
745
|
+
toolOutput.tool_calls = params.toolCalls;
|
|
746
|
+
}
|
|
747
|
+
if (sessionId) {
|
|
748
|
+
client.trackToolCall({
|
|
749
|
+
sessionId,
|
|
750
|
+
toolName: `llm:${params.provider}:${params.model}`,
|
|
751
|
+
toolInput: {
|
|
752
|
+
messages: params.messages,
|
|
753
|
+
model: params.model,
|
|
754
|
+
provider: params.provider
|
|
755
|
+
},
|
|
756
|
+
toolOutput,
|
|
757
|
+
reasoning: `LLM call to ${params.provider} ${params.model}`,
|
|
758
|
+
estimatedCost: params.cost,
|
|
759
|
+
tokenCount: params.totalTokens,
|
|
760
|
+
metadata: {
|
|
761
|
+
provider: params.provider,
|
|
762
|
+
model: params.model,
|
|
763
|
+
duration_ms: params.durationMs,
|
|
764
|
+
prompt_tokens: params.promptTokens,
|
|
765
|
+
completion_tokens: params.completionTokens
|
|
766
|
+
}
|
|
767
|
+
}).catch((err) => {
|
|
768
|
+
console.warn("Sentrial: Failed to track LLM call:", err.message);
|
|
769
|
+
});
|
|
770
|
+
} else if (params.trackWithoutSession) {
|
|
771
|
+
client.createSession({
|
|
772
|
+
name: `LLM: ${params.provider}/${params.model}`,
|
|
773
|
+
agentName: `${params.provider}-wrapper`,
|
|
774
|
+
userId: "anonymous"
|
|
775
|
+
}).then((sid) => {
|
|
776
|
+
if (!sid) return;
|
|
777
|
+
return client.trackToolCall({
|
|
778
|
+
sessionId: sid,
|
|
779
|
+
toolName: `llm:${params.provider}:${params.model}`,
|
|
780
|
+
toolInput: {
|
|
781
|
+
messages: params.messages,
|
|
782
|
+
model: params.model,
|
|
783
|
+
provider: params.provider
|
|
784
|
+
},
|
|
785
|
+
toolOutput,
|
|
786
|
+
estimatedCost: params.cost,
|
|
787
|
+
tokenCount: params.totalTokens,
|
|
788
|
+
metadata: {
|
|
789
|
+
provider: params.provider,
|
|
790
|
+
model: params.model,
|
|
791
|
+
duration_ms: params.durationMs
|
|
792
|
+
}
|
|
793
|
+
}).then(() => {
|
|
794
|
+
return client.completeSession({
|
|
795
|
+
sessionId: sid,
|
|
796
|
+
success: true,
|
|
797
|
+
estimatedCost: params.cost,
|
|
798
|
+
promptTokens: params.promptTokens,
|
|
799
|
+
completionTokens: params.completionTokens,
|
|
800
|
+
totalTokens: params.totalTokens,
|
|
801
|
+
durationMs: params.durationMs
|
|
802
|
+
});
|
|
803
|
+
});
|
|
804
|
+
}).catch((err) => {
|
|
805
|
+
console.warn("Sentrial: Failed to track standalone LLM call:", err.message);
|
|
806
|
+
});
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
function trackLLMError(params) {
|
|
810
|
+
const client = getTrackingClient();
|
|
811
|
+
if (!client) return;
|
|
812
|
+
const sessionId = _currentSessionId.get();
|
|
813
|
+
if (!sessionId && !params.trackWithoutSession) {
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
816
|
+
if (!sessionId) return;
|
|
817
|
+
client.trackError({
|
|
818
|
+
sessionId,
|
|
819
|
+
errorMessage: params.error.message,
|
|
820
|
+
errorType: params.error.name,
|
|
821
|
+
toolName: `llm:${params.provider}:${params.model}`,
|
|
822
|
+
metadata: {
|
|
823
|
+
provider: params.provider,
|
|
824
|
+
model: params.model,
|
|
825
|
+
duration_ms: params.durationMs
|
|
826
|
+
}
|
|
827
|
+
}).catch((err) => {
|
|
828
|
+
console.warn("Sentrial: Failed to track LLM error:", err.message);
|
|
829
|
+
});
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
// src/batcher.ts
|
|
833
|
+
var EventBatcher = class {
|
|
834
|
+
queue = [];
|
|
835
|
+
flushIntervalMs;
|
|
836
|
+
flushThreshold;
|
|
837
|
+
maxQueueSize;
|
|
838
|
+
timer = null;
|
|
839
|
+
sendFn;
|
|
840
|
+
flushing = false;
|
|
841
|
+
shutdownCalled = false;
|
|
842
|
+
exitHandler;
|
|
843
|
+
constructor(sendFn, config = {}) {
|
|
844
|
+
this.sendFn = sendFn;
|
|
845
|
+
this.flushIntervalMs = config.flushIntervalMs ?? 1e3;
|
|
846
|
+
this.flushThreshold = config.flushThreshold ?? 10;
|
|
847
|
+
this.maxQueueSize = config.maxQueueSize ?? 1e3;
|
|
848
|
+
this.timer = setInterval(() => {
|
|
849
|
+
void this.flush();
|
|
850
|
+
}, this.flushIntervalMs);
|
|
851
|
+
if (this.timer && typeof this.timer === "object" && "unref" in this.timer) {
|
|
852
|
+
this.timer.unref();
|
|
853
|
+
}
|
|
854
|
+
this.exitHandler = () => {
|
|
855
|
+
void this.shutdown();
|
|
856
|
+
};
|
|
857
|
+
if (typeof process !== "undefined" && process.on) {
|
|
858
|
+
process.on("beforeExit", this.exitHandler);
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
/**
|
|
862
|
+
* Enqueue an event for batched delivery.
|
|
863
|
+
*
|
|
864
|
+
* If the queue hits `flushThreshold`, an automatic flush is triggered.
|
|
865
|
+
* If the queue is full (`maxQueueSize`), the oldest event is dropped.
|
|
866
|
+
*/
|
|
867
|
+
enqueue(method, url, body) {
|
|
868
|
+
if (this.shutdownCalled) return;
|
|
869
|
+
if (this.queue.length >= this.maxQueueSize) {
|
|
870
|
+
this.queue.shift();
|
|
871
|
+
if (typeof console !== "undefined") {
|
|
872
|
+
console.warn(
|
|
873
|
+
`Sentrial: Event queue full (${this.maxQueueSize}), dropping oldest event`
|
|
874
|
+
);
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
this.queue.push({ method, url, body });
|
|
878
|
+
if (this.queue.length >= this.flushThreshold) {
|
|
879
|
+
void this.flush();
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
/**
|
|
883
|
+
* Flush all queued events to the API.
|
|
884
|
+
*
|
|
885
|
+
* Drains the queue and fires all requests in parallel. Safe to call
|
|
886
|
+
* concurrently — only one flush runs at a time.
|
|
887
|
+
*/
|
|
888
|
+
async flush() {
|
|
889
|
+
if (this.flushing || this.queue.length === 0) return;
|
|
890
|
+
this.flushing = true;
|
|
891
|
+
const batch = this.queue.splice(0, this.queue.length);
|
|
892
|
+
try {
|
|
893
|
+
await Promise.all(
|
|
894
|
+
batch.map(
|
|
895
|
+
(event) => this.sendFn(event.method, event.url, event.body).catch((err) => {
|
|
896
|
+
if (typeof console !== "undefined") {
|
|
897
|
+
console.warn("Sentrial: Batched event failed:", err);
|
|
898
|
+
}
|
|
899
|
+
})
|
|
900
|
+
)
|
|
901
|
+
);
|
|
902
|
+
} finally {
|
|
903
|
+
this.flushing = false;
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
/**
|
|
907
|
+
* Stop the batcher: clear the timer, flush remaining events, remove exit handler.
|
|
908
|
+
*/
|
|
909
|
+
async shutdown() {
|
|
910
|
+
if (this.shutdownCalled) return;
|
|
911
|
+
this.shutdownCalled = true;
|
|
912
|
+
if (this.timer !== null) {
|
|
913
|
+
clearInterval(this.timer);
|
|
914
|
+
this.timer = null;
|
|
915
|
+
}
|
|
916
|
+
if (typeof process !== "undefined" && process.removeListener) {
|
|
917
|
+
process.removeListener("beforeExit", this.exitHandler);
|
|
918
|
+
}
|
|
919
|
+
this.flushing = false;
|
|
920
|
+
await this.flush();
|
|
921
|
+
}
|
|
922
|
+
/** Number of events currently queued. */
|
|
923
|
+
get size() {
|
|
924
|
+
return this.queue.length;
|
|
925
|
+
}
|
|
926
|
+
};
|
|
927
|
+
|
|
928
|
+
// src/types.ts
|
|
929
|
+
var EventType = /* @__PURE__ */ ((EventType2) => {
|
|
930
|
+
EventType2["TOOL_CALL"] = "tool_call";
|
|
931
|
+
EventType2["LLM_DECISION"] = "llm_decision";
|
|
932
|
+
EventType2["STATE_CHANGE"] = "state_change";
|
|
933
|
+
EventType2["ERROR"] = "error";
|
|
934
|
+
return EventType2;
|
|
935
|
+
})(EventType || {});
|
|
936
|
+
|
|
937
|
+
// src/client.ts
|
|
938
|
+
var DEFAULT_API_URL = "https://api.sentrial.com";
|
|
939
|
+
var MAX_RETRIES = 3;
|
|
940
|
+
var INITIAL_BACKOFF_MS = 500;
|
|
941
|
+
var MAX_BACKOFF_MS = 8e3;
|
|
942
|
+
var BACKOFF_MULTIPLIER = 2;
|
|
943
|
+
var RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([408, 429, 500, 502, 503, 504]);
|
|
944
|
+
var REQUEST_TIMEOUT_MS = 1e4;
|
|
945
|
+
var SentrialClient = class {
|
|
946
|
+
apiUrl;
|
|
947
|
+
apiKey;
|
|
948
|
+
failSilently;
|
|
949
|
+
piiConfig;
|
|
950
|
+
piiConfigNeedsHydration = false;
|
|
951
|
+
piiHydrationPromise;
|
|
952
|
+
_stateVar = createContextVar({});
|
|
953
|
+
batcher;
|
|
954
|
+
/** Per-session cost/token accumulator — populated by trackToolCall/trackDecision */
|
|
955
|
+
sessionAccumulators = /* @__PURE__ */ new Map();
|
|
956
|
+
get currentState() {
|
|
957
|
+
return this._stateVar.get();
|
|
958
|
+
}
|
|
959
|
+
set currentState(value) {
|
|
960
|
+
this._stateVar.set(value);
|
|
961
|
+
}
|
|
962
|
+
constructor(config = {}) {
|
|
963
|
+
this.apiUrl = (config.apiUrl ?? (typeof process !== "undefined" ? process.env?.SENTRIAL_API_URL : void 0) ?? DEFAULT_API_URL).replace(/\/$/, "");
|
|
964
|
+
this.apiKey = config.apiKey ?? (typeof process !== "undefined" ? process.env?.SENTRIAL_API_KEY : void 0);
|
|
965
|
+
this.failSilently = config.failSilently ?? true;
|
|
966
|
+
if (config.pii === true) {
|
|
967
|
+
this.piiConfig = { enabled: true };
|
|
968
|
+
this.piiConfigNeedsHydration = true;
|
|
969
|
+
} else if (config.pii && typeof config.pii === "object") {
|
|
970
|
+
this.piiConfig = config.pii;
|
|
971
|
+
this.piiConfigNeedsHydration = false;
|
|
972
|
+
}
|
|
973
|
+
if (config.batching?.enabled) {
|
|
974
|
+
this.batcher = new EventBatcher(
|
|
975
|
+
(method, url, body) => this.safeRequest(method, url, body),
|
|
976
|
+
config.batching
|
|
977
|
+
);
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
/**
|
|
981
|
+
* Fetch the organization's PII config from the server.
|
|
982
|
+
*
|
|
983
|
+
* Called lazily on the first request when `pii: true` was passed to the constructor.
|
|
984
|
+
* Uses a single shared promise so concurrent requests don't trigger duplicate fetches.
|
|
985
|
+
*/
|
|
986
|
+
async hydratePiiConfig() {
|
|
987
|
+
if (!this.piiConfigNeedsHydration) return;
|
|
988
|
+
if (this.piiHydrationPromise) {
|
|
989
|
+
await this.piiHydrationPromise;
|
|
990
|
+
return;
|
|
991
|
+
}
|
|
992
|
+
this.piiHydrationPromise = (async () => {
|
|
993
|
+
try {
|
|
994
|
+
const headers = {};
|
|
995
|
+
if (this.apiKey) {
|
|
996
|
+
headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
997
|
+
}
|
|
998
|
+
const controller = new AbortController();
|
|
999
|
+
const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
1000
|
+
let response;
|
|
1001
|
+
try {
|
|
1002
|
+
response = await fetch(`${this.apiUrl}/api/sdk/pii-config`, {
|
|
1003
|
+
method: "GET",
|
|
1004
|
+
headers,
|
|
1005
|
+
signal: controller.signal
|
|
1006
|
+
});
|
|
1007
|
+
} finally {
|
|
1008
|
+
clearTimeout(timeoutId);
|
|
1009
|
+
}
|
|
1010
|
+
if (response.ok) {
|
|
1011
|
+
const data = await response.json();
|
|
1012
|
+
if (data.config) {
|
|
1013
|
+
this.piiConfig = {
|
|
1014
|
+
enabled: data.config.enabled,
|
|
1015
|
+
mode: data.config.mode,
|
|
1016
|
+
fields: data.config.fields,
|
|
1017
|
+
builtinPatterns: data.config.builtinPatterns,
|
|
1018
|
+
customPatterns: (data.config.customPatterns || []).map(
|
|
1019
|
+
(cp) => ({
|
|
1020
|
+
pattern: new RegExp(cp.pattern, "g"),
|
|
1021
|
+
label: cp.label
|
|
1022
|
+
})
|
|
1023
|
+
),
|
|
1024
|
+
enhancedDetection: data.config.enhancedDetection
|
|
1025
|
+
};
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
} catch {
|
|
1029
|
+
}
|
|
1030
|
+
this.piiConfigNeedsHydration = false;
|
|
1031
|
+
})();
|
|
1032
|
+
await this.piiHydrationPromise;
|
|
1033
|
+
}
|
|
1034
|
+
/**
|
|
1035
|
+
* Make an HTTP request with retry logic and exponential backoff.
|
|
1036
|
+
*
|
|
1037
|
+
* Retries on transient failures (network errors, timeouts, 429/5xx).
|
|
1038
|
+
* Up to MAX_RETRIES attempts with exponential backoff.
|
|
1039
|
+
*/
|
|
1040
|
+
async safeRequest(method, url, body) {
|
|
1041
|
+
if (this.piiConfigNeedsHydration) {
|
|
1042
|
+
await this.hydratePiiConfig();
|
|
1043
|
+
}
|
|
1044
|
+
let lastError;
|
|
1045
|
+
let backoff = INITIAL_BACKOFF_MS;
|
|
1046
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
1047
|
+
try {
|
|
1048
|
+
const headers = {
|
|
1049
|
+
"Content-Type": "application/json"
|
|
1050
|
+
};
|
|
1051
|
+
if (this.apiKey) {
|
|
1052
|
+
headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
1053
|
+
}
|
|
1054
|
+
const finalBody = this.piiConfig && body && typeof body === "object" ? redactPayload(body, this.piiConfig) : body;
|
|
1055
|
+
const controller = new AbortController();
|
|
1056
|
+
const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
1057
|
+
let response;
|
|
1058
|
+
try {
|
|
1059
|
+
response = await fetch(url, {
|
|
1060
|
+
method,
|
|
1061
|
+
headers,
|
|
1062
|
+
body: finalBody ? JSON.stringify(finalBody) : void 0,
|
|
1063
|
+
signal: controller.signal
|
|
1064
|
+
});
|
|
1065
|
+
} finally {
|
|
1066
|
+
clearTimeout(timeoutId);
|
|
1067
|
+
}
|
|
1068
|
+
if (RETRYABLE_STATUS_CODES.has(response.status) && attempt < MAX_RETRIES) {
|
|
1069
|
+
await this.sleep(backoff);
|
|
1070
|
+
backoff = Math.min(backoff * BACKOFF_MULTIPLIER, MAX_BACKOFF_MS);
|
|
1071
|
+
continue;
|
|
1072
|
+
}
|
|
1073
|
+
if (!response.ok) {
|
|
1074
|
+
const errorBody = await response.text();
|
|
1075
|
+
let errorData = {};
|
|
1076
|
+
try {
|
|
1077
|
+
errorData = JSON.parse(errorBody);
|
|
1078
|
+
} catch {
|
|
1079
|
+
}
|
|
1080
|
+
const error = new ApiError(
|
|
1081
|
+
errorData.error?.message || `HTTP ${response.status}: ${response.statusText}`,
|
|
1082
|
+
response.status,
|
|
1083
|
+
errorData.error?.code
|
|
1084
|
+
);
|
|
1085
|
+
if (this.failSilently) {
|
|
1086
|
+
console.warn(`Sentrial: Request failed (${method} ${url}):`, error.message);
|
|
1087
|
+
return null;
|
|
1088
|
+
}
|
|
1089
|
+
throw error;
|
|
1090
|
+
}
|
|
1091
|
+
return await response.json();
|
|
1092
|
+
} catch (error) {
|
|
1093
|
+
if (error instanceof ApiError) {
|
|
1094
|
+
throw error;
|
|
1095
|
+
}
|
|
1096
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
1097
|
+
if (attempt < MAX_RETRIES) {
|
|
1098
|
+
await this.sleep(backoff);
|
|
1099
|
+
backoff = Math.min(backoff * BACKOFF_MULTIPLIER, MAX_BACKOFF_MS);
|
|
1100
|
+
continue;
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
const networkError = new NetworkError(
|
|
1105
|
+
lastError?.message ?? "Unknown network error",
|
|
1106
|
+
lastError
|
|
1107
|
+
);
|
|
1108
|
+
if (this.failSilently) {
|
|
1109
|
+
console.warn(`Sentrial: Request failed after ${MAX_RETRIES + 1} attempts (${method} ${url}):`, networkError.message);
|
|
1110
|
+
return null;
|
|
1111
|
+
}
|
|
1112
|
+
throw networkError;
|
|
1113
|
+
}
|
|
1114
|
+
sleep(ms) {
|
|
1115
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1116
|
+
}
|
|
1117
|
+
accumulate(sessionId, cost, tokenCount, toolOutput) {
|
|
1118
|
+
let acc = this.sessionAccumulators.get(sessionId);
|
|
1119
|
+
if (!acc) {
|
|
1120
|
+
acc = { cost: 0, promptTokens: 0, completionTokens: 0, totalTokens: 0 };
|
|
1121
|
+
this.sessionAccumulators.set(sessionId, acc);
|
|
1122
|
+
}
|
|
1123
|
+
if (cost != null) acc.cost += cost;
|
|
1124
|
+
if (tokenCount != null) acc.totalTokens += tokenCount;
|
|
1125
|
+
const rawTokens = toolOutput?.tokens;
|
|
1126
|
+
if (rawTokens && typeof rawTokens === "object" && !Array.isArray(rawTokens)) {
|
|
1127
|
+
const tokens = rawTokens;
|
|
1128
|
+
if (typeof tokens.prompt === "number") acc.promptTokens += tokens.prompt;
|
|
1129
|
+
if (typeof tokens.completion === "number") acc.completionTokens += tokens.completion;
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
/**
|
|
1133
|
+
* Create a new session
|
|
415
1134
|
*
|
|
416
1135
|
* @param params - Session creation parameters
|
|
417
1136
|
* @returns Session ID, or null if the request failed and failSilently is true
|
|
@@ -444,6 +1163,7 @@ var SentrialClient = class {
|
|
|
444
1163
|
* @returns Event data
|
|
445
1164
|
*/
|
|
446
1165
|
async trackToolCall(params) {
|
|
1166
|
+
this.accumulate(params.sessionId, params.estimatedCost, params.tokenCount, params.toolOutput);
|
|
447
1167
|
const stateBefore = { ...this.currentState };
|
|
448
1168
|
this.currentState[`${params.toolName}_result`] = params.toolOutput;
|
|
449
1169
|
const payload = {
|
|
@@ -462,6 +1182,10 @@ var SentrialClient = class {
|
|
|
462
1182
|
if (params.traceId !== void 0) payload.traceId = params.traceId;
|
|
463
1183
|
if (params.spanId !== void 0) payload.spanId = params.spanId;
|
|
464
1184
|
if (params.metadata !== void 0) payload.metadata = params.metadata;
|
|
1185
|
+
if (this.batcher) {
|
|
1186
|
+
this.batcher.enqueue("POST", `${this.apiUrl}/api/sdk/events`, payload);
|
|
1187
|
+
return null;
|
|
1188
|
+
}
|
|
465
1189
|
return this.safeRequest("POST", `${this.apiUrl}/api/sdk/events`, payload);
|
|
466
1190
|
}
|
|
467
1191
|
/**
|
|
@@ -471,6 +1195,7 @@ var SentrialClient = class {
|
|
|
471
1195
|
* @returns Event data
|
|
472
1196
|
*/
|
|
473
1197
|
async trackDecision(params) {
|
|
1198
|
+
this.accumulate(params.sessionId, params.estimatedCost, params.tokenCount);
|
|
474
1199
|
const stateBefore = { ...this.currentState };
|
|
475
1200
|
const payload = {
|
|
476
1201
|
sessionId: params.sessionId,
|
|
@@ -486,6 +1211,10 @@ var SentrialClient = class {
|
|
|
486
1211
|
if (params.traceId !== void 0) payload.traceId = params.traceId;
|
|
487
1212
|
if (params.spanId !== void 0) payload.spanId = params.spanId;
|
|
488
1213
|
if (params.metadata !== void 0) payload.metadata = params.metadata;
|
|
1214
|
+
if (this.batcher) {
|
|
1215
|
+
this.batcher.enqueue("POST", `${this.apiUrl}/api/sdk/events`, payload);
|
|
1216
|
+
return null;
|
|
1217
|
+
}
|
|
489
1218
|
return this.safeRequest("POST", `${this.apiUrl}/api/sdk/events`, payload);
|
|
490
1219
|
}
|
|
491
1220
|
/**
|
|
@@ -512,6 +1241,10 @@ var SentrialClient = class {
|
|
|
512
1241
|
if (params.traceId !== void 0) payload.traceId = params.traceId;
|
|
513
1242
|
if (params.spanId !== void 0) payload.spanId = params.spanId;
|
|
514
1243
|
if (params.metadata !== void 0) payload.metadata = params.metadata;
|
|
1244
|
+
if (this.batcher) {
|
|
1245
|
+
this.batcher.enqueue("POST", `${this.apiUrl}/api/sdk/events`, payload);
|
|
1246
|
+
return null;
|
|
1247
|
+
}
|
|
515
1248
|
return this.safeRequest("POST", `${this.apiUrl}/api/sdk/events`, payload);
|
|
516
1249
|
}
|
|
517
1250
|
/**
|
|
@@ -557,6 +1290,10 @@ var SentrialClient = class {
|
|
|
557
1290
|
if (params.metadata) {
|
|
558
1291
|
payload.metadata = params.metadata;
|
|
559
1292
|
}
|
|
1293
|
+
if (this.batcher) {
|
|
1294
|
+
this.batcher.enqueue("POST", `${this.apiUrl}/api/sdk/events`, payload);
|
|
1295
|
+
return null;
|
|
1296
|
+
}
|
|
560
1297
|
return this.safeRequest("POST", `${this.apiUrl}/api/sdk/events`, payload);
|
|
561
1298
|
}
|
|
562
1299
|
/**
|
|
@@ -586,6 +1323,17 @@ var SentrialClient = class {
|
|
|
586
1323
|
* ```
|
|
587
1324
|
*/
|
|
588
1325
|
async completeSession(params) {
|
|
1326
|
+
if (this.batcher) {
|
|
1327
|
+
await this.batcher.flush();
|
|
1328
|
+
}
|
|
1329
|
+
const acc = this.sessionAccumulators.get(params.sessionId);
|
|
1330
|
+
if (acc) {
|
|
1331
|
+
if (params.estimatedCost === void 0 && acc.cost > 0) params = { ...params, estimatedCost: acc.cost };
|
|
1332
|
+
if (params.promptTokens === void 0 && acc.promptTokens > 0) params = { ...params, promptTokens: acc.promptTokens };
|
|
1333
|
+
if (params.completionTokens === void 0 && acc.completionTokens > 0) params = { ...params, completionTokens: acc.completionTokens };
|
|
1334
|
+
if (params.totalTokens === void 0 && acc.totalTokens > 0) params = { ...params, totalTokens: acc.totalTokens };
|
|
1335
|
+
this.sessionAccumulators.delete(params.sessionId);
|
|
1336
|
+
}
|
|
589
1337
|
const payload = {
|
|
590
1338
|
status: params.success !== false ? "completed" : "failed",
|
|
591
1339
|
success: params.success ?? true
|
|
@@ -606,6 +1354,27 @@ var SentrialClient = class {
|
|
|
606
1354
|
payload
|
|
607
1355
|
);
|
|
608
1356
|
}
|
|
1357
|
+
/**
|
|
1358
|
+
* Flush any queued events immediately.
|
|
1359
|
+
*
|
|
1360
|
+
* No-op if batching is not enabled.
|
|
1361
|
+
*/
|
|
1362
|
+
async flush() {
|
|
1363
|
+
if (this.batcher) {
|
|
1364
|
+
await this.batcher.flush();
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
/**
|
|
1368
|
+
* Shut down the event batcher, flushing remaining events.
|
|
1369
|
+
*
|
|
1370
|
+
* Call this before your process exits for a clean shutdown.
|
|
1371
|
+
* No-op if batching is not enabled.
|
|
1372
|
+
*/
|
|
1373
|
+
async shutdown() {
|
|
1374
|
+
if (this.batcher) {
|
|
1375
|
+
await this.batcher.shutdown();
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
609
1378
|
/**
|
|
610
1379
|
* Begin tracking an interaction (simplified API)
|
|
611
1380
|
*
|
|
@@ -642,13 +1411,18 @@ var SentrialClient = class {
|
|
|
642
1411
|
if (params.input) {
|
|
643
1412
|
this.currentState.input = params.input;
|
|
644
1413
|
}
|
|
1414
|
+
let sessionTokens;
|
|
1415
|
+
if (sessionId) {
|
|
1416
|
+
sessionTokens = _setSessionContextWithTokens(sessionId, this);
|
|
1417
|
+
}
|
|
645
1418
|
return new Interaction({
|
|
646
1419
|
client: this,
|
|
647
1420
|
sessionId,
|
|
648
1421
|
eventId,
|
|
649
1422
|
userId: params.userId,
|
|
650
1423
|
event: params.event,
|
|
651
|
-
userInput: params.input
|
|
1424
|
+
userInput: params.input,
|
|
1425
|
+
sessionTokens
|
|
652
1426
|
});
|
|
653
1427
|
}
|
|
654
1428
|
// Cost calculation static methods for convenience
|
|
@@ -665,12 +1439,15 @@ var Interaction = class {
|
|
|
665
1439
|
userId;
|
|
666
1440
|
/** Event name for this interaction */
|
|
667
1441
|
event;
|
|
1442
|
+
startTime = Date.now();
|
|
668
1443
|
finished = false;
|
|
669
1444
|
success = true;
|
|
670
1445
|
failureReason;
|
|
671
1446
|
output;
|
|
672
1447
|
userInput;
|
|
673
1448
|
degraded;
|
|
1449
|
+
/** Context tokens for restoring previous session context on finish() */
|
|
1450
|
+
sessionTokens;
|
|
674
1451
|
constructor(config) {
|
|
675
1452
|
this.client = config.client;
|
|
676
1453
|
this.sessionId = config.sessionId;
|
|
@@ -679,6 +1456,7 @@ var Interaction = class {
|
|
|
679
1456
|
this.event = config.event;
|
|
680
1457
|
this.userInput = config.userInput;
|
|
681
1458
|
this.degraded = config.sessionId === null;
|
|
1459
|
+
this.sessionTokens = config.sessionTokens;
|
|
682
1460
|
}
|
|
683
1461
|
/**
|
|
684
1462
|
* Set the output for this interaction
|
|
@@ -714,18 +1492,24 @@ var Interaction = class {
|
|
|
714
1492
|
}
|
|
715
1493
|
this.finished = true;
|
|
716
1494
|
const finalOutput = params.output ?? this.output;
|
|
717
|
-
|
|
1495
|
+
const result = await this.client.completeSession({
|
|
718
1496
|
sessionId: this.sessionId,
|
|
719
1497
|
success: params.success ?? this.success,
|
|
720
1498
|
failureReason: params.failureReason ?? this.failureReason,
|
|
721
1499
|
estimatedCost: params.estimatedCost,
|
|
722
1500
|
customMetrics: params.customMetrics,
|
|
1501
|
+
durationMs: params.durationMs ?? Date.now() - this.startTime,
|
|
723
1502
|
promptTokens: params.promptTokens,
|
|
724
1503
|
completionTokens: params.completionTokens,
|
|
725
1504
|
totalTokens: params.totalTokens,
|
|
726
1505
|
userInput: this.userInput,
|
|
727
1506
|
assistantOutput: finalOutput
|
|
728
1507
|
});
|
|
1508
|
+
if (this.sessionTokens) {
|
|
1509
|
+
_restoreSessionContext(this.sessionTokens);
|
|
1510
|
+
this.sessionTokens = void 0;
|
|
1511
|
+
}
|
|
1512
|
+
return result;
|
|
729
1513
|
}
|
|
730
1514
|
/**
|
|
731
1515
|
* Track a tool call within this interaction
|
|
@@ -785,16 +1569,24 @@ function configure(config) {
|
|
|
785
1569
|
function begin(params) {
|
|
786
1570
|
return getClient().begin(params);
|
|
787
1571
|
}
|
|
1572
|
+
async function flush() {
|
|
1573
|
+
if (defaultClient) await defaultClient.flush();
|
|
1574
|
+
}
|
|
1575
|
+
async function shutdown() {
|
|
1576
|
+
if (defaultClient) await defaultClient.shutdown();
|
|
1577
|
+
}
|
|
788
1578
|
var sentrial = {
|
|
789
1579
|
configure,
|
|
790
|
-
begin
|
|
1580
|
+
begin,
|
|
1581
|
+
flush,
|
|
1582
|
+
shutdown
|
|
791
1583
|
};
|
|
792
1584
|
|
|
793
1585
|
// src/vercel.ts
|
|
794
|
-
var
|
|
1586
|
+
var _defaultClient2 = null;
|
|
795
1587
|
var _globalConfig = {};
|
|
796
1588
|
function configureVercel(config) {
|
|
797
|
-
|
|
1589
|
+
_defaultClient2 = new SentrialClient({
|
|
798
1590
|
apiKey: config.apiKey,
|
|
799
1591
|
apiUrl: config.apiUrl,
|
|
800
1592
|
failSilently: config.failSilently ?? true
|
|
@@ -806,19 +1598,20 @@ function configureVercel(config) {
|
|
|
806
1598
|
};
|
|
807
1599
|
}
|
|
808
1600
|
function getClient2() {
|
|
809
|
-
if (!
|
|
810
|
-
|
|
1601
|
+
if (!_defaultClient2) {
|
|
1602
|
+
_defaultClient2 = new SentrialClient();
|
|
811
1603
|
}
|
|
812
|
-
return
|
|
1604
|
+
return _defaultClient2;
|
|
813
1605
|
}
|
|
814
1606
|
function extractModelInfo(model) {
|
|
815
1607
|
const modelId = model.modelId || model.id || "unknown";
|
|
816
|
-
const
|
|
1608
|
+
const rawProvider = model.provider || "";
|
|
1609
|
+
const provider = rawProvider.split(".")[0] || guessProvider(modelId);
|
|
817
1610
|
return { modelId, provider };
|
|
818
1611
|
}
|
|
819
1612
|
function guessProvider(modelId) {
|
|
820
1613
|
const id = modelId.toLowerCase();
|
|
821
|
-
if (id.includes("gpt") || id.
|
|
1614
|
+
if (id.includes("gpt") || id.startsWith("o1") || id.startsWith("o3") || id.startsWith("o4") || id.startsWith("chatgpt")) return "openai";
|
|
822
1615
|
if (id.includes("claude")) return "anthropic";
|
|
823
1616
|
if (id.includes("gemini")) return "google";
|
|
824
1617
|
if (id.includes("mistral") || id.includes("mixtral") || id.includes("codestral") || id.includes("pixtral")) return "mistral";
|
|
@@ -844,7 +1637,7 @@ function calculateCostForCall(provider, modelId, promptTokens, completionTokens)
|
|
|
844
1637
|
case "mistral":
|
|
845
1638
|
return promptTokens / 1e6 * 2 + completionTokens / 1e6 * 6;
|
|
846
1639
|
default:
|
|
847
|
-
return
|
|
1640
|
+
return 0;
|
|
848
1641
|
}
|
|
849
1642
|
}
|
|
850
1643
|
function extractInput(params) {
|
|
@@ -858,6 +1651,15 @@ function extractInput(params) {
|
|
|
858
1651
|
}
|
|
859
1652
|
return "";
|
|
860
1653
|
}
|
|
1654
|
+
function normalizeUsage(usage) {
|
|
1655
|
+
if (!usage) return void 0;
|
|
1656
|
+
const u = usage;
|
|
1657
|
+
const promptTokens = (u.inputTokens ?? u.promptTokens ?? 0) || 0;
|
|
1658
|
+
const completionTokens = (u.outputTokens ?? u.completionTokens ?? 0) || 0;
|
|
1659
|
+
const totalTokens = (u.totalTokens ?? promptTokens + completionTokens) || 0;
|
|
1660
|
+
if (promptTokens === 0 && completionTokens === 0 && totalTokens === 0) return void 0;
|
|
1661
|
+
return { promptTokens, completionTokens, totalTokens };
|
|
1662
|
+
}
|
|
861
1663
|
function wrapTools(tools, sessionId, client) {
|
|
862
1664
|
if (!tools) return void 0;
|
|
863
1665
|
const wrappedTools = {};
|
|
@@ -910,295 +1712,126 @@ function wrapToolsAsync(tools, sessionPromise, client) {
|
|
|
910
1712
|
...tool,
|
|
911
1713
|
execute: async (...args) => {
|
|
912
1714
|
const startTime = Date.now();
|
|
913
|
-
const sid = await sessionPromise;
|
|
914
1715
|
try {
|
|
915
1716
|
const result = await originalExecute(...args);
|
|
916
1717
|
const durationMs = Date.now() - startTime;
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
wrappedTools[toolName] = tool;
|
|
947
|
-
}
|
|
948
|
-
}
|
|
949
|
-
return wrappedTools;
|
|
950
|
-
}
|
|
951
|
-
function wrapGenerateText(originalFn, client, config) {
|
|
952
|
-
return async (params) => {
|
|
953
|
-
const startTime = Date.now();
|
|
954
|
-
const { modelId, provider } = extractModelInfo(params.model);
|
|
955
|
-
const input = extractInput(params);
|
|
956
|
-
const sessionId = await client.createSession({
|
|
957
|
-
name: `generateText: ${input.slice(0, 50)}${input.length > 50 ? "..." : ""}`,
|
|
958
|
-
agentName: config.defaultAgent ?? "vercel-ai-sdk",
|
|
959
|
-
userId: config.userId ?? "anonymous",
|
|
960
|
-
convoId: config.convoId,
|
|
961
|
-
metadata: {
|
|
962
|
-
model: modelId,
|
|
963
|
-
provider,
|
|
964
|
-
function: "generateText",
|
|
965
|
-
...params.maxSteps ? { maxSteps: params.maxSteps } : {}
|
|
966
|
-
}
|
|
967
|
-
});
|
|
968
|
-
if (!sessionId) {
|
|
969
|
-
return originalFn(params);
|
|
970
|
-
}
|
|
971
|
-
await client.setInput(sessionId, input);
|
|
972
|
-
const wrappedParams = {
|
|
973
|
-
...params,
|
|
974
|
-
tools: wrapTools(params.tools, sessionId, client)
|
|
975
|
-
};
|
|
976
|
-
try {
|
|
977
|
-
const result = await originalFn(wrappedParams);
|
|
978
|
-
const durationMs = Date.now() - startTime;
|
|
979
|
-
const resolvedModelId = result.response?.modelId || modelId;
|
|
980
|
-
const promptTokens = result.usage?.promptTokens || 0;
|
|
981
|
-
const completionTokens = result.usage?.completionTokens || 0;
|
|
982
|
-
const totalTokens = result.usage?.totalTokens || promptTokens + completionTokens;
|
|
983
|
-
const cost = calculateCostForCall(provider, resolvedModelId, promptTokens, completionTokens);
|
|
984
|
-
const steps = result.steps;
|
|
985
|
-
if (steps && steps.length >= 1) {
|
|
986
|
-
for (let i = 0; i < steps.length; i++) {
|
|
987
|
-
const step = steps[i];
|
|
988
|
-
await client.trackEvent({
|
|
989
|
-
sessionId,
|
|
990
|
-
eventType: "llm_call",
|
|
991
|
-
eventData: {
|
|
992
|
-
model: resolvedModelId,
|
|
993
|
-
provider,
|
|
994
|
-
step: i + 1,
|
|
995
|
-
total_steps: steps.length,
|
|
996
|
-
prompt_tokens: step.usage?.promptTokens || 0,
|
|
997
|
-
completion_tokens: step.usage?.completionTokens || 0,
|
|
998
|
-
total_tokens: step.usage?.totalTokens || 0,
|
|
999
|
-
finish_reason: step.finishReason,
|
|
1000
|
-
tool_calls: step.toolCalls?.map((tc) => tc.toolName)
|
|
1001
|
-
}
|
|
1002
|
-
});
|
|
1003
|
-
}
|
|
1004
|
-
} else {
|
|
1005
|
-
await client.trackEvent({
|
|
1006
|
-
sessionId,
|
|
1007
|
-
eventType: "llm_call",
|
|
1008
|
-
eventData: {
|
|
1009
|
-
model: resolvedModelId,
|
|
1010
|
-
provider,
|
|
1011
|
-
prompt_tokens: promptTokens,
|
|
1012
|
-
completion_tokens: completionTokens,
|
|
1013
|
-
total_tokens: totalTokens,
|
|
1014
|
-
finish_reason: result.finishReason,
|
|
1015
|
-
tool_calls: result.toolCalls?.map((tc) => tc.toolName)
|
|
1016
|
-
}
|
|
1017
|
-
});
|
|
1018
|
-
}
|
|
1019
|
-
await client.completeSession({
|
|
1020
|
-
sessionId,
|
|
1021
|
-
success: true,
|
|
1022
|
-
output: result.text,
|
|
1023
|
-
durationMs,
|
|
1024
|
-
estimatedCost: cost,
|
|
1025
|
-
promptTokens,
|
|
1026
|
-
completionTokens,
|
|
1027
|
-
totalTokens
|
|
1028
|
-
});
|
|
1029
|
-
return result;
|
|
1030
|
-
} catch (error) {
|
|
1031
|
-
const durationMs = Date.now() - startTime;
|
|
1032
|
-
await client.trackError({
|
|
1033
|
-
sessionId,
|
|
1034
|
-
errorType: error instanceof Error ? error.name : "Error",
|
|
1035
|
-
errorMessage: error instanceof Error ? error.message : "Unknown error"
|
|
1036
|
-
});
|
|
1037
|
-
await client.completeSession({
|
|
1038
|
-
sessionId,
|
|
1039
|
-
success: false,
|
|
1040
|
-
failureReason: error instanceof Error ? error.message : "Unknown error",
|
|
1041
|
-
durationMs
|
|
1042
|
-
});
|
|
1043
|
-
throw error;
|
|
1044
|
-
}
|
|
1045
|
-
};
|
|
1046
|
-
}
|
|
1047
|
-
function wrapStreamText(originalFn, client, config) {
|
|
1048
|
-
return (params) => {
|
|
1049
|
-
const startTime = Date.now();
|
|
1050
|
-
const { modelId, provider } = extractModelInfo(params.model);
|
|
1051
|
-
const input = extractInput(params);
|
|
1052
|
-
let sessionId = null;
|
|
1053
|
-
const sessionPromise = (async () => {
|
|
1054
|
-
try {
|
|
1055
|
-
const id = await client.createSession({
|
|
1056
|
-
name: `streamText: ${input.slice(0, 50)}${input.length > 50 ? "..." : ""}`,
|
|
1057
|
-
agentName: config.defaultAgent ?? "vercel-ai-sdk",
|
|
1058
|
-
userId: config.userId ?? "anonymous",
|
|
1059
|
-
convoId: config.convoId,
|
|
1060
|
-
metadata: {
|
|
1061
|
-
model: modelId,
|
|
1062
|
-
provider,
|
|
1063
|
-
function: "streamText"
|
|
1064
|
-
}
|
|
1065
|
-
});
|
|
1066
|
-
sessionId = id;
|
|
1067
|
-
if (id) {
|
|
1068
|
-
client.setInput(id, input).catch(() => {
|
|
1069
|
-
});
|
|
1070
|
-
}
|
|
1071
|
-
return id;
|
|
1072
|
-
} catch {
|
|
1073
|
-
return null;
|
|
1074
|
-
}
|
|
1075
|
-
})();
|
|
1076
|
-
const wrappedParams = {
|
|
1077
|
-
...params,
|
|
1078
|
-
tools: params.tools ? wrapToolsAsync(params.tools, sessionPromise, client) : void 0
|
|
1079
|
-
};
|
|
1080
|
-
const result = originalFn(wrappedParams);
|
|
1081
|
-
let tracked = false;
|
|
1082
|
-
async function trackCompletion(fullText, error) {
|
|
1083
|
-
if (tracked) return;
|
|
1084
|
-
tracked = true;
|
|
1085
|
-
const durationMs = Date.now() - startTime;
|
|
1086
|
-
const sid = sessionId || await sessionPromise;
|
|
1087
|
-
if (!sid) return;
|
|
1088
|
-
if (error) {
|
|
1089
|
-
await client.trackError({
|
|
1090
|
-
sessionId: sid,
|
|
1091
|
-
errorType: error.name || "Error",
|
|
1092
|
-
errorMessage: error.message || "Unknown error"
|
|
1093
|
-
});
|
|
1094
|
-
await client.completeSession({
|
|
1095
|
-
sessionId: sid,
|
|
1096
|
-
success: false,
|
|
1097
|
-
failureReason: error.message || "Unknown error",
|
|
1098
|
-
durationMs
|
|
1099
|
-
});
|
|
1100
|
-
return;
|
|
1101
|
-
}
|
|
1102
|
-
let usage;
|
|
1103
|
-
try {
|
|
1104
|
-
usage = result.usage ? await result.usage : void 0;
|
|
1105
|
-
} catch {
|
|
1106
|
-
}
|
|
1107
|
-
const promptTokens = usage?.promptTokens || 0;
|
|
1108
|
-
const completionTokens = usage?.completionTokens || 0;
|
|
1109
|
-
const totalTokens = usage?.totalTokens || promptTokens + completionTokens;
|
|
1110
|
-
const cost = calculateCostForCall(provider, modelId, promptTokens, completionTokens);
|
|
1111
|
-
await client.trackEvent({
|
|
1112
|
-
sessionId: sid,
|
|
1113
|
-
eventType: "llm_call",
|
|
1114
|
-
eventData: {
|
|
1115
|
-
model: modelId,
|
|
1116
|
-
provider,
|
|
1117
|
-
prompt_tokens: promptTokens,
|
|
1118
|
-
completion_tokens: completionTokens,
|
|
1119
|
-
total_tokens: totalTokens
|
|
1120
|
-
}
|
|
1121
|
-
});
|
|
1122
|
-
await client.completeSession({
|
|
1123
|
-
sessionId: sid,
|
|
1124
|
-
success: true,
|
|
1125
|
-
output: fullText,
|
|
1126
|
-
durationMs,
|
|
1127
|
-
estimatedCost: cost,
|
|
1128
|
-
promptTokens,
|
|
1129
|
-
completionTokens,
|
|
1130
|
-
totalTokens
|
|
1131
|
-
});
|
|
1132
|
-
}
|
|
1133
|
-
const textProp = result.text;
|
|
1134
|
-
if (typeof textProp === "string") {
|
|
1135
|
-
trackCompletion(textProp).catch(() => {
|
|
1136
|
-
});
|
|
1137
|
-
} else if (textProp != null && typeof textProp.then === "function") {
|
|
1138
|
-
const originalTextPromise = textProp;
|
|
1139
|
-
result.text = originalTextPromise.then((text) => {
|
|
1140
|
-
trackCompletion(text).catch(() => {
|
|
1141
|
-
});
|
|
1142
|
-
return text;
|
|
1143
|
-
}).catch((err) => {
|
|
1144
|
-
trackCompletion("", err instanceof Error ? err : new Error(String(err))).catch(() => {
|
|
1145
|
-
});
|
|
1146
|
-
throw err;
|
|
1147
|
-
});
|
|
1148
|
-
} else {
|
|
1149
|
-
const originalTextStream = result.textStream;
|
|
1150
|
-
let fullText = "";
|
|
1151
|
-
result.textStream = (async function* () {
|
|
1152
|
-
try {
|
|
1153
|
-
for await (const chunk of originalTextStream) {
|
|
1154
|
-
fullText += chunk;
|
|
1155
|
-
yield chunk;
|
|
1718
|
+
sessionPromise.then((sid) => {
|
|
1719
|
+
if (sid) {
|
|
1720
|
+
client.trackToolCall({
|
|
1721
|
+
sessionId: sid,
|
|
1722
|
+
toolName,
|
|
1723
|
+
toolInput: args[0],
|
|
1724
|
+
toolOutput: result,
|
|
1725
|
+
reasoning: `Tool executed in ${durationMs}ms`
|
|
1726
|
+
}).catch(() => {
|
|
1727
|
+
});
|
|
1728
|
+
}
|
|
1729
|
+
});
|
|
1730
|
+
return result;
|
|
1731
|
+
} catch (error) {
|
|
1732
|
+
const durationMs = Date.now() - startTime;
|
|
1733
|
+
sessionPromise.then((sid) => {
|
|
1734
|
+
if (sid) {
|
|
1735
|
+
client.trackToolCall({
|
|
1736
|
+
sessionId: sid,
|
|
1737
|
+
toolName,
|
|
1738
|
+
toolInput: args[0],
|
|
1739
|
+
toolOutput: {},
|
|
1740
|
+
toolError: { message: error instanceof Error ? error.message : "Unknown error" },
|
|
1741
|
+
reasoning: `Tool failed after ${durationMs}ms`
|
|
1742
|
+
}).catch(() => {
|
|
1743
|
+
});
|
|
1744
|
+
}
|
|
1745
|
+
});
|
|
1746
|
+
throw error;
|
|
1156
1747
|
}
|
|
1157
|
-
await trackCompletion(fullText);
|
|
1158
|
-
} catch (error) {
|
|
1159
|
-
await trackCompletion(
|
|
1160
|
-
"",
|
|
1161
|
-
error instanceof Error ? error : new Error(String(error))
|
|
1162
|
-
);
|
|
1163
|
-
throw error;
|
|
1164
1748
|
}
|
|
1165
|
-
}
|
|
1749
|
+
};
|
|
1750
|
+
} else {
|
|
1751
|
+
wrappedTools[toolName] = tool;
|
|
1166
1752
|
}
|
|
1167
|
-
|
|
1168
|
-
|
|
1753
|
+
}
|
|
1754
|
+
return wrappedTools;
|
|
1169
1755
|
}
|
|
1170
|
-
function
|
|
1756
|
+
function wrapGenerateText(originalFn, client, config) {
|
|
1171
1757
|
return async (params) => {
|
|
1172
1758
|
const startTime = Date.now();
|
|
1173
1759
|
const { modelId, provider } = extractModelInfo(params.model);
|
|
1174
1760
|
const input = extractInput(params);
|
|
1175
1761
|
const sessionId = await client.createSession({
|
|
1176
|
-
name: `
|
|
1762
|
+
name: `generateText: ${input.slice(0, 50)}${input.length > 50 ? "..." : ""}`,
|
|
1177
1763
|
agentName: config.defaultAgent ?? "vercel-ai-sdk",
|
|
1178
1764
|
userId: config.userId ?? "anonymous",
|
|
1179
1765
|
convoId: config.convoId,
|
|
1180
1766
|
metadata: {
|
|
1181
1767
|
model: modelId,
|
|
1182
1768
|
provider,
|
|
1183
|
-
function: "
|
|
1769
|
+
function: "generateText",
|
|
1770
|
+
...params.maxSteps ? { maxSteps: params.maxSteps } : {}
|
|
1184
1771
|
}
|
|
1185
1772
|
});
|
|
1186
1773
|
if (!sessionId) {
|
|
1187
1774
|
return originalFn(params);
|
|
1188
1775
|
}
|
|
1189
1776
|
await client.setInput(sessionId, input);
|
|
1777
|
+
const wrappedParams = {
|
|
1778
|
+
...params,
|
|
1779
|
+
tools: wrapTools(params.tools, sessionId, client)
|
|
1780
|
+
};
|
|
1190
1781
|
try {
|
|
1191
|
-
const result = await originalFn(
|
|
1782
|
+
const result = await originalFn(wrappedParams);
|
|
1192
1783
|
const durationMs = Date.now() - startTime;
|
|
1193
1784
|
const resolvedModelId = result.response?.modelId || modelId;
|
|
1194
|
-
const
|
|
1195
|
-
const
|
|
1196
|
-
const
|
|
1785
|
+
const usage = normalizeUsage(result.usage);
|
|
1786
|
+
const promptTokens = usage?.promptTokens ?? 0;
|
|
1787
|
+
const completionTokens = usage?.completionTokens ?? 0;
|
|
1788
|
+
const totalTokens = usage?.totalTokens ?? 0;
|
|
1197
1789
|
const cost = calculateCostForCall(provider, resolvedModelId, promptTokens, completionTokens);
|
|
1790
|
+
const steps = result.steps;
|
|
1791
|
+
if (steps && steps.length >= 1) {
|
|
1792
|
+
const stepPromises = steps.map((step) => {
|
|
1793
|
+
const su = normalizeUsage(step.usage);
|
|
1794
|
+
return client.trackToolCall({
|
|
1795
|
+
sessionId,
|
|
1796
|
+
toolName: `llm:${provider}:${resolvedModelId}`,
|
|
1797
|
+
toolInput: { finishReason: step.finishReason },
|
|
1798
|
+
toolOutput: {
|
|
1799
|
+
text: step.text?.slice(0, 500),
|
|
1800
|
+
tokens: {
|
|
1801
|
+
prompt: su?.promptTokens ?? 0,
|
|
1802
|
+
completion: su?.completionTokens ?? 0
|
|
1803
|
+
}
|
|
1804
|
+
},
|
|
1805
|
+
estimatedCost: calculateCostForCall(provider, resolvedModelId, su?.promptTokens ?? 0, su?.completionTokens ?? 0),
|
|
1806
|
+
tokenCount: su?.totalTokens,
|
|
1807
|
+
metadata: {
|
|
1808
|
+
tool_calls: step.toolCalls?.map((tc) => tc.toolName)
|
|
1809
|
+
}
|
|
1810
|
+
}).catch(() => {
|
|
1811
|
+
});
|
|
1812
|
+
});
|
|
1813
|
+
await Promise.all(stepPromises);
|
|
1814
|
+
} else {
|
|
1815
|
+
await client.trackToolCall({
|
|
1816
|
+
sessionId,
|
|
1817
|
+
toolName: `llm:${provider}:${resolvedModelId}`,
|
|
1818
|
+
toolInput: { finishReason: result.finishReason },
|
|
1819
|
+
toolOutput: {
|
|
1820
|
+
text: result.text?.slice(0, 500),
|
|
1821
|
+
tokens: { prompt: promptTokens, completion: completionTokens }
|
|
1822
|
+
},
|
|
1823
|
+
estimatedCost: cost,
|
|
1824
|
+
tokenCount: totalTokens,
|
|
1825
|
+
metadata: {
|
|
1826
|
+
tool_calls: result.toolCalls?.map((tc) => tc.toolName)
|
|
1827
|
+
}
|
|
1828
|
+
}).catch(() => {
|
|
1829
|
+
});
|
|
1830
|
+
}
|
|
1198
1831
|
await client.completeSession({
|
|
1199
1832
|
sessionId,
|
|
1200
1833
|
success: true,
|
|
1201
|
-
output:
|
|
1834
|
+
output: result.text,
|
|
1202
1835
|
durationMs,
|
|
1203
1836
|
estimatedCost: cost,
|
|
1204
1837
|
promptTokens,
|
|
@@ -1223,393 +1856,524 @@ function wrapGenerateObject(originalFn, client, config) {
|
|
|
1223
1856
|
}
|
|
1224
1857
|
};
|
|
1225
1858
|
}
|
|
1226
|
-
function
|
|
1859
|
+
function wrapStreamText(originalFn, client, config) {
|
|
1227
1860
|
return (params) => {
|
|
1228
1861
|
const startTime = Date.now();
|
|
1229
1862
|
const { modelId, provider } = extractModelInfo(params.model);
|
|
1230
1863
|
const input = extractInput(params);
|
|
1231
|
-
|
|
1864
|
+
let sessionId = null;
|
|
1865
|
+
let resolveSessionReady;
|
|
1866
|
+
const sessionReady = new Promise((resolve) => {
|
|
1867
|
+
resolveSessionReady = resolve;
|
|
1868
|
+
});
|
|
1869
|
+
(async () => {
|
|
1232
1870
|
try {
|
|
1233
1871
|
const id = await client.createSession({
|
|
1234
|
-
name: `
|
|
1872
|
+
name: `streamText: ${input.slice(0, 50)}${input.length > 50 ? "..." : ""}`,
|
|
1235
1873
|
agentName: config.defaultAgent ?? "vercel-ai-sdk",
|
|
1236
1874
|
userId: config.userId ?? "anonymous",
|
|
1237
1875
|
convoId: config.convoId,
|
|
1238
1876
|
metadata: {
|
|
1239
1877
|
model: modelId,
|
|
1240
1878
|
provider,
|
|
1241
|
-
function: "
|
|
1879
|
+
function: "streamText"
|
|
1242
1880
|
}
|
|
1243
1881
|
});
|
|
1882
|
+
sessionId = id;
|
|
1244
1883
|
if (id) {
|
|
1245
1884
|
client.setInput(id, input).catch(() => {
|
|
1246
1885
|
});
|
|
1247
1886
|
}
|
|
1248
|
-
return id;
|
|
1249
1887
|
} catch {
|
|
1250
|
-
|
|
1888
|
+
sessionId = null;
|
|
1251
1889
|
}
|
|
1890
|
+
resolveSessionReady();
|
|
1252
1891
|
})();
|
|
1253
|
-
const
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1892
|
+
const userOnStepFinish = params.onStepFinish;
|
|
1893
|
+
const userOnFinish = params.onFinish;
|
|
1894
|
+
const userOnError = params.onError;
|
|
1895
|
+
const wrappedParams = {
|
|
1896
|
+
...params,
|
|
1897
|
+
tools: params.tools ? wrapToolsAsync(params.tools, sessionReady.then(() => sessionId), client) : void 0,
|
|
1898
|
+
onStepFinish: async (step) => {
|
|
1899
|
+
await sessionReady;
|
|
1900
|
+
if (sessionId) {
|
|
1901
|
+
const su = normalizeUsage(step.usage);
|
|
1902
|
+
const resolvedStepModel = step.response?.modelId ?? modelId;
|
|
1903
|
+
client.trackToolCall({
|
|
1904
|
+
sessionId,
|
|
1905
|
+
toolName: `llm:${provider}:${resolvedStepModel}`,
|
|
1906
|
+
toolInput: { finishReason: step.finishReason },
|
|
1907
|
+
toolOutput: {
|
|
1908
|
+
text: step.text?.slice(0, 500),
|
|
1909
|
+
tokens: {
|
|
1910
|
+
prompt: su?.promptTokens ?? 0,
|
|
1911
|
+
completion: su?.completionTokens ?? 0
|
|
1912
|
+
}
|
|
1913
|
+
},
|
|
1914
|
+
estimatedCost: calculateCostForCall(provider, resolvedStepModel, su?.promptTokens ?? 0, su?.completionTokens ?? 0),
|
|
1915
|
+
tokenCount: su?.totalTokens,
|
|
1916
|
+
metadata: {
|
|
1917
|
+
tool_calls: step.toolCalls?.map((tc) => tc.toolName)
|
|
1918
|
+
}
|
|
1919
|
+
}).catch(() => {
|
|
1920
|
+
});
|
|
1921
|
+
}
|
|
1922
|
+
if (userOnStepFinish) {
|
|
1261
1923
|
try {
|
|
1262
|
-
|
|
1924
|
+
userOnStepFinish(step);
|
|
1263
1925
|
} catch {
|
|
1264
1926
|
}
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1927
|
+
}
|
|
1928
|
+
},
|
|
1929
|
+
onFinish: async (event) => {
|
|
1930
|
+
await sessionReady;
|
|
1931
|
+
if (sessionId) {
|
|
1932
|
+
const durationMs = Date.now() - startTime;
|
|
1933
|
+
const usage = normalizeUsage(event.totalUsage ?? event.usage);
|
|
1934
|
+
const resolvedModelId = event.response?.modelId ?? modelId;
|
|
1935
|
+
const text = event.text ?? "";
|
|
1936
|
+
const promptTokens = usage?.promptTokens ?? 0;
|
|
1937
|
+
const completionTokens = usage?.completionTokens ?? 0;
|
|
1938
|
+
const totalTokens = usage?.totalTokens ?? promptTokens + completionTokens;
|
|
1939
|
+
const cost = calculateCostForCall(provider, resolvedModelId, promptTokens, completionTokens);
|
|
1269
1940
|
await client.completeSession({
|
|
1270
|
-
sessionId
|
|
1941
|
+
sessionId,
|
|
1271
1942
|
success: true,
|
|
1272
|
-
output:
|
|
1943
|
+
output: text,
|
|
1273
1944
|
durationMs,
|
|
1274
1945
|
estimatedCost: cost,
|
|
1275
1946
|
promptTokens,
|
|
1276
1947
|
completionTokens,
|
|
1277
1948
|
totalTokens
|
|
1949
|
+
}).catch(() => {
|
|
1278
1950
|
});
|
|
1279
1951
|
}
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1952
|
+
if (userOnFinish) {
|
|
1953
|
+
try {
|
|
1954
|
+
userOnFinish(event);
|
|
1955
|
+
} catch {
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1958
|
+
},
|
|
1959
|
+
onError: async (event) => {
|
|
1960
|
+
await sessionReady;
|
|
1961
|
+
if (sessionId) {
|
|
1962
|
+
const durationMs = Date.now() - startTime;
|
|
1963
|
+
const msg = event.error?.message ?? "Unknown error";
|
|
1285
1964
|
await client.trackError({
|
|
1286
|
-
sessionId
|
|
1287
|
-
errorType: error
|
|
1288
|
-
errorMessage:
|
|
1965
|
+
sessionId,
|
|
1966
|
+
errorType: event.error?.name ?? "Error",
|
|
1967
|
+
errorMessage: msg
|
|
1968
|
+
}).catch(() => {
|
|
1289
1969
|
});
|
|
1290
1970
|
await client.completeSession({
|
|
1291
|
-
sessionId
|
|
1971
|
+
sessionId,
|
|
1292
1972
|
success: false,
|
|
1293
|
-
failureReason:
|
|
1973
|
+
failureReason: msg,
|
|
1294
1974
|
durationMs
|
|
1975
|
+
}).catch(() => {
|
|
1295
1976
|
});
|
|
1296
1977
|
}
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
}
|
|
1303
|
-
function wrapAISDK(ai, options) {
|
|
1304
|
-
const client = options?.client ?? getClient2();
|
|
1305
|
-
const config = {
|
|
1306
|
-
defaultAgent: options?.defaultAgent ?? _globalConfig.defaultAgent,
|
|
1307
|
-
userId: options?.userId ?? _globalConfig.userId,
|
|
1308
|
-
convoId: options?.convoId ?? _globalConfig.convoId
|
|
1309
|
-
};
|
|
1310
|
-
return {
|
|
1311
|
-
generateText: ai.generateText ? wrapGenerateText(ai.generateText, client, config) : wrapGenerateText(
|
|
1312
|
-
() => Promise.reject(new Error("generateText not available")),
|
|
1313
|
-
client,
|
|
1314
|
-
config
|
|
1315
|
-
),
|
|
1316
|
-
streamText: ai.streamText ? wrapStreamText(ai.streamText, client, config) : wrapStreamText(() => ({ textStream: (async function* () {
|
|
1317
|
-
})() }), client, config),
|
|
1318
|
-
generateObject: ai.generateObject ? wrapGenerateObject(ai.generateObject, client, config) : wrapGenerateObject(
|
|
1319
|
-
() => Promise.reject(new Error("generateObject not available")),
|
|
1320
|
-
client,
|
|
1321
|
-
config
|
|
1322
|
-
),
|
|
1323
|
-
streamObject: ai.streamObject ? wrapStreamObject(ai.streamObject, client, config) : wrapStreamObject(() => ({}), client, config)
|
|
1324
|
-
};
|
|
1325
|
-
}
|
|
1326
|
-
|
|
1327
|
-
// src/wrappers.ts
|
|
1328
|
-
var _currentSessionId = null;
|
|
1329
|
-
var _currentClient = null;
|
|
1330
|
-
var _defaultClient2 = null;
|
|
1331
|
-
function setSessionContext(sessionId, client) {
|
|
1332
|
-
_currentSessionId = sessionId;
|
|
1333
|
-
if (client) {
|
|
1334
|
-
_currentClient = client;
|
|
1335
|
-
}
|
|
1336
|
-
}
|
|
1337
|
-
function clearSessionContext() {
|
|
1338
|
-
_currentSessionId = null;
|
|
1339
|
-
_currentClient = null;
|
|
1340
|
-
}
|
|
1341
|
-
function getSessionContext() {
|
|
1342
|
-
return _currentSessionId;
|
|
1343
|
-
}
|
|
1344
|
-
function setDefaultClient(client) {
|
|
1345
|
-
_defaultClient2 = client;
|
|
1346
|
-
}
|
|
1347
|
-
function getTrackingClient() {
|
|
1348
|
-
return _currentClient ?? _defaultClient2;
|
|
1349
|
-
}
|
|
1350
|
-
function wrapOpenAI(client, options = {}) {
|
|
1351
|
-
const { trackWithoutSession = false } = options;
|
|
1352
|
-
const chat = client.chat;
|
|
1353
|
-
if (!chat?.completions?.create) {
|
|
1354
|
-
console.warn("Sentrial: OpenAI client does not have chat.completions.create");
|
|
1355
|
-
return client;
|
|
1356
|
-
}
|
|
1357
|
-
const originalCreate = chat.completions.create.bind(chat.completions);
|
|
1358
|
-
chat.completions.create = async function(...args) {
|
|
1359
|
-
const startTime = Date.now();
|
|
1360
|
-
const params = args[0] ?? {};
|
|
1361
|
-
const messages = params.messages ?? [];
|
|
1362
|
-
const model = params.model ?? "unknown";
|
|
1363
|
-
try {
|
|
1364
|
-
const response = await originalCreate(...args);
|
|
1365
|
-
const durationMs = Date.now() - startTime;
|
|
1366
|
-
const promptTokens = response.usage?.prompt_tokens ?? 0;
|
|
1367
|
-
const completionTokens = response.usage?.completion_tokens ?? 0;
|
|
1368
|
-
const totalTokens = response.usage?.total_tokens ?? 0;
|
|
1369
|
-
let outputContent = "";
|
|
1370
|
-
if (response.choices?.[0]?.message?.content) {
|
|
1371
|
-
outputContent = response.choices[0].message.content;
|
|
1978
|
+
if (userOnError) {
|
|
1979
|
+
try {
|
|
1980
|
+
userOnError(event);
|
|
1981
|
+
} catch {
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1372
1984
|
}
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
model,
|
|
1377
|
-
messages,
|
|
1378
|
-
output: outputContent,
|
|
1379
|
-
promptTokens,
|
|
1380
|
-
completionTokens,
|
|
1381
|
-
totalTokens,
|
|
1382
|
-
cost,
|
|
1383
|
-
durationMs,
|
|
1384
|
-
trackWithoutSession
|
|
1385
|
-
});
|
|
1386
|
-
return response;
|
|
1985
|
+
};
|
|
1986
|
+
try {
|
|
1987
|
+
return originalFn(wrappedParams);
|
|
1387
1988
|
} catch (error) {
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1989
|
+
sessionReady.then(() => {
|
|
1990
|
+
if (sessionId) {
|
|
1991
|
+
const durationMs = Date.now() - startTime;
|
|
1992
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
1993
|
+
client.trackError({ sessionId, errorType: error instanceof Error ? error.name : "Error", errorMessage: msg }).catch(() => {
|
|
1994
|
+
});
|
|
1995
|
+
client.completeSession({ sessionId, success: false, failureReason: msg, durationMs }).catch(() => {
|
|
1996
|
+
});
|
|
1997
|
+
}
|
|
1396
1998
|
});
|
|
1397
1999
|
throw error;
|
|
1398
2000
|
}
|
|
1399
2001
|
};
|
|
1400
|
-
return client;
|
|
1401
2002
|
}
|
|
1402
|
-
function
|
|
1403
|
-
|
|
1404
|
-
const messages = client.messages;
|
|
1405
|
-
if (!messages?.create) {
|
|
1406
|
-
console.warn("Sentrial: Anthropic client does not have messages.create");
|
|
1407
|
-
return client;
|
|
1408
|
-
}
|
|
1409
|
-
const originalCreate = messages.create.bind(messages);
|
|
1410
|
-
messages.create = async function(...args) {
|
|
2003
|
+
function wrapGenerateObject(originalFn, client, config) {
|
|
2004
|
+
return async (params) => {
|
|
1411
2005
|
const startTime = Date.now();
|
|
1412
|
-
const
|
|
1413
|
-
const
|
|
1414
|
-
const
|
|
1415
|
-
|
|
2006
|
+
const { modelId, provider } = extractModelInfo(params.model);
|
|
2007
|
+
const input = extractInput(params);
|
|
2008
|
+
const sessionId = await client.createSession({
|
|
2009
|
+
name: `generateObject: ${input.slice(0, 50)}${input.length > 50 ? "..." : ""}`,
|
|
2010
|
+
agentName: config.defaultAgent ?? "vercel-ai-sdk",
|
|
2011
|
+
userId: config.userId ?? "anonymous",
|
|
2012
|
+
convoId: config.convoId,
|
|
2013
|
+
metadata: {
|
|
2014
|
+
model: modelId,
|
|
2015
|
+
provider,
|
|
2016
|
+
function: "generateObject"
|
|
2017
|
+
}
|
|
2018
|
+
});
|
|
2019
|
+
if (!sessionId) {
|
|
2020
|
+
return originalFn(params);
|
|
2021
|
+
}
|
|
2022
|
+
await client.setInput(sessionId, input);
|
|
1416
2023
|
try {
|
|
1417
|
-
const
|
|
2024
|
+
const result = await originalFn(params);
|
|
1418
2025
|
const durationMs = Date.now() - startTime;
|
|
1419
|
-
const
|
|
1420
|
-
const
|
|
1421
|
-
const
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
}
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
2026
|
+
const resolvedModelId = result.response?.modelId || modelId;
|
|
2027
|
+
const usage = normalizeUsage(result.usage);
|
|
2028
|
+
const promptTokens = usage?.promptTokens ?? 0;
|
|
2029
|
+
const completionTokens = usage?.completionTokens ?? 0;
|
|
2030
|
+
const totalTokens = usage?.totalTokens ?? 0;
|
|
2031
|
+
const cost = calculateCostForCall(provider, resolvedModelId, promptTokens, completionTokens);
|
|
2032
|
+
await client.trackToolCall({
|
|
2033
|
+
sessionId,
|
|
2034
|
+
toolName: `llm:${provider}:${resolvedModelId}`,
|
|
2035
|
+
toolInput: { function: "generateObject" },
|
|
2036
|
+
toolOutput: {
|
|
2037
|
+
object: result.object,
|
|
2038
|
+
tokens: { prompt: promptTokens, completion: completionTokens }
|
|
2039
|
+
},
|
|
2040
|
+
estimatedCost: cost,
|
|
2041
|
+
tokenCount: totalTokens
|
|
2042
|
+
}).catch(() => {
|
|
2043
|
+
});
|
|
2044
|
+
await client.completeSession({
|
|
2045
|
+
sessionId,
|
|
2046
|
+
success: true,
|
|
2047
|
+
output: JSON.stringify(result.object),
|
|
2048
|
+
durationMs,
|
|
2049
|
+
estimatedCost: cost,
|
|
1437
2050
|
promptTokens,
|
|
1438
2051
|
completionTokens,
|
|
1439
|
-
totalTokens
|
|
1440
|
-
cost,
|
|
1441
|
-
durationMs,
|
|
1442
|
-
trackWithoutSession
|
|
2052
|
+
totalTokens
|
|
1443
2053
|
});
|
|
1444
|
-
return
|
|
2054
|
+
return result;
|
|
1445
2055
|
} catch (error) {
|
|
1446
2056
|
const durationMs = Date.now() - startTime;
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
2057
|
+
await client.trackError({
|
|
2058
|
+
sessionId,
|
|
2059
|
+
errorType: error instanceof Error ? error.name : "Error",
|
|
2060
|
+
errorMessage: error instanceof Error ? error.message : "Unknown error"
|
|
2061
|
+
});
|
|
2062
|
+
await client.completeSession({
|
|
2063
|
+
sessionId,
|
|
2064
|
+
success: false,
|
|
2065
|
+
failureReason: error instanceof Error ? error.message : "Unknown error",
|
|
2066
|
+
durationMs
|
|
1454
2067
|
});
|
|
1455
2068
|
throw error;
|
|
1456
2069
|
}
|
|
1457
2070
|
};
|
|
1458
|
-
return client;
|
|
1459
2071
|
}
|
|
1460
|
-
function
|
|
1461
|
-
|
|
1462
|
-
const originalGenerate = model.generateContent;
|
|
1463
|
-
if (!originalGenerate) {
|
|
1464
|
-
console.warn("Sentrial: Google model does not have generateContent");
|
|
1465
|
-
return model;
|
|
1466
|
-
}
|
|
1467
|
-
model.generateContent = async function(...args) {
|
|
2072
|
+
function wrapStreamObject(originalFn, client, config) {
|
|
2073
|
+
return (params) => {
|
|
1468
2074
|
const startTime = Date.now();
|
|
1469
|
-
const
|
|
1470
|
-
const
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
if (response.usageMetadata) {
|
|
1478
|
-
promptTokens = response.usageMetadata.promptTokenCount ?? 0;
|
|
1479
|
-
completionTokens = response.usageMetadata.candidatesTokenCount ?? 0;
|
|
1480
|
-
}
|
|
1481
|
-
const totalTokens = promptTokens + completionTokens;
|
|
1482
|
-
let outputContent = "";
|
|
2075
|
+
const { modelId, provider } = extractModelInfo(params.model);
|
|
2076
|
+
const input = extractInput(params);
|
|
2077
|
+
let sessionId = null;
|
|
2078
|
+
let resolveSessionReady;
|
|
2079
|
+
const sessionReady = new Promise((resolve) => {
|
|
2080
|
+
resolveSessionReady = resolve;
|
|
2081
|
+
});
|
|
2082
|
+
(async () => {
|
|
1483
2083
|
try {
|
|
1484
|
-
|
|
2084
|
+
const id = await client.createSession({
|
|
2085
|
+
name: `streamObject: ${input.slice(0, 50)}${input.length > 50 ? "..." : ""}`,
|
|
2086
|
+
agentName: config.defaultAgent ?? "vercel-ai-sdk",
|
|
2087
|
+
userId: config.userId ?? "anonymous",
|
|
2088
|
+
convoId: config.convoId,
|
|
2089
|
+
metadata: {
|
|
2090
|
+
model: modelId,
|
|
2091
|
+
provider,
|
|
2092
|
+
function: "streamObject"
|
|
2093
|
+
}
|
|
2094
|
+
});
|
|
2095
|
+
sessionId = id;
|
|
2096
|
+
if (id) {
|
|
2097
|
+
client.setInput(id, input).catch(() => {
|
|
2098
|
+
});
|
|
2099
|
+
}
|
|
1485
2100
|
} catch {
|
|
2101
|
+
sessionId = null;
|
|
1486
2102
|
}
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
2103
|
+
resolveSessionReady();
|
|
2104
|
+
})();
|
|
2105
|
+
const userOnFinish = params.onFinish;
|
|
2106
|
+
const userOnError = params.onError;
|
|
2107
|
+
const wrappedParams = {
|
|
2108
|
+
...params,
|
|
2109
|
+
onFinish: async (event) => {
|
|
2110
|
+
await sessionReady;
|
|
2111
|
+
if (sessionId) {
|
|
2112
|
+
const durationMs = Date.now() - startTime;
|
|
2113
|
+
const usage = normalizeUsage(event.usage);
|
|
2114
|
+
const resolvedModelId = event.response?.modelId ?? modelId;
|
|
2115
|
+
const promptTokens = usage?.promptTokens ?? 0;
|
|
2116
|
+
const completionTokens = usage?.completionTokens ?? 0;
|
|
2117
|
+
const totalTokens = usage?.totalTokens ?? 0;
|
|
2118
|
+
const cost = calculateCostForCall(provider, resolvedModelId, promptTokens, completionTokens);
|
|
2119
|
+
if (event.error) {
|
|
2120
|
+
const errMsg = event.error instanceof Error ? event.error.message : String(event.error);
|
|
2121
|
+
await client.trackError({
|
|
2122
|
+
sessionId,
|
|
2123
|
+
errorType: "SchemaValidationError",
|
|
2124
|
+
errorMessage: errMsg
|
|
2125
|
+
}).catch(() => {
|
|
2126
|
+
});
|
|
2127
|
+
}
|
|
2128
|
+
await client.trackToolCall({
|
|
2129
|
+
sessionId,
|
|
2130
|
+
toolName: `llm:${provider}:${resolvedModelId}`,
|
|
2131
|
+
toolInput: { function: "streamObject" },
|
|
2132
|
+
toolOutput: {
|
|
2133
|
+
object: event.object,
|
|
2134
|
+
tokens: { prompt: promptTokens, completion: completionTokens }
|
|
2135
|
+
},
|
|
2136
|
+
estimatedCost: cost,
|
|
2137
|
+
tokenCount: totalTokens
|
|
2138
|
+
}).catch(() => {
|
|
2139
|
+
});
|
|
2140
|
+
await client.completeSession({
|
|
2141
|
+
sessionId,
|
|
2142
|
+
success: !event.error,
|
|
2143
|
+
output: event.object != null ? JSON.stringify(event.object) : void 0,
|
|
2144
|
+
failureReason: event.error ? String(event.error) : void 0,
|
|
2145
|
+
durationMs,
|
|
2146
|
+
estimatedCost: cost,
|
|
2147
|
+
promptTokens,
|
|
2148
|
+
completionTokens,
|
|
2149
|
+
totalTokens
|
|
2150
|
+
}).catch(() => {
|
|
2151
|
+
});
|
|
2152
|
+
}
|
|
2153
|
+
if (userOnFinish) {
|
|
2154
|
+
try {
|
|
2155
|
+
userOnFinish(event);
|
|
2156
|
+
} catch {
|
|
2157
|
+
}
|
|
2158
|
+
}
|
|
2159
|
+
},
|
|
2160
|
+
onError: async (event) => {
|
|
2161
|
+
await sessionReady;
|
|
2162
|
+
if (sessionId) {
|
|
2163
|
+
const durationMs = Date.now() - startTime;
|
|
2164
|
+
const msg = event.error?.message ?? "Unknown error";
|
|
2165
|
+
await client.trackError({
|
|
2166
|
+
sessionId,
|
|
2167
|
+
errorType: event.error?.name ?? "Error",
|
|
2168
|
+
errorMessage: msg
|
|
2169
|
+
}).catch(() => {
|
|
2170
|
+
});
|
|
2171
|
+
await client.completeSession({
|
|
2172
|
+
sessionId,
|
|
2173
|
+
success: false,
|
|
2174
|
+
failureReason: msg,
|
|
2175
|
+
durationMs
|
|
2176
|
+
}).catch(() => {
|
|
2177
|
+
});
|
|
2178
|
+
}
|
|
2179
|
+
if (userOnError) {
|
|
2180
|
+
try {
|
|
2181
|
+
userOnError(event);
|
|
2182
|
+
} catch {
|
|
2183
|
+
}
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
2186
|
+
};
|
|
2187
|
+
try {
|
|
2188
|
+
return originalFn(wrappedParams);
|
|
1501
2189
|
} catch (error) {
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
2190
|
+
sessionReady.then(() => {
|
|
2191
|
+
if (sessionId) {
|
|
2192
|
+
const durationMs = Date.now() - startTime;
|
|
2193
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
2194
|
+
client.trackError({ sessionId, errorType: error instanceof Error ? error.name : "Error", errorMessage: msg }).catch(() => {
|
|
2195
|
+
});
|
|
2196
|
+
client.completeSession({ sessionId, success: false, failureReason: msg, durationMs }).catch(() => {
|
|
2197
|
+
});
|
|
2198
|
+
}
|
|
1510
2199
|
});
|
|
1511
2200
|
throw error;
|
|
1512
2201
|
}
|
|
1513
2202
|
};
|
|
1514
|
-
return model;
|
|
1515
|
-
}
|
|
1516
|
-
function googleContentsToMessages(contents) {
|
|
1517
|
-
if (typeof contents === "string") {
|
|
1518
|
-
return [{ role: "user", content: contents }];
|
|
1519
|
-
}
|
|
1520
|
-
if (Array.isArray(contents)) {
|
|
1521
|
-
return contents.map((item) => {
|
|
1522
|
-
if (typeof item === "string") {
|
|
1523
|
-
return { role: "user", content: item };
|
|
1524
|
-
}
|
|
1525
|
-
if (item && typeof item === "object") {
|
|
1526
|
-
return { role: item.role ?? "user", content: String(item.content ?? item) };
|
|
1527
|
-
}
|
|
1528
|
-
return { role: "user", content: String(item) };
|
|
1529
|
-
});
|
|
1530
|
-
}
|
|
1531
|
-
return [{ role: "user", content: String(contents) }];
|
|
1532
2203
|
}
|
|
1533
|
-
function
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
}
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
2204
|
+
function wrapAISDK(ai, options) {
|
|
2205
|
+
const client = options?.client ?? getClient2();
|
|
2206
|
+
const config = {
|
|
2207
|
+
defaultAgent: options?.defaultAgent ?? _globalConfig.defaultAgent,
|
|
2208
|
+
userId: options?.userId ?? _globalConfig.userId,
|
|
2209
|
+
convoId: options?.convoId ?? _globalConfig.convoId
|
|
2210
|
+
};
|
|
2211
|
+
return {
|
|
2212
|
+
generateText: ai.generateText ? wrapGenerateText(ai.generateText, client, config) : wrapGenerateText(
|
|
2213
|
+
() => Promise.reject(new Error("generateText not available")),
|
|
2214
|
+
client,
|
|
2215
|
+
config
|
|
2216
|
+
),
|
|
2217
|
+
streamText: ai.streamText ? wrapStreamText(ai.streamText, client, config) : wrapStreamText(() => ({ textStream: (async function* () {
|
|
2218
|
+
})() }), client, config),
|
|
2219
|
+
generateObject: ai.generateObject ? wrapGenerateObject(ai.generateObject, client, config) : wrapGenerateObject(
|
|
2220
|
+
() => Promise.reject(new Error("generateObject not available")),
|
|
2221
|
+
client,
|
|
2222
|
+
config
|
|
2223
|
+
),
|
|
2224
|
+
streamObject: ai.streamObject ? wrapStreamObject(ai.streamObject, client, config) : wrapStreamObject(() => ({}), client, config)
|
|
2225
|
+
};
|
|
1545
2226
|
}
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
const
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
content: params.output,
|
|
1564
|
-
tokens: {
|
|
1565
|
-
prompt: params.promptTokens,
|
|
1566
|
-
completion: params.completionTokens,
|
|
1567
|
-
total: params.totalTokens
|
|
1568
|
-
},
|
|
1569
|
-
cost_usd: params.cost
|
|
1570
|
-
},
|
|
1571
|
-
reasoning: `LLM call to ${params.provider} ${params.model}`,
|
|
1572
|
-
estimatedCost: params.cost,
|
|
1573
|
-
tokenCount: params.totalTokens,
|
|
1574
|
-
metadata: {
|
|
1575
|
-
provider: params.provider,
|
|
1576
|
-
model: params.model,
|
|
1577
|
-
duration_ms: params.durationMs,
|
|
1578
|
-
prompt_tokens: params.promptTokens,
|
|
1579
|
-
completion_tokens: params.completionTokens
|
|
1580
|
-
}
|
|
1581
|
-
}).catch((err) => {
|
|
1582
|
-
console.warn("Sentrial: Failed to track LLM call:", err.message);
|
|
2227
|
+
|
|
2228
|
+
// src/claude-code.ts
|
|
2229
|
+
function wrapClaudeAgent(queryFn, wrapOptions) {
|
|
2230
|
+
const {
|
|
2231
|
+
client,
|
|
2232
|
+
defaultAgent = "claude-agent",
|
|
2233
|
+
userId = "anonymous",
|
|
2234
|
+
convoId,
|
|
2235
|
+
extraMetadata
|
|
2236
|
+
} = wrapOptions;
|
|
2237
|
+
return function wrappedQuery(params) {
|
|
2238
|
+
const { prompt, options = {} } = params;
|
|
2239
|
+
const startTime = Date.now();
|
|
2240
|
+
let sessionId = null;
|
|
2241
|
+
let resolveSessionReady;
|
|
2242
|
+
const sessionReady = new Promise((resolve) => {
|
|
2243
|
+
resolveSessionReady = resolve;
|
|
1583
2244
|
});
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
2245
|
+
const sessionName = typeof prompt === "string" ? `${defaultAgent}: ${prompt.slice(0, 100)}` : `${defaultAgent} session`;
|
|
2246
|
+
const pendingToolCalls = [];
|
|
2247
|
+
const sentrialToolHook = {
|
|
2248
|
+
hooks: [
|
|
2249
|
+
async (input, toolUseID, _opts) => {
|
|
2250
|
+
await sessionReady;
|
|
2251
|
+
if (!sessionId) return;
|
|
2252
|
+
const toolOutput = input?.tool_response && typeof input.tool_response === "object" ? input.tool_response : { response: input?.tool_response ?? null };
|
|
2253
|
+
const p = client.trackToolCall({
|
|
2254
|
+
sessionId,
|
|
2255
|
+
toolName: input?.tool_name ?? "unknown",
|
|
2256
|
+
toolInput: input?.tool_input ?? {},
|
|
2257
|
+
toolOutput,
|
|
2258
|
+
metadata: { tool_use_id: toolUseID }
|
|
2259
|
+
}).catch(() => {
|
|
2260
|
+
});
|
|
2261
|
+
pendingToolCalls.push(p);
|
|
2262
|
+
}
|
|
2263
|
+
]
|
|
2264
|
+
};
|
|
2265
|
+
const sentrialToolFailureHook = {
|
|
2266
|
+
hooks: [
|
|
2267
|
+
async (input, toolUseID, _opts) => {
|
|
2268
|
+
await sessionReady;
|
|
2269
|
+
if (!sessionId) return;
|
|
2270
|
+
const p = client.trackToolCall({
|
|
2271
|
+
sessionId,
|
|
2272
|
+
toolName: input?.tool_name ?? "unknown",
|
|
2273
|
+
toolInput: input?.tool_input ?? {},
|
|
2274
|
+
toolOutput: {},
|
|
2275
|
+
toolError: { message: input?.error ?? "unknown error" },
|
|
2276
|
+
metadata: { tool_use_id: toolUseID }
|
|
2277
|
+
}).catch(() => {
|
|
2278
|
+
});
|
|
2279
|
+
pendingToolCalls.push(p);
|
|
2280
|
+
}
|
|
2281
|
+
]
|
|
2282
|
+
};
|
|
2283
|
+
const mergedHooks = {
|
|
2284
|
+
...options.hooks ?? {}
|
|
2285
|
+
};
|
|
2286
|
+
const existingPostToolUse = mergedHooks.PostToolUse ?? [];
|
|
2287
|
+
mergedHooks.PostToolUse = [...existingPostToolUse, sentrialToolHook];
|
|
2288
|
+
const existingPostToolUseFailure = mergedHooks.PostToolUseFailure ?? [];
|
|
2289
|
+
mergedHooks.PostToolUseFailure = [...existingPostToolUseFailure, sentrialToolFailureHook];
|
|
2290
|
+
const mergedOptions = {
|
|
2291
|
+
...options,
|
|
2292
|
+
hooks: mergedHooks
|
|
2293
|
+
};
|
|
2294
|
+
const generator = queryFn({ prompt, options: mergedOptions });
|
|
2295
|
+
return (async function* () {
|
|
2296
|
+
try {
|
|
2297
|
+
for await (const message of generator) {
|
|
2298
|
+
if (message.type === "system" && message.subtype === "init") {
|
|
2299
|
+
const metadata = {
|
|
2300
|
+
model: message.model,
|
|
2301
|
+
tools: message.tools,
|
|
2302
|
+
cwd: message.cwd,
|
|
2303
|
+
mcp_servers: message.mcp_servers,
|
|
2304
|
+
sdk_session_id: message.session_id,
|
|
2305
|
+
...extraMetadata ?? {}
|
|
2306
|
+
};
|
|
2307
|
+
try {
|
|
2308
|
+
sessionId = await client.createSession({
|
|
2309
|
+
name: sessionName,
|
|
2310
|
+
agentName: defaultAgent,
|
|
2311
|
+
userId,
|
|
2312
|
+
convoId,
|
|
2313
|
+
metadata
|
|
2314
|
+
});
|
|
2315
|
+
} catch {
|
|
2316
|
+
sessionId = null;
|
|
2317
|
+
}
|
|
2318
|
+
resolveSessionReady();
|
|
2319
|
+
}
|
|
2320
|
+
if (message.type === "result" && sessionId) {
|
|
2321
|
+
const isError = !!message.is_error;
|
|
2322
|
+
const inputTokens = message.usage?.input_tokens ?? 0;
|
|
2323
|
+
const outputTokens = message.usage?.output_tokens ?? 0;
|
|
2324
|
+
let failureReason;
|
|
2325
|
+
if (isError) {
|
|
2326
|
+
if (message.errors && message.errors.length > 0) {
|
|
2327
|
+
failureReason = message.errors.join("; ");
|
|
2328
|
+
} else {
|
|
2329
|
+
failureReason = message.subtype;
|
|
2330
|
+
}
|
|
2331
|
+
}
|
|
2332
|
+
await Promise.allSettled(pendingToolCalls);
|
|
2333
|
+
try {
|
|
2334
|
+
await client.completeSession({
|
|
2335
|
+
sessionId,
|
|
2336
|
+
success: !isError,
|
|
2337
|
+
failureReason,
|
|
2338
|
+
estimatedCost: message.total_cost_usd,
|
|
2339
|
+
promptTokens: inputTokens,
|
|
2340
|
+
completionTokens: outputTokens,
|
|
2341
|
+
totalTokens: inputTokens + outputTokens,
|
|
2342
|
+
durationMs: message.duration_ms ?? Date.now() - startTime,
|
|
2343
|
+
userInput: typeof prompt === "string" ? prompt : void 0,
|
|
2344
|
+
output: message.result,
|
|
2345
|
+
customMetrics: {
|
|
2346
|
+
num_turns: message.num_turns ?? 0,
|
|
2347
|
+
duration_api_ms: message.duration_api_ms ?? 0
|
|
2348
|
+
}
|
|
2349
|
+
});
|
|
2350
|
+
} catch {
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
2353
|
+
yield message;
|
|
2354
|
+
}
|
|
2355
|
+
} catch (error) {
|
|
2356
|
+
if (sessionId) {
|
|
2357
|
+
await Promise.allSettled(pendingToolCalls);
|
|
2358
|
+
try {
|
|
2359
|
+
await client.completeSession({
|
|
2360
|
+
sessionId,
|
|
2361
|
+
success: false,
|
|
2362
|
+
failureReason: error instanceof Error ? error.message : String(error),
|
|
2363
|
+
durationMs: Date.now() - startTime
|
|
2364
|
+
});
|
|
2365
|
+
} catch {
|
|
2366
|
+
}
|
|
2367
|
+
}
|
|
2368
|
+
throw error;
|
|
1603
2369
|
}
|
|
1604
|
-
})
|
|
1605
|
-
|
|
1606
|
-
});
|
|
1607
|
-
}
|
|
2370
|
+
})();
|
|
2371
|
+
};
|
|
1608
2372
|
}
|
|
1609
2373
|
|
|
1610
2374
|
// src/decorators.ts
|
|
1611
2375
|
var _defaultClient3 = null;
|
|
1612
|
-
var _currentInteraction = null;
|
|
2376
|
+
var _currentInteraction = createContextVar(null);
|
|
1613
2377
|
function getClient3() {
|
|
1614
2378
|
if (!_defaultClient3) {
|
|
1615
2379
|
try {
|
|
@@ -1629,7 +2393,7 @@ function getCurrentSessionId() {
|
|
|
1629
2393
|
return getSessionContext();
|
|
1630
2394
|
}
|
|
1631
2395
|
function getCurrentInteraction() {
|
|
1632
|
-
return _currentInteraction;
|
|
2396
|
+
return _currentInteraction.get();
|
|
1633
2397
|
}
|
|
1634
2398
|
function withTool(name, fn) {
|
|
1635
2399
|
const isAsync = fn.constructor.name === "AsyncFunction";
|
|
@@ -1730,10 +2494,11 @@ function withSession(agentName, fn, options = {}) {
|
|
|
1730
2494
|
input: userInput
|
|
1731
2495
|
});
|
|
1732
2496
|
const sessionId = interaction.getSessionId();
|
|
2497
|
+
let sessionTokens;
|
|
1733
2498
|
if (sessionId) {
|
|
1734
|
-
|
|
2499
|
+
sessionTokens = _setSessionContextWithTokens(sessionId, client);
|
|
1735
2500
|
}
|
|
1736
|
-
|
|
2501
|
+
const interactionToken = _currentInteraction.set(interaction);
|
|
1737
2502
|
try {
|
|
1738
2503
|
const result = await fn(...args);
|
|
1739
2504
|
let output;
|
|
@@ -1758,8 +2523,10 @@ function withSession(agentName, fn, options = {}) {
|
|
|
1758
2523
|
});
|
|
1759
2524
|
throw error;
|
|
1760
2525
|
} finally {
|
|
1761
|
-
|
|
1762
|
-
|
|
2526
|
+
if (sessionTokens) {
|
|
2527
|
+
_restoreSessionContext(sessionTokens);
|
|
2528
|
+
}
|
|
2529
|
+
_currentInteraction.reset(interactionToken);
|
|
1763
2530
|
}
|
|
1764
2531
|
};
|
|
1765
2532
|
}
|
|
@@ -1845,10 +2612,11 @@ function TrackSession(agentName, options) {
|
|
|
1845
2612
|
input: userInput
|
|
1846
2613
|
});
|
|
1847
2614
|
const sessionId = interaction.getSessionId();
|
|
2615
|
+
let sessionTokens;
|
|
1848
2616
|
if (sessionId) {
|
|
1849
|
-
|
|
2617
|
+
sessionTokens = _setSessionContextWithTokens(sessionId, client);
|
|
1850
2618
|
}
|
|
1851
|
-
|
|
2619
|
+
const interactionToken = _currentInteraction.set(interaction);
|
|
1852
2620
|
try {
|
|
1853
2621
|
const result = await originalMethod.apply(this, args);
|
|
1854
2622
|
let output;
|
|
@@ -1873,8 +2641,10 @@ function TrackSession(agentName, options) {
|
|
|
1873
2641
|
});
|
|
1874
2642
|
throw error;
|
|
1875
2643
|
} finally {
|
|
1876
|
-
|
|
1877
|
-
|
|
2644
|
+
if (sessionTokens) {
|
|
2645
|
+
_restoreSessionContext(sessionTokens);
|
|
2646
|
+
}
|
|
2647
|
+
_currentInteraction.reset(interactionToken);
|
|
1878
2648
|
}
|
|
1879
2649
|
};
|
|
1880
2650
|
return descriptor;
|
|
@@ -1887,6 +2657,8 @@ var SessionContext = class {
|
|
|
1887
2657
|
client;
|
|
1888
2658
|
interaction = null;
|
|
1889
2659
|
output;
|
|
2660
|
+
sessionTokens;
|
|
2661
|
+
interactionToken;
|
|
1890
2662
|
constructor(options) {
|
|
1891
2663
|
this.userId = options.userId;
|
|
1892
2664
|
this.agent = options.agent;
|
|
@@ -1905,9 +2677,9 @@ var SessionContext = class {
|
|
|
1905
2677
|
});
|
|
1906
2678
|
const sessionId = this.interaction.getSessionId();
|
|
1907
2679
|
if (sessionId) {
|
|
1908
|
-
|
|
2680
|
+
this.sessionTokens = _setSessionContextWithTokens(sessionId, this.client);
|
|
1909
2681
|
}
|
|
1910
|
-
|
|
2682
|
+
this.interactionToken = _currentInteraction.set(this.interaction);
|
|
1911
2683
|
return this;
|
|
1912
2684
|
}
|
|
1913
2685
|
/**
|
|
@@ -1927,8 +2699,12 @@ var SessionContext = class {
|
|
|
1927
2699
|
failureReason: options?.error
|
|
1928
2700
|
});
|
|
1929
2701
|
}
|
|
1930
|
-
|
|
1931
|
-
|
|
2702
|
+
if (this.sessionTokens) {
|
|
2703
|
+
_restoreSessionContext(this.sessionTokens);
|
|
2704
|
+
}
|
|
2705
|
+
if (this.interactionToken) {
|
|
2706
|
+
_currentInteraction.reset(this.interactionToken);
|
|
2707
|
+
}
|
|
1932
2708
|
}
|
|
1933
2709
|
/**
|
|
1934
2710
|
* Get the session ID
|
|
@@ -1982,30 +2758,31 @@ function serializeOutput(value) {
|
|
|
1982
2758
|
}
|
|
1983
2759
|
|
|
1984
2760
|
// src/context.ts
|
|
1985
|
-
var _experimentContext = null;
|
|
2761
|
+
var _experimentContext = createContextVar(null);
|
|
1986
2762
|
function getSystemPrompt(defaultPrompt) {
|
|
1987
|
-
|
|
1988
|
-
|
|
2763
|
+
const ctx = _experimentContext.get();
|
|
2764
|
+
if (ctx?.systemPrompt) {
|
|
2765
|
+
return ctx.systemPrompt;
|
|
1989
2766
|
}
|
|
1990
2767
|
return defaultPrompt ?? "";
|
|
1991
2768
|
}
|
|
1992
2769
|
function getExperimentContext() {
|
|
1993
|
-
return _experimentContext;
|
|
2770
|
+
return _experimentContext.get();
|
|
1994
2771
|
}
|
|
1995
2772
|
function isExperimentMode() {
|
|
1996
|
-
return _experimentContext !== null;
|
|
2773
|
+
return _experimentContext.get() !== null;
|
|
1997
2774
|
}
|
|
1998
2775
|
function getVariantName() {
|
|
1999
|
-
return _experimentContext?.variantName ?? null;
|
|
2776
|
+
return _experimentContext.get()?.variantName ?? null;
|
|
2000
2777
|
}
|
|
2001
2778
|
function getExperimentId() {
|
|
2002
|
-
return _experimentContext?.experimentId ?? null;
|
|
2779
|
+
return _experimentContext.get()?.experimentId ?? null;
|
|
2003
2780
|
}
|
|
2004
2781
|
function setExperimentContext(context) {
|
|
2005
|
-
_experimentContext
|
|
2782
|
+
_experimentContext.set(context);
|
|
2006
2783
|
}
|
|
2007
2784
|
function clearExperimentContext() {
|
|
2008
|
-
_experimentContext
|
|
2785
|
+
_experimentContext.set(null);
|
|
2009
2786
|
}
|
|
2010
2787
|
|
|
2011
2788
|
// src/experiment.ts
|
|
@@ -2338,6 +3115,7 @@ var Experiment = class {
|
|
|
2338
3115
|
};
|
|
2339
3116
|
export {
|
|
2340
3117
|
ApiError,
|
|
3118
|
+
EventBatcher,
|
|
2341
3119
|
EventType,
|
|
2342
3120
|
Experiment,
|
|
2343
3121
|
ExperimentRunTracker,
|
|
@@ -2357,6 +3135,7 @@ export {
|
|
|
2357
3135
|
clearSessionContext,
|
|
2358
3136
|
configure,
|
|
2359
3137
|
configureVercel,
|
|
3138
|
+
createContextVar,
|
|
2360
3139
|
getCurrentInteraction,
|
|
2361
3140
|
getCurrentSessionId,
|
|
2362
3141
|
getExperimentContext,
|
|
@@ -2379,6 +3158,7 @@ export {
|
|
|
2379
3158
|
withTool,
|
|
2380
3159
|
wrapAISDK,
|
|
2381
3160
|
wrapAnthropic,
|
|
3161
|
+
wrapClaudeAgent,
|
|
2382
3162
|
wrapGoogle,
|
|
2383
3163
|
wrapLLM,
|
|
2384
3164
|
wrapOpenAI
|