@sentrial/sdk 0.3.3 → 0.4.2
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 +1418 -680
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +343 -142
- package/dist/index.d.ts +343 -142
- package/dist/index.js +1414 -679
- 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,179 +305,783 @@ 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
|
-
const headers = {
|
|
345
|
-
"Content-Type": "application/json"
|
|
346
|
-
};
|
|
347
|
-
if (this.apiKey) {
|
|
348
|
-
headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
349
|
-
}
|
|
350
|
-
const finalBody = this.piiConfig && body && typeof body === "object" ? redactPayload(body, this.piiConfig) : body;
|
|
351
|
-
const controller = new AbortController();
|
|
352
|
-
const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
353
|
-
let response;
|
|
354
|
-
try {
|
|
355
|
-
response = await fetch(url, {
|
|
356
|
-
method,
|
|
357
|
-
headers,
|
|
358
|
-
body: finalBody ? JSON.stringify(finalBody) : void 0,
|
|
359
|
-
signal: controller.signal
|
|
360
|
-
});
|
|
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
|
-
}
|
|
357
|
+
try {
|
|
358
|
+
const response = await originalCreate(...args);
|
|
359
|
+
if (isStreaming) {
|
|
360
|
+
return wrapOpenAIStream(response, { startTime, messages, model, trackWithoutSession });
|
|
398
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
|
+
if (response.choices?.[0]?.message?.content) {
|
|
368
|
+
outputContent = response.choices[0].message.content;
|
|
369
|
+
}
|
|
370
|
+
const cost = calculateOpenAICost({ model, inputTokens: promptTokens, outputTokens: completionTokens });
|
|
371
|
+
trackLLMCall({
|
|
372
|
+
provider: "openai",
|
|
373
|
+
model,
|
|
374
|
+
messages,
|
|
375
|
+
output: outputContent,
|
|
376
|
+
promptTokens,
|
|
377
|
+
completionTokens,
|
|
378
|
+
totalTokens,
|
|
379
|
+
cost,
|
|
380
|
+
durationMs,
|
|
381
|
+
trackWithoutSession
|
|
382
|
+
});
|
|
383
|
+
return response;
|
|
384
|
+
} catch (error) {
|
|
385
|
+
const durationMs = Date.now() - startTime;
|
|
386
|
+
trackLLMError({
|
|
387
|
+
provider: "openai",
|
|
388
|
+
model,
|
|
389
|
+
messages,
|
|
390
|
+
error,
|
|
391
|
+
durationMs,
|
|
392
|
+
trackWithoutSession
|
|
393
|
+
});
|
|
394
|
+
throw error;
|
|
399
395
|
}
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
396
|
+
};
|
|
397
|
+
return client;
|
|
398
|
+
}
|
|
399
|
+
function wrapAnthropic(client, options = {}) {
|
|
400
|
+
const { trackWithoutSession = false } = options;
|
|
401
|
+
const messages = client.messages;
|
|
402
|
+
if (!messages?.create) {
|
|
403
|
+
console.warn("Sentrial: Anthropic client does not have messages.create");
|
|
404
|
+
return client;
|
|
409
405
|
}
|
|
410
|
-
|
|
411
|
-
|
|
406
|
+
const originalCreate = messages.create.bind(messages);
|
|
407
|
+
messages.create = async function(...args) {
|
|
408
|
+
const startTime = Date.now();
|
|
409
|
+
const params = args[0] ?? {};
|
|
410
|
+
const inputMessages = params.messages ?? [];
|
|
411
|
+
const model = params.model ?? "unknown";
|
|
412
|
+
const system = params.system ?? "";
|
|
413
|
+
const isStreaming = params.stream === true;
|
|
414
|
+
try {
|
|
415
|
+
const response = await originalCreate(...args);
|
|
416
|
+
if (isStreaming) {
|
|
417
|
+
return wrapAnthropicStream(response, {
|
|
418
|
+
startTime,
|
|
419
|
+
messages: inputMessages,
|
|
420
|
+
model,
|
|
421
|
+
system,
|
|
422
|
+
trackWithoutSession
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
const durationMs = Date.now() - startTime;
|
|
426
|
+
const promptTokens = response.usage?.input_tokens ?? 0;
|
|
427
|
+
const completionTokens = response.usage?.output_tokens ?? 0;
|
|
428
|
+
const totalTokens = promptTokens + completionTokens;
|
|
429
|
+
let outputContent = "";
|
|
430
|
+
if (response.content) {
|
|
431
|
+
for (const block of response.content) {
|
|
432
|
+
if (block.type === "text") {
|
|
433
|
+
outputContent += block.text;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
const cost = calculateAnthropicCost({ model, inputTokens: promptTokens, outputTokens: completionTokens });
|
|
438
|
+
const fullMessages = system ? [{ role: "system", content: system }, ...inputMessages] : inputMessages;
|
|
439
|
+
trackLLMCall({
|
|
440
|
+
provider: "anthropic",
|
|
441
|
+
model,
|
|
442
|
+
messages: fullMessages,
|
|
443
|
+
output: outputContent,
|
|
444
|
+
promptTokens,
|
|
445
|
+
completionTokens,
|
|
446
|
+
totalTokens,
|
|
447
|
+
cost,
|
|
448
|
+
durationMs,
|
|
449
|
+
trackWithoutSession
|
|
450
|
+
});
|
|
451
|
+
return response;
|
|
452
|
+
} catch (error) {
|
|
453
|
+
const durationMs = Date.now() - startTime;
|
|
454
|
+
trackLLMError({
|
|
455
|
+
provider: "anthropic",
|
|
456
|
+
model,
|
|
457
|
+
messages: inputMessages,
|
|
458
|
+
error,
|
|
459
|
+
durationMs,
|
|
460
|
+
trackWithoutSession
|
|
461
|
+
});
|
|
462
|
+
throw error;
|
|
463
|
+
}
|
|
464
|
+
};
|
|
465
|
+
return client;
|
|
466
|
+
}
|
|
467
|
+
function wrapGoogle(model, options = {}) {
|
|
468
|
+
const { trackWithoutSession = false } = options;
|
|
469
|
+
const originalGenerate = model.generateContent;
|
|
470
|
+
if (!originalGenerate) {
|
|
471
|
+
console.warn("Sentrial: Google model does not have generateContent");
|
|
472
|
+
return model;
|
|
473
|
+
}
|
|
474
|
+
model.generateContent = async function(...args) {
|
|
475
|
+
const startTime = Date.now();
|
|
476
|
+
const contents = args[0];
|
|
477
|
+
const modelName = model.model ?? "gemini-unknown";
|
|
478
|
+
const messages = googleContentsToMessages(contents);
|
|
479
|
+
try {
|
|
480
|
+
const response = await originalGenerate.apply(model, args);
|
|
481
|
+
const durationMs = Date.now() - startTime;
|
|
482
|
+
let promptTokens = 0;
|
|
483
|
+
let completionTokens = 0;
|
|
484
|
+
const usageMeta = response.response?.usageMetadata ?? response.usageMetadata;
|
|
485
|
+
if (usageMeta) {
|
|
486
|
+
promptTokens = usageMeta.promptTokenCount ?? 0;
|
|
487
|
+
completionTokens = usageMeta.candidatesTokenCount ?? 0;
|
|
488
|
+
}
|
|
489
|
+
const totalTokens = promptTokens + completionTokens;
|
|
490
|
+
let outputContent = "";
|
|
491
|
+
try {
|
|
492
|
+
outputContent = response.response?.text?.() ?? response.text?.() ?? "";
|
|
493
|
+
} catch {
|
|
494
|
+
}
|
|
495
|
+
const cost = calculateGoogleCost({ model: modelName, inputTokens: promptTokens, outputTokens: completionTokens });
|
|
496
|
+
trackLLMCall({
|
|
497
|
+
provider: "google",
|
|
498
|
+
model: modelName,
|
|
499
|
+
messages,
|
|
500
|
+
output: outputContent,
|
|
501
|
+
promptTokens,
|
|
502
|
+
completionTokens,
|
|
503
|
+
totalTokens,
|
|
504
|
+
cost,
|
|
505
|
+
durationMs,
|
|
506
|
+
trackWithoutSession
|
|
507
|
+
});
|
|
508
|
+
return response;
|
|
509
|
+
} catch (error) {
|
|
510
|
+
const durationMs = Date.now() - startTime;
|
|
511
|
+
trackLLMError({
|
|
512
|
+
provider: "google",
|
|
513
|
+
model: modelName,
|
|
514
|
+
messages,
|
|
515
|
+
error,
|
|
516
|
+
durationMs,
|
|
517
|
+
trackWithoutSession
|
|
518
|
+
});
|
|
519
|
+
throw error;
|
|
520
|
+
}
|
|
521
|
+
};
|
|
522
|
+
return model;
|
|
523
|
+
}
|
|
524
|
+
function googleContentsToMessages(contents) {
|
|
525
|
+
if (typeof contents === "string") {
|
|
526
|
+
return [{ role: "user", content: contents }];
|
|
527
|
+
}
|
|
528
|
+
if (Array.isArray(contents)) {
|
|
529
|
+
return contents.map((item) => {
|
|
530
|
+
if (typeof item === "string") {
|
|
531
|
+
return { role: "user", content: item };
|
|
532
|
+
}
|
|
533
|
+
if (item && typeof item === "object") {
|
|
534
|
+
return { role: item.role ?? "user", content: String(item.content ?? item) };
|
|
535
|
+
}
|
|
536
|
+
return { role: "user", content: String(item) };
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
return [{ role: "user", content: String(contents) }];
|
|
540
|
+
}
|
|
541
|
+
function wrapLLM(client, provider) {
|
|
542
|
+
if (provider === "openai" || client.chat?.completions?.create) {
|
|
543
|
+
return wrapOpenAI(client);
|
|
544
|
+
}
|
|
545
|
+
if (provider === "anthropic" || client.messages?.create) {
|
|
546
|
+
return wrapAnthropic(client);
|
|
547
|
+
}
|
|
548
|
+
if (provider === "google" || client.generateContent) {
|
|
549
|
+
return wrapGoogle(client);
|
|
550
|
+
}
|
|
551
|
+
console.warn("Sentrial: Unknown LLM client type. No auto-tracking applied.");
|
|
552
|
+
return client;
|
|
553
|
+
}
|
|
554
|
+
function wrapOpenAIStream(stream, ctx) {
|
|
555
|
+
let fullContent = "";
|
|
556
|
+
let usage = null;
|
|
557
|
+
let tracked = false;
|
|
558
|
+
const originalIterator = stream[Symbol.asyncIterator]?.bind(stream);
|
|
559
|
+
if (!originalIterator) return stream;
|
|
560
|
+
const trackResult = () => {
|
|
561
|
+
if (tracked) return;
|
|
562
|
+
tracked = true;
|
|
563
|
+
const durationMs = Date.now() - ctx.startTime;
|
|
564
|
+
const promptTokens = usage?.prompt_tokens ?? 0;
|
|
565
|
+
const completionTokens = usage?.completion_tokens ?? 0;
|
|
566
|
+
const totalTokens = usage?.total_tokens ?? promptTokens + completionTokens;
|
|
567
|
+
const cost = calculateOpenAICost({ model: ctx.model, inputTokens: promptTokens, outputTokens: completionTokens });
|
|
568
|
+
trackLLMCall({
|
|
569
|
+
provider: "openai",
|
|
570
|
+
model: ctx.model,
|
|
571
|
+
messages: ctx.messages,
|
|
572
|
+
output: fullContent,
|
|
573
|
+
promptTokens,
|
|
574
|
+
completionTokens,
|
|
575
|
+
totalTokens,
|
|
576
|
+
cost,
|
|
577
|
+
durationMs,
|
|
578
|
+
trackWithoutSession: ctx.trackWithoutSession
|
|
579
|
+
});
|
|
580
|
+
};
|
|
581
|
+
return new Proxy(stream, {
|
|
582
|
+
get(target, prop, receiver) {
|
|
583
|
+
if (prop === Symbol.asyncIterator) {
|
|
584
|
+
return function() {
|
|
585
|
+
const iter = originalIterator();
|
|
586
|
+
return {
|
|
587
|
+
async next() {
|
|
588
|
+
const result = await iter.next();
|
|
589
|
+
if (!result.done) {
|
|
590
|
+
const chunk = result.value;
|
|
591
|
+
const delta = chunk.choices?.[0]?.delta?.content;
|
|
592
|
+
if (delta) fullContent += delta;
|
|
593
|
+
if (chunk.usage) usage = chunk.usage;
|
|
594
|
+
} else {
|
|
595
|
+
trackResult();
|
|
596
|
+
}
|
|
597
|
+
return result;
|
|
598
|
+
},
|
|
599
|
+
async return(value) {
|
|
600
|
+
trackResult();
|
|
601
|
+
return iter.return?.(value) ?? { done: true, value: void 0 };
|
|
602
|
+
},
|
|
603
|
+
async throw(error) {
|
|
604
|
+
return iter.throw?.(error) ?? { done: true, value: void 0 };
|
|
605
|
+
}
|
|
606
|
+
};
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
return Reflect.get(target, prop, receiver);
|
|
610
|
+
}
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
function wrapAnthropicStream(stream, ctx) {
|
|
614
|
+
let fullContent = "";
|
|
615
|
+
let inputTokens = 0;
|
|
616
|
+
let outputTokens = 0;
|
|
617
|
+
let tracked = false;
|
|
618
|
+
const originalIterator = stream[Symbol.asyncIterator]?.bind(stream);
|
|
619
|
+
if (!originalIterator) return stream;
|
|
620
|
+
const trackResult = () => {
|
|
621
|
+
if (tracked) return;
|
|
622
|
+
tracked = true;
|
|
623
|
+
const durationMs = Date.now() - ctx.startTime;
|
|
624
|
+
const totalTokens = inputTokens + outputTokens;
|
|
625
|
+
const cost = calculateAnthropicCost({ model: ctx.model, inputTokens, outputTokens });
|
|
626
|
+
const fullMessages = ctx.system ? [{ role: "system", content: ctx.system }, ...ctx.messages] : ctx.messages;
|
|
627
|
+
trackLLMCall({
|
|
628
|
+
provider: "anthropic",
|
|
629
|
+
model: ctx.model,
|
|
630
|
+
messages: fullMessages,
|
|
631
|
+
output: fullContent,
|
|
632
|
+
promptTokens: inputTokens,
|
|
633
|
+
completionTokens: outputTokens,
|
|
634
|
+
totalTokens,
|
|
635
|
+
cost,
|
|
636
|
+
durationMs,
|
|
637
|
+
trackWithoutSession: ctx.trackWithoutSession
|
|
638
|
+
});
|
|
639
|
+
};
|
|
640
|
+
return new Proxy(stream, {
|
|
641
|
+
get(target, prop, receiver) {
|
|
642
|
+
if (prop === Symbol.asyncIterator) {
|
|
643
|
+
return function() {
|
|
644
|
+
const iter = originalIterator();
|
|
645
|
+
return {
|
|
646
|
+
async next() {
|
|
647
|
+
const result = await iter.next();
|
|
648
|
+
if (!result.done) {
|
|
649
|
+
const event = result.value;
|
|
650
|
+
if (event.type === "content_block_delta" && event.delta?.text) {
|
|
651
|
+
fullContent += event.delta.text;
|
|
652
|
+
}
|
|
653
|
+
if (event.type === "message_start" && event.message?.usage) {
|
|
654
|
+
inputTokens = event.message.usage.input_tokens ?? 0;
|
|
655
|
+
}
|
|
656
|
+
if (event.type === "message_delta" && event.usage) {
|
|
657
|
+
outputTokens = event.usage.output_tokens ?? 0;
|
|
658
|
+
}
|
|
659
|
+
} else {
|
|
660
|
+
trackResult();
|
|
661
|
+
}
|
|
662
|
+
return result;
|
|
663
|
+
},
|
|
664
|
+
async return(value) {
|
|
665
|
+
trackResult();
|
|
666
|
+
return iter.return?.(value) ?? { done: true, value: void 0 };
|
|
667
|
+
},
|
|
668
|
+
async throw(error) {
|
|
669
|
+
return iter.throw?.(error) ?? { done: true, value: void 0 };
|
|
670
|
+
}
|
|
671
|
+
};
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
return Reflect.get(target, prop, receiver);
|
|
675
|
+
}
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
function trackLLMCall(params) {
|
|
679
|
+
const client = getTrackingClient();
|
|
680
|
+
if (!client) return;
|
|
681
|
+
const sessionId = _currentSessionId.get();
|
|
682
|
+
if (!sessionId && !params.trackWithoutSession) {
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
if (sessionId) {
|
|
686
|
+
client.trackToolCall({
|
|
687
|
+
sessionId,
|
|
688
|
+
toolName: `llm:${params.provider}:${params.model}`,
|
|
689
|
+
toolInput: {
|
|
690
|
+
messages: params.messages,
|
|
691
|
+
model: params.model,
|
|
692
|
+
provider: params.provider
|
|
693
|
+
},
|
|
694
|
+
toolOutput: {
|
|
695
|
+
content: params.output,
|
|
696
|
+
tokens: {
|
|
697
|
+
prompt: params.promptTokens,
|
|
698
|
+
completion: params.completionTokens,
|
|
699
|
+
total: params.totalTokens
|
|
700
|
+
},
|
|
701
|
+
cost_usd: params.cost
|
|
702
|
+
},
|
|
703
|
+
reasoning: `LLM call to ${params.provider} ${params.model}`,
|
|
704
|
+
estimatedCost: params.cost,
|
|
705
|
+
tokenCount: params.totalTokens,
|
|
706
|
+
metadata: {
|
|
707
|
+
provider: params.provider,
|
|
708
|
+
model: params.model,
|
|
709
|
+
duration_ms: params.durationMs,
|
|
710
|
+
prompt_tokens: params.promptTokens,
|
|
711
|
+
completion_tokens: params.completionTokens
|
|
712
|
+
}
|
|
713
|
+
}).catch((err) => {
|
|
714
|
+
console.warn("Sentrial: Failed to track LLM call:", err.message);
|
|
715
|
+
});
|
|
716
|
+
} else if (params.trackWithoutSession) {
|
|
717
|
+
client.createSession({
|
|
718
|
+
name: `LLM: ${params.provider}/${params.model}`,
|
|
719
|
+
agentName: `${params.provider}-wrapper`,
|
|
720
|
+
userId: "anonymous"
|
|
721
|
+
}).then((sid) => {
|
|
722
|
+
if (!sid) return;
|
|
723
|
+
return client.trackToolCall({
|
|
724
|
+
sessionId: sid,
|
|
725
|
+
toolName: `llm:${params.provider}:${params.model}`,
|
|
726
|
+
toolInput: {
|
|
727
|
+
messages: params.messages,
|
|
728
|
+
model: params.model,
|
|
729
|
+
provider: params.provider
|
|
730
|
+
},
|
|
731
|
+
toolOutput: {
|
|
732
|
+
content: params.output,
|
|
733
|
+
tokens: {
|
|
734
|
+
prompt: params.promptTokens,
|
|
735
|
+
completion: params.completionTokens,
|
|
736
|
+
total: params.totalTokens
|
|
737
|
+
},
|
|
738
|
+
cost_usd: params.cost
|
|
739
|
+
},
|
|
740
|
+
estimatedCost: params.cost,
|
|
741
|
+
tokenCount: params.totalTokens,
|
|
742
|
+
metadata: {
|
|
743
|
+
provider: params.provider,
|
|
744
|
+
model: params.model,
|
|
745
|
+
duration_ms: params.durationMs
|
|
746
|
+
}
|
|
747
|
+
}).then(() => {
|
|
748
|
+
return client.completeSession({
|
|
749
|
+
sessionId: sid,
|
|
750
|
+
success: true,
|
|
751
|
+
estimatedCost: params.cost,
|
|
752
|
+
promptTokens: params.promptTokens,
|
|
753
|
+
completionTokens: params.completionTokens,
|
|
754
|
+
totalTokens: params.totalTokens,
|
|
755
|
+
durationMs: params.durationMs
|
|
756
|
+
});
|
|
757
|
+
});
|
|
758
|
+
}).catch((err) => {
|
|
759
|
+
console.warn("Sentrial: Failed to track standalone LLM call:", err.message);
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
function trackLLMError(params) {
|
|
764
|
+
const client = getTrackingClient();
|
|
765
|
+
if (!client) return;
|
|
766
|
+
const sessionId = _currentSessionId.get();
|
|
767
|
+
if (!sessionId && !params.trackWithoutSession) {
|
|
768
|
+
return;
|
|
769
|
+
}
|
|
770
|
+
if (!sessionId) return;
|
|
771
|
+
client.trackError({
|
|
772
|
+
sessionId,
|
|
773
|
+
errorMessage: params.error.message,
|
|
774
|
+
errorType: params.error.name,
|
|
775
|
+
toolName: `llm:${params.provider}:${params.model}`,
|
|
776
|
+
metadata: {
|
|
777
|
+
provider: params.provider,
|
|
778
|
+
model: params.model,
|
|
779
|
+
duration_ms: params.durationMs
|
|
780
|
+
}
|
|
781
|
+
}).catch((err) => {
|
|
782
|
+
console.warn("Sentrial: Failed to track LLM error:", err.message);
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
// src/batcher.ts
|
|
787
|
+
var EventBatcher = class {
|
|
788
|
+
queue = [];
|
|
789
|
+
flushIntervalMs;
|
|
790
|
+
flushThreshold;
|
|
791
|
+
maxQueueSize;
|
|
792
|
+
timer = null;
|
|
793
|
+
sendFn;
|
|
794
|
+
flushing = false;
|
|
795
|
+
shutdownCalled = false;
|
|
796
|
+
exitHandler;
|
|
797
|
+
constructor(sendFn, config = {}) {
|
|
798
|
+
this.sendFn = sendFn;
|
|
799
|
+
this.flushIntervalMs = config.flushIntervalMs ?? 1e3;
|
|
800
|
+
this.flushThreshold = config.flushThreshold ?? 10;
|
|
801
|
+
this.maxQueueSize = config.maxQueueSize ?? 1e3;
|
|
802
|
+
this.timer = setInterval(() => {
|
|
803
|
+
void this.flush();
|
|
804
|
+
}, this.flushIntervalMs);
|
|
805
|
+
if (this.timer && typeof this.timer === "object" && "unref" in this.timer) {
|
|
806
|
+
this.timer.unref();
|
|
807
|
+
}
|
|
808
|
+
this.exitHandler = () => {
|
|
809
|
+
void this.shutdown();
|
|
810
|
+
};
|
|
811
|
+
if (typeof process !== "undefined" && process.on) {
|
|
812
|
+
process.on("beforeExit", this.exitHandler);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
/**
|
|
816
|
+
* Enqueue an event for batched delivery.
|
|
817
|
+
*
|
|
818
|
+
* If the queue hits `flushThreshold`, an automatic flush is triggered.
|
|
819
|
+
* If the queue is full (`maxQueueSize`), the oldest event is dropped.
|
|
820
|
+
*/
|
|
821
|
+
enqueue(method, url, body) {
|
|
822
|
+
if (this.shutdownCalled) return;
|
|
823
|
+
if (this.queue.length >= this.maxQueueSize) {
|
|
824
|
+
this.queue.shift();
|
|
825
|
+
if (typeof console !== "undefined") {
|
|
826
|
+
console.warn(
|
|
827
|
+
`Sentrial: Event queue full (${this.maxQueueSize}), dropping oldest event`
|
|
828
|
+
);
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
this.queue.push({ method, url, body });
|
|
832
|
+
if (this.queue.length >= this.flushThreshold) {
|
|
833
|
+
void this.flush();
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
/**
|
|
837
|
+
* Flush all queued events to the API.
|
|
838
|
+
*
|
|
839
|
+
* Drains the queue and fires all requests in parallel. Safe to call
|
|
840
|
+
* concurrently — only one flush runs at a time.
|
|
841
|
+
*/
|
|
842
|
+
async flush() {
|
|
843
|
+
if (this.flushing || this.queue.length === 0) return;
|
|
844
|
+
this.flushing = true;
|
|
845
|
+
const batch = this.queue.splice(0, this.queue.length);
|
|
846
|
+
try {
|
|
847
|
+
await Promise.all(
|
|
848
|
+
batch.map(
|
|
849
|
+
(event) => this.sendFn(event.method, event.url, event.body).catch((err) => {
|
|
850
|
+
if (typeof console !== "undefined") {
|
|
851
|
+
console.warn("Sentrial: Batched event failed:", err);
|
|
852
|
+
}
|
|
853
|
+
})
|
|
854
|
+
)
|
|
855
|
+
);
|
|
856
|
+
} finally {
|
|
857
|
+
this.flushing = false;
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
/**
|
|
861
|
+
* Stop the batcher: clear the timer, flush remaining events, remove exit handler.
|
|
862
|
+
*/
|
|
863
|
+
async shutdown() {
|
|
864
|
+
if (this.shutdownCalled) return;
|
|
865
|
+
this.shutdownCalled = true;
|
|
866
|
+
if (this.timer !== null) {
|
|
867
|
+
clearInterval(this.timer);
|
|
868
|
+
this.timer = null;
|
|
869
|
+
}
|
|
870
|
+
if (typeof process !== "undefined" && process.removeListener) {
|
|
871
|
+
process.removeListener("beforeExit", this.exitHandler);
|
|
872
|
+
}
|
|
873
|
+
this.flushing = false;
|
|
874
|
+
await this.flush();
|
|
875
|
+
}
|
|
876
|
+
/** Number of events currently queued. */
|
|
877
|
+
get size() {
|
|
878
|
+
return this.queue.length;
|
|
879
|
+
}
|
|
880
|
+
};
|
|
881
|
+
|
|
882
|
+
// src/types.ts
|
|
883
|
+
var EventType = /* @__PURE__ */ ((EventType2) => {
|
|
884
|
+
EventType2["TOOL_CALL"] = "tool_call";
|
|
885
|
+
EventType2["LLM_DECISION"] = "llm_decision";
|
|
886
|
+
EventType2["STATE_CHANGE"] = "state_change";
|
|
887
|
+
EventType2["ERROR"] = "error";
|
|
888
|
+
return EventType2;
|
|
889
|
+
})(EventType || {});
|
|
890
|
+
|
|
891
|
+
// src/client.ts
|
|
892
|
+
var DEFAULT_API_URL = "https://api.sentrial.com";
|
|
893
|
+
var MAX_RETRIES = 3;
|
|
894
|
+
var INITIAL_BACKOFF_MS = 500;
|
|
895
|
+
var MAX_BACKOFF_MS = 8e3;
|
|
896
|
+
var BACKOFF_MULTIPLIER = 2;
|
|
897
|
+
var RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([408, 429, 500, 502, 503, 504]);
|
|
898
|
+
var REQUEST_TIMEOUT_MS = 1e4;
|
|
899
|
+
var SentrialClient = class {
|
|
900
|
+
apiUrl;
|
|
901
|
+
apiKey;
|
|
902
|
+
failSilently;
|
|
903
|
+
piiConfig;
|
|
904
|
+
piiConfigNeedsHydration = false;
|
|
905
|
+
piiHydrationPromise;
|
|
906
|
+
_stateVar = createContextVar({});
|
|
907
|
+
batcher;
|
|
908
|
+
/** Per-session cost/token accumulator — populated by trackToolCall/trackDecision */
|
|
909
|
+
sessionAccumulators = /* @__PURE__ */ new Map();
|
|
910
|
+
get currentState() {
|
|
911
|
+
return this._stateVar.get();
|
|
912
|
+
}
|
|
913
|
+
set currentState(value) {
|
|
914
|
+
this._stateVar.set(value);
|
|
915
|
+
}
|
|
916
|
+
constructor(config = {}) {
|
|
917
|
+
this.apiUrl = (config.apiUrl ?? (typeof process !== "undefined" ? process.env?.SENTRIAL_API_URL : void 0) ?? DEFAULT_API_URL).replace(/\/$/, "");
|
|
918
|
+
this.apiKey = config.apiKey ?? (typeof process !== "undefined" ? process.env?.SENTRIAL_API_KEY : void 0);
|
|
919
|
+
this.failSilently = config.failSilently ?? true;
|
|
920
|
+
if (config.pii === true) {
|
|
921
|
+
this.piiConfig = { enabled: true };
|
|
922
|
+
this.piiConfigNeedsHydration = true;
|
|
923
|
+
} else if (config.pii && typeof config.pii === "object") {
|
|
924
|
+
this.piiConfig = config.pii;
|
|
925
|
+
this.piiConfigNeedsHydration = false;
|
|
926
|
+
}
|
|
927
|
+
if (config.batching?.enabled) {
|
|
928
|
+
this.batcher = new EventBatcher(
|
|
929
|
+
(method, url, body) => this.safeRequest(method, url, body),
|
|
930
|
+
config.batching
|
|
931
|
+
);
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
/**
|
|
935
|
+
* Fetch the organization's PII config from the server.
|
|
936
|
+
*
|
|
937
|
+
* Called lazily on the first request when `pii: true` was passed to the constructor.
|
|
938
|
+
* Uses a single shared promise so concurrent requests don't trigger duplicate fetches.
|
|
939
|
+
*/
|
|
940
|
+
async hydratePiiConfig() {
|
|
941
|
+
if (!this.piiConfigNeedsHydration) return;
|
|
942
|
+
if (this.piiHydrationPromise) {
|
|
943
|
+
await this.piiHydrationPromise;
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
946
|
+
this.piiHydrationPromise = (async () => {
|
|
947
|
+
try {
|
|
948
|
+
const headers = {};
|
|
949
|
+
if (this.apiKey) {
|
|
950
|
+
headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
951
|
+
}
|
|
952
|
+
const controller = new AbortController();
|
|
953
|
+
const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
954
|
+
let response;
|
|
955
|
+
try {
|
|
956
|
+
response = await fetch(`${this.apiUrl}/api/sdk/pii-config`, {
|
|
957
|
+
method: "GET",
|
|
958
|
+
headers,
|
|
959
|
+
signal: controller.signal
|
|
960
|
+
});
|
|
961
|
+
} finally {
|
|
962
|
+
clearTimeout(timeoutId);
|
|
963
|
+
}
|
|
964
|
+
if (response.ok) {
|
|
965
|
+
const data = await response.json();
|
|
966
|
+
if (data.config) {
|
|
967
|
+
this.piiConfig = {
|
|
968
|
+
enabled: data.config.enabled,
|
|
969
|
+
mode: data.config.mode,
|
|
970
|
+
fields: data.config.fields,
|
|
971
|
+
builtinPatterns: data.config.builtinPatterns,
|
|
972
|
+
customPatterns: (data.config.customPatterns || []).map(
|
|
973
|
+
(cp) => ({
|
|
974
|
+
pattern: new RegExp(cp.pattern, "g"),
|
|
975
|
+
label: cp.label
|
|
976
|
+
})
|
|
977
|
+
),
|
|
978
|
+
enhancedDetection: data.config.enhancedDetection
|
|
979
|
+
};
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
} catch {
|
|
983
|
+
}
|
|
984
|
+
this.piiConfigNeedsHydration = false;
|
|
985
|
+
})();
|
|
986
|
+
await this.piiHydrationPromise;
|
|
987
|
+
}
|
|
988
|
+
/**
|
|
989
|
+
* Make an HTTP request with retry logic and exponential backoff.
|
|
990
|
+
*
|
|
991
|
+
* Retries on transient failures (network errors, timeouts, 429/5xx).
|
|
992
|
+
* Up to MAX_RETRIES attempts with exponential backoff.
|
|
993
|
+
*/
|
|
994
|
+
async safeRequest(method, url, body) {
|
|
995
|
+
if (this.piiConfigNeedsHydration) {
|
|
996
|
+
await this.hydratePiiConfig();
|
|
997
|
+
}
|
|
998
|
+
let lastError;
|
|
999
|
+
let backoff = INITIAL_BACKOFF_MS;
|
|
1000
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
1001
|
+
try {
|
|
1002
|
+
const headers = {
|
|
1003
|
+
"Content-Type": "application/json"
|
|
1004
|
+
};
|
|
1005
|
+
if (this.apiKey) {
|
|
1006
|
+
headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
1007
|
+
}
|
|
1008
|
+
const finalBody = this.piiConfig && body && typeof body === "object" ? redactPayload(body, this.piiConfig) : body;
|
|
1009
|
+
const controller = new AbortController();
|
|
1010
|
+
const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
1011
|
+
let response;
|
|
1012
|
+
try {
|
|
1013
|
+
response = await fetch(url, {
|
|
1014
|
+
method,
|
|
1015
|
+
headers,
|
|
1016
|
+
body: finalBody ? JSON.stringify(finalBody) : void 0,
|
|
1017
|
+
signal: controller.signal
|
|
1018
|
+
});
|
|
1019
|
+
} finally {
|
|
1020
|
+
clearTimeout(timeoutId);
|
|
1021
|
+
}
|
|
1022
|
+
if (RETRYABLE_STATUS_CODES.has(response.status) && attempt < MAX_RETRIES) {
|
|
1023
|
+
await this.sleep(backoff);
|
|
1024
|
+
backoff = Math.min(backoff * BACKOFF_MULTIPLIER, MAX_BACKOFF_MS);
|
|
1025
|
+
continue;
|
|
1026
|
+
}
|
|
1027
|
+
if (!response.ok) {
|
|
1028
|
+
const errorBody = await response.text();
|
|
1029
|
+
let errorData = {};
|
|
1030
|
+
try {
|
|
1031
|
+
errorData = JSON.parse(errorBody);
|
|
1032
|
+
} catch {
|
|
1033
|
+
}
|
|
1034
|
+
const error = new ApiError(
|
|
1035
|
+
errorData.error?.message || `HTTP ${response.status}: ${response.statusText}`,
|
|
1036
|
+
response.status,
|
|
1037
|
+
errorData.error?.code
|
|
1038
|
+
);
|
|
1039
|
+
if (this.failSilently) {
|
|
1040
|
+
console.warn(`Sentrial: Request failed (${method} ${url}):`, error.message);
|
|
1041
|
+
return null;
|
|
1042
|
+
}
|
|
1043
|
+
throw error;
|
|
1044
|
+
}
|
|
1045
|
+
return await response.json();
|
|
1046
|
+
} catch (error) {
|
|
1047
|
+
if (error instanceof ApiError) {
|
|
1048
|
+
throw error;
|
|
1049
|
+
}
|
|
1050
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
1051
|
+
if (attempt < MAX_RETRIES) {
|
|
1052
|
+
await this.sleep(backoff);
|
|
1053
|
+
backoff = Math.min(backoff * BACKOFF_MULTIPLIER, MAX_BACKOFF_MS);
|
|
1054
|
+
continue;
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
const networkError = new NetworkError(
|
|
1059
|
+
lastError?.message ?? "Unknown network error",
|
|
1060
|
+
lastError
|
|
1061
|
+
);
|
|
1062
|
+
if (this.failSilently) {
|
|
1063
|
+
console.warn(`Sentrial: Request failed after ${MAX_RETRIES + 1} attempts (${method} ${url}):`, networkError.message);
|
|
1064
|
+
return null;
|
|
1065
|
+
}
|
|
1066
|
+
throw networkError;
|
|
1067
|
+
}
|
|
1068
|
+
sleep(ms) {
|
|
1069
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1070
|
+
}
|
|
1071
|
+
accumulate(sessionId, cost, tokenCount, toolOutput) {
|
|
1072
|
+
let acc = this.sessionAccumulators.get(sessionId);
|
|
1073
|
+
if (!acc) {
|
|
1074
|
+
acc = { cost: 0, promptTokens: 0, completionTokens: 0, totalTokens: 0 };
|
|
1075
|
+
this.sessionAccumulators.set(sessionId, acc);
|
|
1076
|
+
}
|
|
1077
|
+
if (cost != null) acc.cost += cost;
|
|
1078
|
+
if (tokenCount != null) acc.totalTokens += tokenCount;
|
|
1079
|
+
const rawTokens = toolOutput?.tokens;
|
|
1080
|
+
if (rawTokens && typeof rawTokens === "object" && !Array.isArray(rawTokens)) {
|
|
1081
|
+
const tokens = rawTokens;
|
|
1082
|
+
if (typeof tokens.prompt === "number") acc.promptTokens += tokens.prompt;
|
|
1083
|
+
if (typeof tokens.completion === "number") acc.completionTokens += tokens.completion;
|
|
1084
|
+
}
|
|
412
1085
|
}
|
|
413
1086
|
/**
|
|
414
1087
|
* Create a new session
|
|
@@ -444,6 +1117,7 @@ var SentrialClient = class {
|
|
|
444
1117
|
* @returns Event data
|
|
445
1118
|
*/
|
|
446
1119
|
async trackToolCall(params) {
|
|
1120
|
+
this.accumulate(params.sessionId, params.estimatedCost, params.tokenCount, params.toolOutput);
|
|
447
1121
|
const stateBefore = { ...this.currentState };
|
|
448
1122
|
this.currentState[`${params.toolName}_result`] = params.toolOutput;
|
|
449
1123
|
const payload = {
|
|
@@ -462,6 +1136,10 @@ var SentrialClient = class {
|
|
|
462
1136
|
if (params.traceId !== void 0) payload.traceId = params.traceId;
|
|
463
1137
|
if (params.spanId !== void 0) payload.spanId = params.spanId;
|
|
464
1138
|
if (params.metadata !== void 0) payload.metadata = params.metadata;
|
|
1139
|
+
if (this.batcher) {
|
|
1140
|
+
this.batcher.enqueue("POST", `${this.apiUrl}/api/sdk/events`, payload);
|
|
1141
|
+
return null;
|
|
1142
|
+
}
|
|
465
1143
|
return this.safeRequest("POST", `${this.apiUrl}/api/sdk/events`, payload);
|
|
466
1144
|
}
|
|
467
1145
|
/**
|
|
@@ -471,6 +1149,7 @@ var SentrialClient = class {
|
|
|
471
1149
|
* @returns Event data
|
|
472
1150
|
*/
|
|
473
1151
|
async trackDecision(params) {
|
|
1152
|
+
this.accumulate(params.sessionId, params.estimatedCost, params.tokenCount);
|
|
474
1153
|
const stateBefore = { ...this.currentState };
|
|
475
1154
|
const payload = {
|
|
476
1155
|
sessionId: params.sessionId,
|
|
@@ -486,6 +1165,10 @@ var SentrialClient = class {
|
|
|
486
1165
|
if (params.traceId !== void 0) payload.traceId = params.traceId;
|
|
487
1166
|
if (params.spanId !== void 0) payload.spanId = params.spanId;
|
|
488
1167
|
if (params.metadata !== void 0) payload.metadata = params.metadata;
|
|
1168
|
+
if (this.batcher) {
|
|
1169
|
+
this.batcher.enqueue("POST", `${this.apiUrl}/api/sdk/events`, payload);
|
|
1170
|
+
return null;
|
|
1171
|
+
}
|
|
489
1172
|
return this.safeRequest("POST", `${this.apiUrl}/api/sdk/events`, payload);
|
|
490
1173
|
}
|
|
491
1174
|
/**
|
|
@@ -512,6 +1195,10 @@ var SentrialClient = class {
|
|
|
512
1195
|
if (params.traceId !== void 0) payload.traceId = params.traceId;
|
|
513
1196
|
if (params.spanId !== void 0) payload.spanId = params.spanId;
|
|
514
1197
|
if (params.metadata !== void 0) payload.metadata = params.metadata;
|
|
1198
|
+
if (this.batcher) {
|
|
1199
|
+
this.batcher.enqueue("POST", `${this.apiUrl}/api/sdk/events`, payload);
|
|
1200
|
+
return null;
|
|
1201
|
+
}
|
|
515
1202
|
return this.safeRequest("POST", `${this.apiUrl}/api/sdk/events`, payload);
|
|
516
1203
|
}
|
|
517
1204
|
/**
|
|
@@ -557,6 +1244,10 @@ var SentrialClient = class {
|
|
|
557
1244
|
if (params.metadata) {
|
|
558
1245
|
payload.metadata = params.metadata;
|
|
559
1246
|
}
|
|
1247
|
+
if (this.batcher) {
|
|
1248
|
+
this.batcher.enqueue("POST", `${this.apiUrl}/api/sdk/events`, payload);
|
|
1249
|
+
return null;
|
|
1250
|
+
}
|
|
560
1251
|
return this.safeRequest("POST", `${this.apiUrl}/api/sdk/events`, payload);
|
|
561
1252
|
}
|
|
562
1253
|
/**
|
|
@@ -586,6 +1277,17 @@ var SentrialClient = class {
|
|
|
586
1277
|
* ```
|
|
587
1278
|
*/
|
|
588
1279
|
async completeSession(params) {
|
|
1280
|
+
if (this.batcher) {
|
|
1281
|
+
await this.batcher.flush();
|
|
1282
|
+
}
|
|
1283
|
+
const acc = this.sessionAccumulators.get(params.sessionId);
|
|
1284
|
+
if (acc) {
|
|
1285
|
+
if (params.estimatedCost === void 0 && acc.cost > 0) params = { ...params, estimatedCost: acc.cost };
|
|
1286
|
+
if (params.promptTokens === void 0 && acc.promptTokens > 0) params = { ...params, promptTokens: acc.promptTokens };
|
|
1287
|
+
if (params.completionTokens === void 0 && acc.completionTokens > 0) params = { ...params, completionTokens: acc.completionTokens };
|
|
1288
|
+
if (params.totalTokens === void 0 && acc.totalTokens > 0) params = { ...params, totalTokens: acc.totalTokens };
|
|
1289
|
+
this.sessionAccumulators.delete(params.sessionId);
|
|
1290
|
+
}
|
|
589
1291
|
const payload = {
|
|
590
1292
|
status: params.success !== false ? "completed" : "failed",
|
|
591
1293
|
success: params.success ?? true
|
|
@@ -606,6 +1308,27 @@ var SentrialClient = class {
|
|
|
606
1308
|
payload
|
|
607
1309
|
);
|
|
608
1310
|
}
|
|
1311
|
+
/**
|
|
1312
|
+
* Flush any queued events immediately.
|
|
1313
|
+
*
|
|
1314
|
+
* No-op if batching is not enabled.
|
|
1315
|
+
*/
|
|
1316
|
+
async flush() {
|
|
1317
|
+
if (this.batcher) {
|
|
1318
|
+
await this.batcher.flush();
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
/**
|
|
1322
|
+
* Shut down the event batcher, flushing remaining events.
|
|
1323
|
+
*
|
|
1324
|
+
* Call this before your process exits for a clean shutdown.
|
|
1325
|
+
* No-op if batching is not enabled.
|
|
1326
|
+
*/
|
|
1327
|
+
async shutdown() {
|
|
1328
|
+
if (this.batcher) {
|
|
1329
|
+
await this.batcher.shutdown();
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
609
1332
|
/**
|
|
610
1333
|
* Begin tracking an interaction (simplified API)
|
|
611
1334
|
*
|
|
@@ -642,13 +1365,18 @@ var SentrialClient = class {
|
|
|
642
1365
|
if (params.input) {
|
|
643
1366
|
this.currentState.input = params.input;
|
|
644
1367
|
}
|
|
1368
|
+
let sessionTokens;
|
|
1369
|
+
if (sessionId) {
|
|
1370
|
+
sessionTokens = _setSessionContextWithTokens(sessionId, this);
|
|
1371
|
+
}
|
|
645
1372
|
return new Interaction({
|
|
646
1373
|
client: this,
|
|
647
1374
|
sessionId,
|
|
648
1375
|
eventId,
|
|
649
1376
|
userId: params.userId,
|
|
650
1377
|
event: params.event,
|
|
651
|
-
userInput: params.input
|
|
1378
|
+
userInput: params.input,
|
|
1379
|
+
sessionTokens
|
|
652
1380
|
});
|
|
653
1381
|
}
|
|
654
1382
|
// Cost calculation static methods for convenience
|
|
@@ -665,12 +1393,15 @@ var Interaction = class {
|
|
|
665
1393
|
userId;
|
|
666
1394
|
/** Event name for this interaction */
|
|
667
1395
|
event;
|
|
1396
|
+
startTime = Date.now();
|
|
668
1397
|
finished = false;
|
|
669
1398
|
success = true;
|
|
670
1399
|
failureReason;
|
|
671
1400
|
output;
|
|
672
1401
|
userInput;
|
|
673
1402
|
degraded;
|
|
1403
|
+
/** Context tokens for restoring previous session context on finish() */
|
|
1404
|
+
sessionTokens;
|
|
674
1405
|
constructor(config) {
|
|
675
1406
|
this.client = config.client;
|
|
676
1407
|
this.sessionId = config.sessionId;
|
|
@@ -679,6 +1410,7 @@ var Interaction = class {
|
|
|
679
1410
|
this.event = config.event;
|
|
680
1411
|
this.userInput = config.userInput;
|
|
681
1412
|
this.degraded = config.sessionId === null;
|
|
1413
|
+
this.sessionTokens = config.sessionTokens;
|
|
682
1414
|
}
|
|
683
1415
|
/**
|
|
684
1416
|
* Set the output for this interaction
|
|
@@ -714,18 +1446,24 @@ var Interaction = class {
|
|
|
714
1446
|
}
|
|
715
1447
|
this.finished = true;
|
|
716
1448
|
const finalOutput = params.output ?? this.output;
|
|
717
|
-
|
|
1449
|
+
const result = await this.client.completeSession({
|
|
718
1450
|
sessionId: this.sessionId,
|
|
719
1451
|
success: params.success ?? this.success,
|
|
720
1452
|
failureReason: params.failureReason ?? this.failureReason,
|
|
721
1453
|
estimatedCost: params.estimatedCost,
|
|
722
1454
|
customMetrics: params.customMetrics,
|
|
1455
|
+
durationMs: params.durationMs ?? Date.now() - this.startTime,
|
|
723
1456
|
promptTokens: params.promptTokens,
|
|
724
1457
|
completionTokens: params.completionTokens,
|
|
725
1458
|
totalTokens: params.totalTokens,
|
|
726
1459
|
userInput: this.userInput,
|
|
727
1460
|
assistantOutput: finalOutput
|
|
728
1461
|
});
|
|
1462
|
+
if (this.sessionTokens) {
|
|
1463
|
+
_restoreSessionContext(this.sessionTokens);
|
|
1464
|
+
this.sessionTokens = void 0;
|
|
1465
|
+
}
|
|
1466
|
+
return result;
|
|
729
1467
|
}
|
|
730
1468
|
/**
|
|
731
1469
|
* Track a tool call within this interaction
|
|
@@ -785,16 +1523,24 @@ function configure(config) {
|
|
|
785
1523
|
function begin(params) {
|
|
786
1524
|
return getClient().begin(params);
|
|
787
1525
|
}
|
|
1526
|
+
async function flush() {
|
|
1527
|
+
if (defaultClient) await defaultClient.flush();
|
|
1528
|
+
}
|
|
1529
|
+
async function shutdown() {
|
|
1530
|
+
if (defaultClient) await defaultClient.shutdown();
|
|
1531
|
+
}
|
|
788
1532
|
var sentrial = {
|
|
789
1533
|
configure,
|
|
790
|
-
begin
|
|
1534
|
+
begin,
|
|
1535
|
+
flush,
|
|
1536
|
+
shutdown
|
|
791
1537
|
};
|
|
792
1538
|
|
|
793
1539
|
// src/vercel.ts
|
|
794
|
-
var
|
|
1540
|
+
var _defaultClient2 = null;
|
|
795
1541
|
var _globalConfig = {};
|
|
796
1542
|
function configureVercel(config) {
|
|
797
|
-
|
|
1543
|
+
_defaultClient2 = new SentrialClient({
|
|
798
1544
|
apiKey: config.apiKey,
|
|
799
1545
|
apiUrl: config.apiUrl,
|
|
800
1546
|
failSilently: config.failSilently ?? true
|
|
@@ -806,10 +1552,10 @@ function configureVercel(config) {
|
|
|
806
1552
|
};
|
|
807
1553
|
}
|
|
808
1554
|
function getClient2() {
|
|
809
|
-
if (!
|
|
810
|
-
|
|
1555
|
+
if (!_defaultClient2) {
|
|
1556
|
+
_defaultClient2 = new SentrialClient();
|
|
811
1557
|
}
|
|
812
|
-
return
|
|
1558
|
+
return _defaultClient2;
|
|
813
1559
|
}
|
|
814
1560
|
function extractModelInfo(model) {
|
|
815
1561
|
const modelId = model.modelId || model.id || "unknown";
|
|
@@ -818,7 +1564,7 @@ function extractModelInfo(model) {
|
|
|
818
1564
|
}
|
|
819
1565
|
function guessProvider(modelId) {
|
|
820
1566
|
const id = modelId.toLowerCase();
|
|
821
|
-
if (id.includes("gpt") || id.
|
|
1567
|
+
if (id.includes("gpt") || id.startsWith("o1") || id.startsWith("o3") || id.startsWith("o4") || id.startsWith("chatgpt")) return "openai";
|
|
822
1568
|
if (id.includes("claude")) return "anthropic";
|
|
823
1569
|
if (id.includes("gemini")) return "google";
|
|
824
1570
|
if (id.includes("mistral") || id.includes("mixtral") || id.includes("codestral") || id.includes("pixtral")) return "mistral";
|
|
@@ -844,7 +1590,7 @@ function calculateCostForCall(provider, modelId, promptTokens, completionTokens)
|
|
|
844
1590
|
case "mistral":
|
|
845
1591
|
return promptTokens / 1e6 * 2 + completionTokens / 1e6 * 6;
|
|
846
1592
|
default:
|
|
847
|
-
return
|
|
1593
|
+
return 0;
|
|
848
1594
|
}
|
|
849
1595
|
}
|
|
850
1596
|
function extractInput(params) {
|
|
@@ -977,15 +1723,14 @@ function wrapGenerateText(originalFn, client, config) {
|
|
|
977
1723
|
const result = await originalFn(wrappedParams);
|
|
978
1724
|
const durationMs = Date.now() - startTime;
|
|
979
1725
|
const resolvedModelId = result.response?.modelId || modelId;
|
|
980
|
-
const promptTokens = result.usage?.promptTokens
|
|
981
|
-
const completionTokens = result.usage?.completionTokens
|
|
982
|
-
const totalTokens = result.usage?.totalTokens
|
|
1726
|
+
const promptTokens = result.usage?.promptTokens ?? 0;
|
|
1727
|
+
const completionTokens = result.usage?.completionTokens ?? 0;
|
|
1728
|
+
const totalTokens = result.usage?.totalTokens ?? promptTokens + completionTokens;
|
|
983
1729
|
const cost = calculateCostForCall(provider, resolvedModelId, promptTokens, completionTokens);
|
|
984
1730
|
const steps = result.steps;
|
|
985
1731
|
if (steps && steps.length >= 1) {
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
await client.trackEvent({
|
|
1732
|
+
const stepPromises = steps.map(
|
|
1733
|
+
(step, i) => client.trackEvent({
|
|
989
1734
|
sessionId,
|
|
990
1735
|
eventType: "llm_call",
|
|
991
1736
|
eventData: {
|
|
@@ -993,14 +1738,16 @@ function wrapGenerateText(originalFn, client, config) {
|
|
|
993
1738
|
provider,
|
|
994
1739
|
step: i + 1,
|
|
995
1740
|
total_steps: steps.length,
|
|
996
|
-
prompt_tokens: step.usage?.promptTokens
|
|
997
|
-
completion_tokens: step.usage?.completionTokens
|
|
998
|
-
total_tokens: step.usage?.totalTokens
|
|
1741
|
+
prompt_tokens: step.usage?.promptTokens ?? 0,
|
|
1742
|
+
completion_tokens: step.usage?.completionTokens ?? 0,
|
|
1743
|
+
total_tokens: step.usage?.totalTokens ?? 0,
|
|
999
1744
|
finish_reason: step.finishReason,
|
|
1000
1745
|
tool_calls: step.toolCalls?.map((tc) => tc.toolName)
|
|
1001
1746
|
}
|
|
1002
|
-
})
|
|
1003
|
-
|
|
1747
|
+
}).catch(() => {
|
|
1748
|
+
})
|
|
1749
|
+
);
|
|
1750
|
+
await Promise.all(stepPromises);
|
|
1004
1751
|
} else {
|
|
1005
1752
|
await client.trackEvent({
|
|
1006
1753
|
sessionId,
|
|
@@ -1009,164 +1756,17 @@ function wrapGenerateText(originalFn, client, config) {
|
|
|
1009
1756
|
model: resolvedModelId,
|
|
1010
1757
|
provider,
|
|
1011
1758
|
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
|
-
let tracked = false;
|
|
1077
|
-
async function trackCompletion(fullText, usageInfo, error) {
|
|
1078
|
-
if (tracked) return;
|
|
1079
|
-
tracked = true;
|
|
1080
|
-
const durationMs = Date.now() - startTime;
|
|
1081
|
-
const sid = sessionId || await sessionPromise;
|
|
1082
|
-
if (!sid) return;
|
|
1083
|
-
if (error) {
|
|
1084
|
-
await client.trackError({
|
|
1085
|
-
sessionId: sid,
|
|
1086
|
-
errorType: error.name || "Error",
|
|
1087
|
-
errorMessage: error.message || "Unknown error"
|
|
1088
|
-
});
|
|
1089
|
-
await client.completeSession({
|
|
1090
|
-
sessionId: sid,
|
|
1091
|
-
success: false,
|
|
1092
|
-
failureReason: error.message || "Unknown error",
|
|
1093
|
-
durationMs
|
|
1094
|
-
});
|
|
1095
|
-
return;
|
|
1096
|
-
}
|
|
1097
|
-
const promptTokens = usageInfo?.promptTokens || 0;
|
|
1098
|
-
const completionTokens = usageInfo?.completionTokens || 0;
|
|
1099
|
-
const totalTokens = usageInfo?.totalTokens || promptTokens + completionTokens;
|
|
1100
|
-
const cost = calculateCostForCall(provider, modelId, promptTokens, completionTokens);
|
|
1101
|
-
await client.completeSession({
|
|
1102
|
-
sessionId: sid,
|
|
1103
|
-
success: true,
|
|
1104
|
-
output: fullText,
|
|
1105
|
-
durationMs,
|
|
1106
|
-
estimatedCost: cost,
|
|
1107
|
-
promptTokens,
|
|
1108
|
-
completionTokens,
|
|
1109
|
-
totalTokens
|
|
1110
|
-
});
|
|
1111
|
-
}
|
|
1112
|
-
const userOnFinish = params.onFinish;
|
|
1113
|
-
const wrappedParams = {
|
|
1114
|
-
...params,
|
|
1115
|
-
tools: params.tools ? wrapToolsAsync(params.tools, sessionPromise, client) : void 0,
|
|
1116
|
-
onFinish: async (event) => {
|
|
1117
|
-
try {
|
|
1118
|
-
if (userOnFinish) await userOnFinish(event);
|
|
1119
|
-
} catch {
|
|
1120
|
-
}
|
|
1121
|
-
await trackCompletion(event.text, event.usage);
|
|
1122
|
-
}
|
|
1123
|
-
};
|
|
1124
|
-
const result = originalFn(wrappedParams);
|
|
1125
|
-
const textProp = result.text;
|
|
1126
|
-
if (textProp != null && typeof textProp.then === "function") {
|
|
1127
|
-
textProp.then((text) => {
|
|
1128
|
-
trackCompletion(text).catch(() => {
|
|
1129
|
-
});
|
|
1130
|
-
}).catch((err) => {
|
|
1131
|
-
trackCompletion("", void 0, err instanceof Error ? err : new Error(String(err))).catch(() => {
|
|
1132
|
-
});
|
|
1133
|
-
});
|
|
1134
|
-
}
|
|
1135
|
-
return result;
|
|
1136
|
-
};
|
|
1137
|
-
}
|
|
1138
|
-
function wrapGenerateObject(originalFn, client, config) {
|
|
1139
|
-
return async (params) => {
|
|
1140
|
-
const startTime = Date.now();
|
|
1141
|
-
const { modelId, provider } = extractModelInfo(params.model);
|
|
1142
|
-
const input = extractInput(params);
|
|
1143
|
-
const sessionId = await client.createSession({
|
|
1144
|
-
name: `generateObject: ${input.slice(0, 50)}${input.length > 50 ? "..." : ""}`,
|
|
1145
|
-
agentName: config.defaultAgent ?? "vercel-ai-sdk",
|
|
1146
|
-
userId: config.userId ?? "anonymous",
|
|
1147
|
-
convoId: config.convoId,
|
|
1148
|
-
metadata: {
|
|
1149
|
-
model: modelId,
|
|
1150
|
-
provider,
|
|
1151
|
-
function: "generateObject"
|
|
1152
|
-
}
|
|
1153
|
-
});
|
|
1154
|
-
if (!sessionId) {
|
|
1155
|
-
return originalFn(params);
|
|
1156
|
-
}
|
|
1157
|
-
await client.setInput(sessionId, input);
|
|
1158
|
-
try {
|
|
1159
|
-
const result = await originalFn(params);
|
|
1160
|
-
const durationMs = Date.now() - startTime;
|
|
1161
|
-
const resolvedModelId = result.response?.modelId || modelId;
|
|
1162
|
-
const promptTokens = result.usage?.promptTokens || 0;
|
|
1163
|
-
const completionTokens = result.usage?.completionTokens || 0;
|
|
1164
|
-
const totalTokens = result.usage?.totalTokens || promptTokens + completionTokens;
|
|
1165
|
-
const cost = calculateCostForCall(provider, resolvedModelId, promptTokens, completionTokens);
|
|
1759
|
+
completion_tokens: completionTokens,
|
|
1760
|
+
total_tokens: totalTokens,
|
|
1761
|
+
finish_reason: result.finishReason,
|
|
1762
|
+
tool_calls: result.toolCalls?.map((tc) => tc.toolName)
|
|
1763
|
+
}
|
|
1764
|
+
});
|
|
1765
|
+
}
|
|
1166
1766
|
await client.completeSession({
|
|
1167
1767
|
sessionId,
|
|
1168
1768
|
success: true,
|
|
1169
|
-
output:
|
|
1769
|
+
output: result.text,
|
|
1170
1770
|
durationMs,
|
|
1171
1771
|
estimatedCost: cost,
|
|
1172
1772
|
promptTokens,
|
|
@@ -1191,24 +1791,26 @@ function wrapGenerateObject(originalFn, client, config) {
|
|
|
1191
1791
|
}
|
|
1192
1792
|
};
|
|
1193
1793
|
}
|
|
1194
|
-
function
|
|
1794
|
+
function wrapStreamText(originalFn, client, config) {
|
|
1195
1795
|
return (params) => {
|
|
1196
1796
|
const startTime = Date.now();
|
|
1197
1797
|
const { modelId, provider } = extractModelInfo(params.model);
|
|
1198
1798
|
const input = extractInput(params);
|
|
1799
|
+
let sessionId = null;
|
|
1199
1800
|
const sessionPromise = (async () => {
|
|
1200
1801
|
try {
|
|
1201
1802
|
const id = await client.createSession({
|
|
1202
|
-
name: `
|
|
1803
|
+
name: `streamText: ${input.slice(0, 50)}${input.length > 50 ? "..." : ""}`,
|
|
1203
1804
|
agentName: config.defaultAgent ?? "vercel-ai-sdk",
|
|
1204
1805
|
userId: config.userId ?? "anonymous",
|
|
1205
1806
|
convoId: config.convoId,
|
|
1206
1807
|
metadata: {
|
|
1207
1808
|
model: modelId,
|
|
1208
1809
|
provider,
|
|
1209
|
-
function: "
|
|
1810
|
+
function: "streamText"
|
|
1210
1811
|
}
|
|
1211
1812
|
});
|
|
1813
|
+
sessionId = id;
|
|
1212
1814
|
if (id) {
|
|
1213
1815
|
client.setInput(id, input).catch(() => {
|
|
1214
1816
|
});
|
|
@@ -1218,364 +1820,481 @@ function wrapStreamObject(originalFn, client, config) {
|
|
|
1218
1820
|
return null;
|
|
1219
1821
|
}
|
|
1220
1822
|
})();
|
|
1221
|
-
let tracked = false;
|
|
1222
|
-
const userOnFinish = params.onFinish;
|
|
1223
1823
|
const wrappedParams = {
|
|
1224
1824
|
...params,
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1825
|
+
tools: params.tools ? wrapToolsAsync(params.tools, sessionPromise, client) : void 0
|
|
1826
|
+
};
|
|
1827
|
+
const result = originalFn(wrappedParams);
|
|
1828
|
+
const originalTextStream = result.textStream;
|
|
1829
|
+
let fullText = "";
|
|
1830
|
+
let tracked = false;
|
|
1831
|
+
async function trackCompletion(text, error) {
|
|
1832
|
+
if (tracked) return;
|
|
1833
|
+
tracked = true;
|
|
1834
|
+
const durationMs = Date.now() - startTime;
|
|
1835
|
+
const sid = sessionId || await sessionPromise;
|
|
1836
|
+
if (!sid) return;
|
|
1837
|
+
if (error) {
|
|
1838
|
+
await client.trackError({
|
|
1839
|
+
sessionId: sid,
|
|
1840
|
+
errorType: error.name || "Error",
|
|
1841
|
+
errorMessage: error.message || "Unknown error"
|
|
1842
|
+
}).catch(() => {
|
|
1843
|
+
});
|
|
1844
|
+
await client.completeSession({
|
|
1845
|
+
sessionId: sid,
|
|
1846
|
+
success: false,
|
|
1847
|
+
failureReason: error.message || "Unknown error",
|
|
1848
|
+
durationMs
|
|
1849
|
+
}).catch(() => {
|
|
1850
|
+
});
|
|
1851
|
+
return;
|
|
1852
|
+
}
|
|
1853
|
+
let resolvedModelId = modelId;
|
|
1854
|
+
try {
|
|
1855
|
+
const resp = result.response ? await result.response : void 0;
|
|
1856
|
+
if (resp?.modelId) resolvedModelId = resp.modelId;
|
|
1857
|
+
} catch {
|
|
1858
|
+
}
|
|
1859
|
+
let usage;
|
|
1860
|
+
try {
|
|
1861
|
+
usage = result.usage ? await result.usage : void 0;
|
|
1862
|
+
} catch {
|
|
1863
|
+
}
|
|
1864
|
+
let steps;
|
|
1865
|
+
try {
|
|
1866
|
+
steps = result.steps ? await result.steps : void 0;
|
|
1867
|
+
} catch {
|
|
1868
|
+
}
|
|
1869
|
+
if (steps && steps.length >= 1) {
|
|
1870
|
+
let totalPrompt = 0, totalCompletion = 0;
|
|
1871
|
+
const stepPromises = steps.map((step, i) => {
|
|
1872
|
+
const sp = step.usage?.promptTokens ?? 0;
|
|
1873
|
+
const sc = step.usage?.completionTokens ?? 0;
|
|
1874
|
+
totalPrompt += sp;
|
|
1875
|
+
totalCompletion += sc;
|
|
1876
|
+
return client.trackEvent({
|
|
1243
1877
|
sessionId: sid,
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1878
|
+
eventType: "llm_call",
|
|
1879
|
+
eventData: {
|
|
1880
|
+
model: resolvedModelId,
|
|
1881
|
+
provider,
|
|
1882
|
+
step: i + 1,
|
|
1883
|
+
total_steps: steps.length,
|
|
1884
|
+
prompt_tokens: sp,
|
|
1885
|
+
completion_tokens: sc,
|
|
1886
|
+
total_tokens: step.usage?.totalTokens ?? 0,
|
|
1887
|
+
finish_reason: step.finishReason,
|
|
1888
|
+
tool_calls: step.toolCalls?.map((tc) => tc.toolName)
|
|
1889
|
+
}
|
|
1890
|
+
}).catch(() => {
|
|
1247
1891
|
});
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
const promptTokens =
|
|
1251
|
-
const completionTokens =
|
|
1252
|
-
const totalTokens =
|
|
1253
|
-
const cost = calculateCostForCall(provider,
|
|
1892
|
+
});
|
|
1893
|
+
await Promise.all(stepPromises);
|
|
1894
|
+
const promptTokens = usage?.promptTokens ?? totalPrompt;
|
|
1895
|
+
const completionTokens = usage?.completionTokens ?? totalCompletion;
|
|
1896
|
+
const totalTokens = usage?.totalTokens ?? promptTokens + completionTokens;
|
|
1897
|
+
const cost = calculateCostForCall(provider, resolvedModelId, promptTokens, completionTokens);
|
|
1898
|
+
await client.completeSession({
|
|
1899
|
+
sessionId: sid,
|
|
1900
|
+
success: true,
|
|
1901
|
+
output: text,
|
|
1902
|
+
durationMs,
|
|
1903
|
+
estimatedCost: cost,
|
|
1904
|
+
promptTokens,
|
|
1905
|
+
completionTokens,
|
|
1906
|
+
totalTokens
|
|
1907
|
+
}).catch(() => {
|
|
1908
|
+
});
|
|
1909
|
+
} else {
|
|
1910
|
+
const promptTokens = usage?.promptTokens ?? 0;
|
|
1911
|
+
const completionTokens = usage?.completionTokens ?? 0;
|
|
1912
|
+
const totalTokens = usage?.totalTokens ?? promptTokens + completionTokens;
|
|
1913
|
+
const cost = calculateCostForCall(provider, resolvedModelId, promptTokens, completionTokens);
|
|
1914
|
+
await client.trackEvent({
|
|
1915
|
+
sessionId: sid,
|
|
1916
|
+
eventType: "llm_call",
|
|
1917
|
+
eventData: {
|
|
1918
|
+
model: resolvedModelId,
|
|
1919
|
+
provider,
|
|
1920
|
+
prompt_tokens: promptTokens,
|
|
1921
|
+
completion_tokens: completionTokens,
|
|
1922
|
+
total_tokens: totalTokens
|
|
1923
|
+
}
|
|
1924
|
+
}).catch(() => {
|
|
1925
|
+
});
|
|
1254
1926
|
await client.completeSession({
|
|
1255
1927
|
sessionId: sid,
|
|
1256
1928
|
success: true,
|
|
1257
|
-
output:
|
|
1929
|
+
output: text,
|
|
1258
1930
|
durationMs,
|
|
1259
1931
|
estimatedCost: cost,
|
|
1260
1932
|
promptTokens,
|
|
1261
1933
|
completionTokens,
|
|
1262
1934
|
totalTokens
|
|
1935
|
+
}).catch(() => {
|
|
1263
1936
|
});
|
|
1264
1937
|
}
|
|
1265
|
-
}
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
streamText: ai.streamText ? wrapStreamText(ai.streamText, client, config) : wrapStreamText(() => ({ textStream: (async function* () {
|
|
1283
|
-
})() }), client, config),
|
|
1284
|
-
generateObject: ai.generateObject ? wrapGenerateObject(ai.generateObject, client, config) : wrapGenerateObject(
|
|
1285
|
-
() => Promise.reject(new Error("generateObject not available")),
|
|
1286
|
-
client,
|
|
1287
|
-
config
|
|
1288
|
-
),
|
|
1289
|
-
streamObject: ai.streamObject ? wrapStreamObject(ai.streamObject, client, config) : wrapStreamObject(() => ({}), client, config)
|
|
1938
|
+
}
|
|
1939
|
+
result.textStream = (async function* () {
|
|
1940
|
+
try {
|
|
1941
|
+
for await (const chunk of originalTextStream) {
|
|
1942
|
+
fullText += chunk;
|
|
1943
|
+
yield chunk;
|
|
1944
|
+
}
|
|
1945
|
+
await trackCompletion(fullText);
|
|
1946
|
+
} catch (error) {
|
|
1947
|
+
await trackCompletion(
|
|
1948
|
+
fullText,
|
|
1949
|
+
error instanceof Error ? error : new Error(String(error))
|
|
1950
|
+
);
|
|
1951
|
+
throw error;
|
|
1952
|
+
}
|
|
1953
|
+
})();
|
|
1954
|
+
return result;
|
|
1290
1955
|
};
|
|
1291
1956
|
}
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
var _currentSessionId = null;
|
|
1295
|
-
var _currentClient = null;
|
|
1296
|
-
var _defaultClient2 = null;
|
|
1297
|
-
function setSessionContext(sessionId, client) {
|
|
1298
|
-
_currentSessionId = sessionId;
|
|
1299
|
-
if (client) {
|
|
1300
|
-
_currentClient = client;
|
|
1301
|
-
}
|
|
1302
|
-
}
|
|
1303
|
-
function clearSessionContext() {
|
|
1304
|
-
_currentSessionId = null;
|
|
1305
|
-
_currentClient = null;
|
|
1306
|
-
}
|
|
1307
|
-
function getSessionContext() {
|
|
1308
|
-
return _currentSessionId;
|
|
1309
|
-
}
|
|
1310
|
-
function setDefaultClient(client) {
|
|
1311
|
-
_defaultClient2 = client;
|
|
1312
|
-
}
|
|
1313
|
-
function getTrackingClient() {
|
|
1314
|
-
return _currentClient ?? _defaultClient2;
|
|
1315
|
-
}
|
|
1316
|
-
function wrapOpenAI(client, options = {}) {
|
|
1317
|
-
const { trackWithoutSession = false } = options;
|
|
1318
|
-
const chat = client.chat;
|
|
1319
|
-
if (!chat?.completions?.create) {
|
|
1320
|
-
console.warn("Sentrial: OpenAI client does not have chat.completions.create");
|
|
1321
|
-
return client;
|
|
1322
|
-
}
|
|
1323
|
-
const originalCreate = chat.completions.create.bind(chat.completions);
|
|
1324
|
-
chat.completions.create = async function(...args) {
|
|
1957
|
+
function wrapGenerateObject(originalFn, client, config) {
|
|
1958
|
+
return async (params) => {
|
|
1325
1959
|
const startTime = Date.now();
|
|
1326
|
-
const
|
|
1327
|
-
const
|
|
1328
|
-
const
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
outputContent = response.choices[0].message.content;
|
|
1960
|
+
const { modelId, provider } = extractModelInfo(params.model);
|
|
1961
|
+
const input = extractInput(params);
|
|
1962
|
+
const sessionId = await client.createSession({
|
|
1963
|
+
name: `generateObject: ${input.slice(0, 50)}${input.length > 50 ? "..." : ""}`,
|
|
1964
|
+
agentName: config.defaultAgent ?? "vercel-ai-sdk",
|
|
1965
|
+
userId: config.userId ?? "anonymous",
|
|
1966
|
+
convoId: config.convoId,
|
|
1967
|
+
metadata: {
|
|
1968
|
+
model: modelId,
|
|
1969
|
+
provider,
|
|
1970
|
+
function: "generateObject"
|
|
1338
1971
|
}
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
model,
|
|
1343
|
-
messages,
|
|
1344
|
-
output: outputContent,
|
|
1345
|
-
promptTokens,
|
|
1346
|
-
completionTokens,
|
|
1347
|
-
totalTokens,
|
|
1348
|
-
cost,
|
|
1349
|
-
durationMs,
|
|
1350
|
-
trackWithoutSession
|
|
1351
|
-
});
|
|
1352
|
-
return response;
|
|
1353
|
-
} catch (error) {
|
|
1354
|
-
const durationMs = Date.now() - startTime;
|
|
1355
|
-
trackLLMError({
|
|
1356
|
-
provider: "openai",
|
|
1357
|
-
model,
|
|
1358
|
-
messages,
|
|
1359
|
-
error,
|
|
1360
|
-
durationMs,
|
|
1361
|
-
trackWithoutSession
|
|
1362
|
-
});
|
|
1363
|
-
throw error;
|
|
1972
|
+
});
|
|
1973
|
+
if (!sessionId) {
|
|
1974
|
+
return originalFn(params);
|
|
1364
1975
|
}
|
|
1365
|
-
|
|
1366
|
-
return client;
|
|
1367
|
-
}
|
|
1368
|
-
function wrapAnthropic(client, options = {}) {
|
|
1369
|
-
const { trackWithoutSession = false } = options;
|
|
1370
|
-
const messages = client.messages;
|
|
1371
|
-
if (!messages?.create) {
|
|
1372
|
-
console.warn("Sentrial: Anthropic client does not have messages.create");
|
|
1373
|
-
return client;
|
|
1374
|
-
}
|
|
1375
|
-
const originalCreate = messages.create.bind(messages);
|
|
1376
|
-
messages.create = async function(...args) {
|
|
1377
|
-
const startTime = Date.now();
|
|
1378
|
-
const params = args[0] ?? {};
|
|
1379
|
-
const inputMessages = params.messages ?? [];
|
|
1380
|
-
const model = params.model ?? "unknown";
|
|
1381
|
-
const system = params.system ?? "";
|
|
1976
|
+
await client.setInput(sessionId, input);
|
|
1382
1977
|
try {
|
|
1383
|
-
const
|
|
1978
|
+
const result = await originalFn(params);
|
|
1384
1979
|
const durationMs = Date.now() - startTime;
|
|
1385
|
-
const
|
|
1386
|
-
const
|
|
1387
|
-
const
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1980
|
+
const resolvedModelId = result.response?.modelId || modelId;
|
|
1981
|
+
const promptTokens = result.usage?.promptTokens ?? 0;
|
|
1982
|
+
const completionTokens = result.usage?.completionTokens ?? 0;
|
|
1983
|
+
const totalTokens = result.usage?.totalTokens ?? promptTokens + completionTokens;
|
|
1984
|
+
const cost = calculateCostForCall(provider, resolvedModelId, promptTokens, completionTokens);
|
|
1985
|
+
await client.trackEvent({
|
|
1986
|
+
sessionId,
|
|
1987
|
+
eventType: "llm_call",
|
|
1988
|
+
eventData: {
|
|
1989
|
+
model: resolvedModelId,
|
|
1990
|
+
provider,
|
|
1991
|
+
prompt_tokens: promptTokens,
|
|
1992
|
+
completion_tokens: completionTokens,
|
|
1993
|
+
total_tokens: totalTokens
|
|
1394
1994
|
}
|
|
1395
|
-
}
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1995
|
+
}).catch(() => {
|
|
1996
|
+
});
|
|
1997
|
+
await client.completeSession({
|
|
1998
|
+
sessionId,
|
|
1999
|
+
success: true,
|
|
2000
|
+
output: JSON.stringify(result.object),
|
|
2001
|
+
durationMs,
|
|
2002
|
+
estimatedCost: cost,
|
|
1403
2003
|
promptTokens,
|
|
1404
2004
|
completionTokens,
|
|
1405
|
-
totalTokens
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
2005
|
+
totalTokens
|
|
2006
|
+
});
|
|
2007
|
+
return result;
|
|
2008
|
+
} catch (error) {
|
|
2009
|
+
const durationMs = Date.now() - startTime;
|
|
2010
|
+
await client.trackError({
|
|
2011
|
+
sessionId,
|
|
2012
|
+
errorType: error instanceof Error ? error.name : "Error",
|
|
2013
|
+
errorMessage: error instanceof Error ? error.message : "Unknown error"
|
|
1409
2014
|
});
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
model,
|
|
1416
|
-
messages: inputMessages,
|
|
1417
|
-
error,
|
|
1418
|
-
durationMs,
|
|
1419
|
-
trackWithoutSession
|
|
2015
|
+
await client.completeSession({
|
|
2016
|
+
sessionId,
|
|
2017
|
+
success: false,
|
|
2018
|
+
failureReason: error instanceof Error ? error.message : "Unknown error",
|
|
2019
|
+
durationMs
|
|
1420
2020
|
});
|
|
1421
2021
|
throw error;
|
|
1422
2022
|
}
|
|
1423
2023
|
};
|
|
1424
|
-
return client;
|
|
1425
2024
|
}
|
|
1426
|
-
function
|
|
1427
|
-
|
|
1428
|
-
const originalGenerate = model.generateContent;
|
|
1429
|
-
if (!originalGenerate) {
|
|
1430
|
-
console.warn("Sentrial: Google model does not have generateContent");
|
|
1431
|
-
return model;
|
|
1432
|
-
}
|
|
1433
|
-
model.generateContent = async function(...args) {
|
|
2025
|
+
function wrapStreamObject(originalFn, client, config) {
|
|
2026
|
+
return (params) => {
|
|
1434
2027
|
const startTime = Date.now();
|
|
1435
|
-
const
|
|
1436
|
-
const
|
|
1437
|
-
const
|
|
1438
|
-
|
|
1439
|
-
|
|
2028
|
+
const { modelId, provider } = extractModelInfo(params.model);
|
|
2029
|
+
const input = extractInput(params);
|
|
2030
|
+
const sessionPromise = (async () => {
|
|
2031
|
+
try {
|
|
2032
|
+
const id = await client.createSession({
|
|
2033
|
+
name: `streamObject: ${input.slice(0, 50)}${input.length > 50 ? "..." : ""}`,
|
|
2034
|
+
agentName: config.defaultAgent ?? "vercel-ai-sdk",
|
|
2035
|
+
userId: config.userId ?? "anonymous",
|
|
2036
|
+
convoId: config.convoId,
|
|
2037
|
+
metadata: {
|
|
2038
|
+
model: modelId,
|
|
2039
|
+
provider,
|
|
2040
|
+
function: "streamObject"
|
|
2041
|
+
}
|
|
2042
|
+
});
|
|
2043
|
+
if (id) {
|
|
2044
|
+
client.setInput(id, input).catch(() => {
|
|
2045
|
+
});
|
|
2046
|
+
}
|
|
2047
|
+
return id;
|
|
2048
|
+
} catch {
|
|
2049
|
+
return null;
|
|
2050
|
+
}
|
|
2051
|
+
})();
|
|
2052
|
+
const result = originalFn(params);
|
|
2053
|
+
async function completeStreamObject(obj, error) {
|
|
1440
2054
|
const durationMs = Date.now() - startTime;
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
if (
|
|
1444
|
-
|
|
1445
|
-
|
|
2055
|
+
const sid = await sessionPromise;
|
|
2056
|
+
if (!sid) return;
|
|
2057
|
+
if (error) {
|
|
2058
|
+
await client.trackError({
|
|
2059
|
+
sessionId: sid,
|
|
2060
|
+
errorType: error.name || "Error",
|
|
2061
|
+
errorMessage: error.message || "Unknown error"
|
|
2062
|
+
}).catch(() => {
|
|
2063
|
+
});
|
|
2064
|
+
await client.completeSession({
|
|
2065
|
+
sessionId: sid,
|
|
2066
|
+
success: false,
|
|
2067
|
+
failureReason: error.message || "Unknown error",
|
|
2068
|
+
durationMs
|
|
2069
|
+
}).catch(() => {
|
|
2070
|
+
});
|
|
2071
|
+
return;
|
|
1446
2072
|
}
|
|
1447
|
-
|
|
1448
|
-
let outputContent = "";
|
|
2073
|
+
let usage;
|
|
1449
2074
|
try {
|
|
1450
|
-
|
|
2075
|
+
usage = result.usage ? await result.usage : void 0;
|
|
1451
2076
|
} catch {
|
|
1452
2077
|
}
|
|
1453
|
-
const
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
2078
|
+
const promptTokens = usage?.promptTokens ?? 0;
|
|
2079
|
+
const completionTokens = usage?.completionTokens ?? 0;
|
|
2080
|
+
const totalTokens = usage?.totalTokens ?? promptTokens + completionTokens;
|
|
2081
|
+
const cost = calculateCostForCall(provider, modelId, promptTokens, completionTokens);
|
|
2082
|
+
await client.trackEvent({
|
|
2083
|
+
sessionId: sid,
|
|
2084
|
+
eventType: "llm_call",
|
|
2085
|
+
eventData: {
|
|
2086
|
+
model: modelId,
|
|
2087
|
+
provider,
|
|
2088
|
+
prompt_tokens: promptTokens,
|
|
2089
|
+
completion_tokens: completionTokens,
|
|
2090
|
+
total_tokens: totalTokens
|
|
2091
|
+
}
|
|
2092
|
+
}).catch(() => {
|
|
2093
|
+
});
|
|
2094
|
+
await client.completeSession({
|
|
2095
|
+
sessionId: sid,
|
|
2096
|
+
success: true,
|
|
2097
|
+
output: JSON.stringify(obj),
|
|
2098
|
+
durationMs,
|
|
2099
|
+
estimatedCost: cost,
|
|
1459
2100
|
promptTokens,
|
|
1460
2101
|
completionTokens,
|
|
1461
|
-
totalTokens
|
|
1462
|
-
|
|
1463
|
-
durationMs,
|
|
1464
|
-
trackWithoutSession
|
|
2102
|
+
totalTokens
|
|
2103
|
+
}).catch(() => {
|
|
1465
2104
|
});
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
const
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
error
|
|
1474
|
-
|
|
1475
|
-
|
|
2105
|
+
}
|
|
2106
|
+
if (result.object) {
|
|
2107
|
+
const originalObjectPromise = result.object;
|
|
2108
|
+
result.object = originalObjectPromise.then(async (obj) => {
|
|
2109
|
+
await completeStreamObject(obj);
|
|
2110
|
+
return obj;
|
|
2111
|
+
}).catch(async (error) => {
|
|
2112
|
+
await completeStreamObject(void 0, error instanceof Error ? error : new Error(String(error)));
|
|
2113
|
+
throw error;
|
|
2114
|
+
});
|
|
2115
|
+
} else if (result.usage) {
|
|
2116
|
+
result.usage.then(async () => {
|
|
2117
|
+
await completeStreamObject(void 0);
|
|
2118
|
+
}).catch(async (error) => {
|
|
2119
|
+
await completeStreamObject(void 0, error instanceof Error ? error : new Error(String(error)));
|
|
1476
2120
|
});
|
|
1477
|
-
throw error;
|
|
1478
2121
|
}
|
|
2122
|
+
return result;
|
|
1479
2123
|
};
|
|
1480
|
-
return model;
|
|
1481
|
-
}
|
|
1482
|
-
function googleContentsToMessages(contents) {
|
|
1483
|
-
if (typeof contents === "string") {
|
|
1484
|
-
return [{ role: "user", content: contents }];
|
|
1485
|
-
}
|
|
1486
|
-
if (Array.isArray(contents)) {
|
|
1487
|
-
return contents.map((item) => {
|
|
1488
|
-
if (typeof item === "string") {
|
|
1489
|
-
return { role: "user", content: item };
|
|
1490
|
-
}
|
|
1491
|
-
if (item && typeof item === "object") {
|
|
1492
|
-
return { role: item.role ?? "user", content: String(item.content ?? item) };
|
|
1493
|
-
}
|
|
1494
|
-
return { role: "user", content: String(item) };
|
|
1495
|
-
});
|
|
1496
|
-
}
|
|
1497
|
-
return [{ role: "user", content: String(contents) }];
|
|
1498
2124
|
}
|
|
1499
|
-
function
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
}
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
2125
|
+
function wrapAISDK(ai, options) {
|
|
2126
|
+
const client = options?.client ?? getClient2();
|
|
2127
|
+
const config = {
|
|
2128
|
+
defaultAgent: options?.defaultAgent ?? _globalConfig.defaultAgent,
|
|
2129
|
+
userId: options?.userId ?? _globalConfig.userId,
|
|
2130
|
+
convoId: options?.convoId ?? _globalConfig.convoId
|
|
2131
|
+
};
|
|
2132
|
+
return {
|
|
2133
|
+
generateText: ai.generateText ? wrapGenerateText(ai.generateText, client, config) : wrapGenerateText(
|
|
2134
|
+
() => Promise.reject(new Error("generateText not available")),
|
|
2135
|
+
client,
|
|
2136
|
+
config
|
|
2137
|
+
),
|
|
2138
|
+
streamText: ai.streamText ? wrapStreamText(ai.streamText, client, config) : wrapStreamText(() => ({ textStream: (async function* () {
|
|
2139
|
+
})() }), client, config),
|
|
2140
|
+
generateObject: ai.generateObject ? wrapGenerateObject(ai.generateObject, client, config) : wrapGenerateObject(
|
|
2141
|
+
() => Promise.reject(new Error("generateObject not available")),
|
|
2142
|
+
client,
|
|
2143
|
+
config
|
|
2144
|
+
),
|
|
2145
|
+
streamObject: ai.streamObject ? wrapStreamObject(ai.streamObject, client, config) : wrapStreamObject(() => ({}), client, config)
|
|
2146
|
+
};
|
|
1511
2147
|
}
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
const
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
content: params.output,
|
|
1530
|
-
tokens: {
|
|
1531
|
-
prompt: params.promptTokens,
|
|
1532
|
-
completion: params.completionTokens,
|
|
1533
|
-
total: params.totalTokens
|
|
1534
|
-
},
|
|
1535
|
-
cost_usd: params.cost
|
|
1536
|
-
},
|
|
1537
|
-
reasoning: `LLM call to ${params.provider} ${params.model}`,
|
|
1538
|
-
estimatedCost: params.cost,
|
|
1539
|
-
tokenCount: params.totalTokens,
|
|
1540
|
-
metadata: {
|
|
1541
|
-
provider: params.provider,
|
|
1542
|
-
model: params.model,
|
|
1543
|
-
duration_ms: params.durationMs,
|
|
1544
|
-
prompt_tokens: params.promptTokens,
|
|
1545
|
-
completion_tokens: params.completionTokens
|
|
1546
|
-
}
|
|
1547
|
-
}).catch((err) => {
|
|
1548
|
-
console.warn("Sentrial: Failed to track LLM call:", err.message);
|
|
2148
|
+
|
|
2149
|
+
// src/claude-code.ts
|
|
2150
|
+
function wrapClaudeAgent(queryFn, wrapOptions) {
|
|
2151
|
+
const {
|
|
2152
|
+
client,
|
|
2153
|
+
defaultAgent = "claude-agent",
|
|
2154
|
+
userId = "anonymous",
|
|
2155
|
+
convoId,
|
|
2156
|
+
extraMetadata
|
|
2157
|
+
} = wrapOptions;
|
|
2158
|
+
return function wrappedQuery(params) {
|
|
2159
|
+
const { prompt, options = {} } = params;
|
|
2160
|
+
const startTime = Date.now();
|
|
2161
|
+
let sessionId = null;
|
|
2162
|
+
let resolveSessionReady;
|
|
2163
|
+
const sessionReady = new Promise((resolve) => {
|
|
2164
|
+
resolveSessionReady = resolve;
|
|
1549
2165
|
});
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
2166
|
+
const sessionName = typeof prompt === "string" ? `${defaultAgent}: ${prompt.slice(0, 100)}` : `${defaultAgent} session`;
|
|
2167
|
+
const pendingToolCalls = [];
|
|
2168
|
+
const sentrialToolHook = {
|
|
2169
|
+
hooks: [
|
|
2170
|
+
async (input, toolUseID, _opts) => {
|
|
2171
|
+
await sessionReady;
|
|
2172
|
+
if (!sessionId) return;
|
|
2173
|
+
const toolOutput = input?.tool_response && typeof input.tool_response === "object" ? input.tool_response : { response: input?.tool_response ?? null };
|
|
2174
|
+
const p = client.trackToolCall({
|
|
2175
|
+
sessionId,
|
|
2176
|
+
toolName: input?.tool_name ?? "unknown",
|
|
2177
|
+
toolInput: input?.tool_input ?? {},
|
|
2178
|
+
toolOutput,
|
|
2179
|
+
metadata: { tool_use_id: toolUseID }
|
|
2180
|
+
}).catch(() => {
|
|
2181
|
+
});
|
|
2182
|
+
pendingToolCalls.push(p);
|
|
2183
|
+
}
|
|
2184
|
+
]
|
|
2185
|
+
};
|
|
2186
|
+
const sentrialToolFailureHook = {
|
|
2187
|
+
hooks: [
|
|
2188
|
+
async (input, toolUseID, _opts) => {
|
|
2189
|
+
await sessionReady;
|
|
2190
|
+
if (!sessionId) return;
|
|
2191
|
+
const p = client.trackToolCall({
|
|
2192
|
+
sessionId,
|
|
2193
|
+
toolName: input?.tool_name ?? "unknown",
|
|
2194
|
+
toolInput: input?.tool_input ?? {},
|
|
2195
|
+
toolOutput: {},
|
|
2196
|
+
toolError: { message: input?.error ?? "unknown error" },
|
|
2197
|
+
metadata: { tool_use_id: toolUseID }
|
|
2198
|
+
}).catch(() => {
|
|
2199
|
+
});
|
|
2200
|
+
pendingToolCalls.push(p);
|
|
2201
|
+
}
|
|
2202
|
+
]
|
|
2203
|
+
};
|
|
2204
|
+
const mergedHooks = {
|
|
2205
|
+
...options.hooks ?? {}
|
|
2206
|
+
};
|
|
2207
|
+
const existingPostToolUse = mergedHooks.PostToolUse ?? [];
|
|
2208
|
+
mergedHooks.PostToolUse = [...existingPostToolUse, sentrialToolHook];
|
|
2209
|
+
const existingPostToolUseFailure = mergedHooks.PostToolUseFailure ?? [];
|
|
2210
|
+
mergedHooks.PostToolUseFailure = [...existingPostToolUseFailure, sentrialToolFailureHook];
|
|
2211
|
+
const mergedOptions = {
|
|
2212
|
+
...options,
|
|
2213
|
+
hooks: mergedHooks
|
|
2214
|
+
};
|
|
2215
|
+
const generator = queryFn({ prompt, options: mergedOptions });
|
|
2216
|
+
return (async function* () {
|
|
2217
|
+
try {
|
|
2218
|
+
for await (const message of generator) {
|
|
2219
|
+
if (message.type === "system" && message.subtype === "init") {
|
|
2220
|
+
const metadata = {
|
|
2221
|
+
model: message.model,
|
|
2222
|
+
tools: message.tools,
|
|
2223
|
+
cwd: message.cwd,
|
|
2224
|
+
mcp_servers: message.mcp_servers,
|
|
2225
|
+
sdk_session_id: message.session_id,
|
|
2226
|
+
...extraMetadata ?? {}
|
|
2227
|
+
};
|
|
2228
|
+
try {
|
|
2229
|
+
sessionId = await client.createSession({
|
|
2230
|
+
name: sessionName,
|
|
2231
|
+
agentName: defaultAgent,
|
|
2232
|
+
userId,
|
|
2233
|
+
convoId,
|
|
2234
|
+
metadata
|
|
2235
|
+
});
|
|
2236
|
+
} catch {
|
|
2237
|
+
sessionId = null;
|
|
2238
|
+
}
|
|
2239
|
+
resolveSessionReady();
|
|
2240
|
+
}
|
|
2241
|
+
if (message.type === "result" && sessionId) {
|
|
2242
|
+
const isError = !!message.is_error;
|
|
2243
|
+
const inputTokens = message.usage?.input_tokens ?? 0;
|
|
2244
|
+
const outputTokens = message.usage?.output_tokens ?? 0;
|
|
2245
|
+
let failureReason;
|
|
2246
|
+
if (isError) {
|
|
2247
|
+
if (message.errors && message.errors.length > 0) {
|
|
2248
|
+
failureReason = message.errors.join("; ");
|
|
2249
|
+
} else {
|
|
2250
|
+
failureReason = message.subtype;
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2253
|
+
await Promise.allSettled(pendingToolCalls);
|
|
2254
|
+
try {
|
|
2255
|
+
await client.completeSession({
|
|
2256
|
+
sessionId,
|
|
2257
|
+
success: !isError,
|
|
2258
|
+
failureReason,
|
|
2259
|
+
estimatedCost: message.total_cost_usd,
|
|
2260
|
+
promptTokens: inputTokens,
|
|
2261
|
+
completionTokens: outputTokens,
|
|
2262
|
+
totalTokens: inputTokens + outputTokens,
|
|
2263
|
+
durationMs: message.duration_ms ?? Date.now() - startTime,
|
|
2264
|
+
userInput: typeof prompt === "string" ? prompt : void 0,
|
|
2265
|
+
output: message.result,
|
|
2266
|
+
customMetrics: {
|
|
2267
|
+
num_turns: message.num_turns ?? 0,
|
|
2268
|
+
duration_api_ms: message.duration_api_ms ?? 0
|
|
2269
|
+
}
|
|
2270
|
+
});
|
|
2271
|
+
} catch {
|
|
2272
|
+
}
|
|
2273
|
+
}
|
|
2274
|
+
yield message;
|
|
2275
|
+
}
|
|
2276
|
+
} catch (error) {
|
|
2277
|
+
if (sessionId) {
|
|
2278
|
+
await Promise.allSettled(pendingToolCalls);
|
|
2279
|
+
try {
|
|
2280
|
+
await client.completeSession({
|
|
2281
|
+
sessionId,
|
|
2282
|
+
success: false,
|
|
2283
|
+
failureReason: error instanceof Error ? error.message : String(error),
|
|
2284
|
+
durationMs: Date.now() - startTime
|
|
2285
|
+
});
|
|
2286
|
+
} catch {
|
|
2287
|
+
}
|
|
2288
|
+
}
|
|
2289
|
+
throw error;
|
|
1569
2290
|
}
|
|
1570
|
-
})
|
|
1571
|
-
|
|
1572
|
-
});
|
|
1573
|
-
}
|
|
2291
|
+
})();
|
|
2292
|
+
};
|
|
1574
2293
|
}
|
|
1575
2294
|
|
|
1576
2295
|
// src/decorators.ts
|
|
1577
2296
|
var _defaultClient3 = null;
|
|
1578
|
-
var _currentInteraction = null;
|
|
2297
|
+
var _currentInteraction = createContextVar(null);
|
|
1579
2298
|
function getClient3() {
|
|
1580
2299
|
if (!_defaultClient3) {
|
|
1581
2300
|
try {
|
|
@@ -1595,7 +2314,7 @@ function getCurrentSessionId() {
|
|
|
1595
2314
|
return getSessionContext();
|
|
1596
2315
|
}
|
|
1597
2316
|
function getCurrentInteraction() {
|
|
1598
|
-
return _currentInteraction;
|
|
2317
|
+
return _currentInteraction.get();
|
|
1599
2318
|
}
|
|
1600
2319
|
function withTool(name, fn) {
|
|
1601
2320
|
const isAsync = fn.constructor.name === "AsyncFunction";
|
|
@@ -1696,10 +2415,11 @@ function withSession(agentName, fn, options = {}) {
|
|
|
1696
2415
|
input: userInput
|
|
1697
2416
|
});
|
|
1698
2417
|
const sessionId = interaction.getSessionId();
|
|
2418
|
+
let sessionTokens;
|
|
1699
2419
|
if (sessionId) {
|
|
1700
|
-
|
|
2420
|
+
sessionTokens = _setSessionContextWithTokens(sessionId, client);
|
|
1701
2421
|
}
|
|
1702
|
-
|
|
2422
|
+
const interactionToken = _currentInteraction.set(interaction);
|
|
1703
2423
|
try {
|
|
1704
2424
|
const result = await fn(...args);
|
|
1705
2425
|
let output;
|
|
@@ -1724,8 +2444,10 @@ function withSession(agentName, fn, options = {}) {
|
|
|
1724
2444
|
});
|
|
1725
2445
|
throw error;
|
|
1726
2446
|
} finally {
|
|
1727
|
-
|
|
1728
|
-
|
|
2447
|
+
if (sessionTokens) {
|
|
2448
|
+
_restoreSessionContext(sessionTokens);
|
|
2449
|
+
}
|
|
2450
|
+
_currentInteraction.reset(interactionToken);
|
|
1729
2451
|
}
|
|
1730
2452
|
};
|
|
1731
2453
|
}
|
|
@@ -1811,10 +2533,11 @@ function TrackSession(agentName, options) {
|
|
|
1811
2533
|
input: userInput
|
|
1812
2534
|
});
|
|
1813
2535
|
const sessionId = interaction.getSessionId();
|
|
2536
|
+
let sessionTokens;
|
|
1814
2537
|
if (sessionId) {
|
|
1815
|
-
|
|
2538
|
+
sessionTokens = _setSessionContextWithTokens(sessionId, client);
|
|
1816
2539
|
}
|
|
1817
|
-
|
|
2540
|
+
const interactionToken = _currentInteraction.set(interaction);
|
|
1818
2541
|
try {
|
|
1819
2542
|
const result = await originalMethod.apply(this, args);
|
|
1820
2543
|
let output;
|
|
@@ -1839,8 +2562,10 @@ function TrackSession(agentName, options) {
|
|
|
1839
2562
|
});
|
|
1840
2563
|
throw error;
|
|
1841
2564
|
} finally {
|
|
1842
|
-
|
|
1843
|
-
|
|
2565
|
+
if (sessionTokens) {
|
|
2566
|
+
_restoreSessionContext(sessionTokens);
|
|
2567
|
+
}
|
|
2568
|
+
_currentInteraction.reset(interactionToken);
|
|
1844
2569
|
}
|
|
1845
2570
|
};
|
|
1846
2571
|
return descriptor;
|
|
@@ -1853,6 +2578,8 @@ var SessionContext = class {
|
|
|
1853
2578
|
client;
|
|
1854
2579
|
interaction = null;
|
|
1855
2580
|
output;
|
|
2581
|
+
sessionTokens;
|
|
2582
|
+
interactionToken;
|
|
1856
2583
|
constructor(options) {
|
|
1857
2584
|
this.userId = options.userId;
|
|
1858
2585
|
this.agent = options.agent;
|
|
@@ -1871,9 +2598,9 @@ var SessionContext = class {
|
|
|
1871
2598
|
});
|
|
1872
2599
|
const sessionId = this.interaction.getSessionId();
|
|
1873
2600
|
if (sessionId) {
|
|
1874
|
-
|
|
2601
|
+
this.sessionTokens = _setSessionContextWithTokens(sessionId, this.client);
|
|
1875
2602
|
}
|
|
1876
|
-
|
|
2603
|
+
this.interactionToken = _currentInteraction.set(this.interaction);
|
|
1877
2604
|
return this;
|
|
1878
2605
|
}
|
|
1879
2606
|
/**
|
|
@@ -1893,8 +2620,12 @@ var SessionContext = class {
|
|
|
1893
2620
|
failureReason: options?.error
|
|
1894
2621
|
});
|
|
1895
2622
|
}
|
|
1896
|
-
|
|
1897
|
-
|
|
2623
|
+
if (this.sessionTokens) {
|
|
2624
|
+
_restoreSessionContext(this.sessionTokens);
|
|
2625
|
+
}
|
|
2626
|
+
if (this.interactionToken) {
|
|
2627
|
+
_currentInteraction.reset(this.interactionToken);
|
|
2628
|
+
}
|
|
1898
2629
|
}
|
|
1899
2630
|
/**
|
|
1900
2631
|
* Get the session ID
|
|
@@ -1948,30 +2679,31 @@ function serializeOutput(value) {
|
|
|
1948
2679
|
}
|
|
1949
2680
|
|
|
1950
2681
|
// src/context.ts
|
|
1951
|
-
var _experimentContext = null;
|
|
2682
|
+
var _experimentContext = createContextVar(null);
|
|
1952
2683
|
function getSystemPrompt(defaultPrompt) {
|
|
1953
|
-
|
|
1954
|
-
|
|
2684
|
+
const ctx = _experimentContext.get();
|
|
2685
|
+
if (ctx?.systemPrompt) {
|
|
2686
|
+
return ctx.systemPrompt;
|
|
1955
2687
|
}
|
|
1956
2688
|
return defaultPrompt ?? "";
|
|
1957
2689
|
}
|
|
1958
2690
|
function getExperimentContext() {
|
|
1959
|
-
return _experimentContext;
|
|
2691
|
+
return _experimentContext.get();
|
|
1960
2692
|
}
|
|
1961
2693
|
function isExperimentMode() {
|
|
1962
|
-
return _experimentContext !== null;
|
|
2694
|
+
return _experimentContext.get() !== null;
|
|
1963
2695
|
}
|
|
1964
2696
|
function getVariantName() {
|
|
1965
|
-
return _experimentContext?.variantName ?? null;
|
|
2697
|
+
return _experimentContext.get()?.variantName ?? null;
|
|
1966
2698
|
}
|
|
1967
2699
|
function getExperimentId() {
|
|
1968
|
-
return _experimentContext?.experimentId ?? null;
|
|
2700
|
+
return _experimentContext.get()?.experimentId ?? null;
|
|
1969
2701
|
}
|
|
1970
2702
|
function setExperimentContext(context) {
|
|
1971
|
-
_experimentContext
|
|
2703
|
+
_experimentContext.set(context);
|
|
1972
2704
|
}
|
|
1973
2705
|
function clearExperimentContext() {
|
|
1974
|
-
_experimentContext
|
|
2706
|
+
_experimentContext.set(null);
|
|
1975
2707
|
}
|
|
1976
2708
|
|
|
1977
2709
|
// src/experiment.ts
|
|
@@ -2304,6 +3036,7 @@ var Experiment = class {
|
|
|
2304
3036
|
};
|
|
2305
3037
|
export {
|
|
2306
3038
|
ApiError,
|
|
3039
|
+
EventBatcher,
|
|
2307
3040
|
EventType,
|
|
2308
3041
|
Experiment,
|
|
2309
3042
|
ExperimentRunTracker,
|
|
@@ -2323,6 +3056,7 @@ export {
|
|
|
2323
3056
|
clearSessionContext,
|
|
2324
3057
|
configure,
|
|
2325
3058
|
configureVercel,
|
|
3059
|
+
createContextVar,
|
|
2326
3060
|
getCurrentInteraction,
|
|
2327
3061
|
getCurrentSessionId,
|
|
2328
3062
|
getExperimentContext,
|
|
@@ -2345,6 +3079,7 @@ export {
|
|
|
2345
3079
|
withTool,
|
|
2346
3080
|
wrapAISDK,
|
|
2347
3081
|
wrapAnthropic,
|
|
3082
|
+
wrapClaudeAgent,
|
|
2348
3083
|
wrapGoogle,
|
|
2349
3084
|
wrapLLM,
|
|
2350
3085
|
wrapOpenAI
|