@introspection-sdk/introspection-browser 0.19.1 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -4
- package/dist/api/client.d.ts +10 -1
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/client.js +17 -8
- package/dist/api/client.js.map +1 -1
- package/dist/api/index.d.ts +3 -2
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +2 -1
- package/dist/api/index.js.map +1 -1
- package/dist/api/tasks.d.ts +7 -1
- package/dist/api/tasks.d.ts.map +1 -1
- package/dist/api/tasks.js.map +1 -1
- package/dist/client.d.ts +14 -38
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +90 -35
- package/dist/client.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -1
- package/dist/types.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +12 -6
- package/.turbo/turbo-build.log +0 -4
- package/eslint.config.mjs +0 -3
- package/src/api/client.ts +0 -196
- package/src/api/fetch.ts +0 -31
- package/src/api/http.ts +0 -88
- package/src/api/index.ts +0 -122
- package/src/api/tasks.ts +0 -87
- package/src/client.ts +0 -796
- package/src/index.ts +0 -41
- package/src/types.ts +0 -13
- package/src/version.ts +0 -2
- package/tsconfig.json +0 -9
package/src/client.ts
DELETED
|
@@ -1,796 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Introspection Client for Browser
|
|
3
|
-
* Provides an API for tracking events and feedback using OTLP Logs.
|
|
4
|
-
*
|
|
5
|
-
* Identity and gen_ai context can be managed via baggage (requires OpenTelemetry
|
|
6
|
-
* browser SDK with Zone.js for automatic propagation) or via client instance.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { SeverityNumber, type LogAttributes } from "@opentelemetry/api-logs";
|
|
10
|
-
import {
|
|
11
|
-
LoggerProvider,
|
|
12
|
-
BatchLogRecordProcessor,
|
|
13
|
-
} from "@opentelemetry/sdk-logs";
|
|
14
|
-
import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
|
|
15
|
-
import { propagation, context } from "@opentelemetry/api";
|
|
16
|
-
import { VERSION } from "./version.js";
|
|
17
|
-
import {
|
|
18
|
-
generateEventId,
|
|
19
|
-
type IntrospectionClientOptions,
|
|
20
|
-
type FeedbackOptions,
|
|
21
|
-
type UserTraits,
|
|
22
|
-
type GenAiContext,
|
|
23
|
-
type IdentityContext,
|
|
24
|
-
} from "./types.js";
|
|
25
|
-
|
|
26
|
-
const ANONYMOUS_ID_KEY = "introspection_anonymous_id";
|
|
27
|
-
const USER_ID_KEY = "introspection_user_id";
|
|
28
|
-
const TRAITS_KEY = "introspection_traits";
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Generate a UUID-like anonymous ID
|
|
32
|
-
*/
|
|
33
|
-
function generateAnonymousId(): string {
|
|
34
|
-
const segments = [];
|
|
35
|
-
for (let i = 0; i < 4; i++) {
|
|
36
|
-
segments.push(Math.random().toString(16).substring(2, 6));
|
|
37
|
-
}
|
|
38
|
-
return segments.join("-");
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Simple logger that respects debug flag
|
|
43
|
-
*/
|
|
44
|
-
class Logger {
|
|
45
|
-
constructor(private debug: boolean) {}
|
|
46
|
-
|
|
47
|
-
log(...args: unknown[]): void {
|
|
48
|
-
if (this.debug) {
|
|
49
|
-
console.log("[introspection-sdk]", ...args);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
warn(...args: unknown[]): void {
|
|
54
|
-
console.warn("[introspection-sdk]", ...args);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
error(...args: unknown[]): void {
|
|
58
|
-
console.error("[introspection-sdk]", ...args);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Introspection client for tracking events and feedback in the browser using OTLP Logs.
|
|
64
|
-
*
|
|
65
|
-
* Identity (user_id, anonymous_id) and gen_ai context (conversation_id, previous_response_id, agent)
|
|
66
|
-
* can be managed via client instance (persisted to localStorage) or via OpenTelemetry baggage
|
|
67
|
-
* for scoped context.
|
|
68
|
-
*
|
|
69
|
-
* Note: Automatic baggage propagation in browsers requires Zone.js from @opentelemetry/context-zone.
|
|
70
|
-
*
|
|
71
|
-
* @example
|
|
72
|
-
* ```typescript
|
|
73
|
-
* const client = new IntrospectionClient({
|
|
74
|
-
* token: "intro_xxx",
|
|
75
|
-
* debug: true,
|
|
76
|
-
* });
|
|
77
|
-
*
|
|
78
|
-
* // Set identity once (persisted to localStorage)
|
|
79
|
-
* client.identify("user_123", { email: "user@example.com" });
|
|
80
|
-
*
|
|
81
|
-
* // Simple feedback
|
|
82
|
-
* client.feedback("thumbs_up", { comments: "Great response!" });
|
|
83
|
-
*
|
|
84
|
-
* // With explicit gen_ai context
|
|
85
|
-
* client.feedback("thumbs_down", {
|
|
86
|
-
* conversationId: "conv_456",
|
|
87
|
-
* previousResponseId: "msg_123",
|
|
88
|
-
* comments: "Off topic",
|
|
89
|
-
* });
|
|
90
|
-
*
|
|
91
|
-
* // With scoped baggage context
|
|
92
|
-
* await client.withConversation("conv_456", undefined, async () => {
|
|
93
|
-
* client.feedback("thumbs_up"); // automatically includes conversation_id
|
|
94
|
-
* });
|
|
95
|
-
*
|
|
96
|
-
* // Track a custom event
|
|
97
|
-
* client.track("Button Clicked", { buttonId: "submit" });
|
|
98
|
-
* ```
|
|
99
|
-
*/
|
|
100
|
-
export class IntrospectionClient {
|
|
101
|
-
private loggerProvider: LoggerProvider;
|
|
102
|
-
private otelLogger: ReturnType<LoggerProvider["getLogger"]>;
|
|
103
|
-
private userId: string | undefined;
|
|
104
|
-
private anonymousId: string;
|
|
105
|
-
private traits: Record<string, unknown> = {};
|
|
106
|
-
private logger: Logger;
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* @param options - Client configuration (token, service name, advanced settings).
|
|
110
|
-
*/
|
|
111
|
-
constructor(options: IntrospectionClientOptions = {}) {
|
|
112
|
-
const token = options.token || "";
|
|
113
|
-
const advanced = options.advanced || {};
|
|
114
|
-
const baseUrl =
|
|
115
|
-
advanced.baseUrl ||
|
|
116
|
-
(typeof process !== "undefined" &&
|
|
117
|
-
process.env?.INTROSPECTION_BASE_OTEL_URL) ||
|
|
118
|
-
"https://otel.introspection.dev";
|
|
119
|
-
this.logger = new Logger(advanced.debug ?? false);
|
|
120
|
-
|
|
121
|
-
// Load persisted state from localStorage
|
|
122
|
-
this.anonymousId = this.loadAnonymousId();
|
|
123
|
-
this.userId = this.loadUserId();
|
|
124
|
-
this.traits = this.loadTraits();
|
|
125
|
-
|
|
126
|
-
if (!token) {
|
|
127
|
-
this.logger.warn("No token provided. Events will not be sent.");
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Construct endpoint URL for logs
|
|
131
|
-
let endpoint: string;
|
|
132
|
-
if (baseUrl.endsWith("/v1/logs")) {
|
|
133
|
-
endpoint = baseUrl;
|
|
134
|
-
} else {
|
|
135
|
-
endpoint = `${baseUrl.replace(/\/$/, "")}/v1/logs`;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Build headers
|
|
139
|
-
const headers: Record<string, string> = {
|
|
140
|
-
Authorization: `Bearer ${token}`,
|
|
141
|
-
};
|
|
142
|
-
if (advanced.additionalHeaders) {
|
|
143
|
-
Object.assign(headers, advanced.additionalHeaders);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// Create OTLP log exporter with HTTP/JSON
|
|
147
|
-
const exporter = new OTLPLogExporter({
|
|
148
|
-
url: endpoint,
|
|
149
|
-
headers,
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
// Create batch processor
|
|
153
|
-
const processor = new BatchLogRecordProcessor({
|
|
154
|
-
exporter,
|
|
155
|
-
maxQueueSize: advanced.maxBatchSize ?? 100,
|
|
156
|
-
scheduledDelayMillis: advanced.flushInterval ?? 5000,
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
// Create logger provider with processor
|
|
160
|
-
this.loggerProvider = new LoggerProvider({
|
|
161
|
-
processors: [processor],
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
// Get a logger instance
|
|
165
|
-
this.otelLogger = this.loggerProvider.getLogger(
|
|
166
|
-
"@introspection-sdk/introspection-browser",
|
|
167
|
-
VERSION,
|
|
168
|
-
);
|
|
169
|
-
|
|
170
|
-
// Flush on page unload
|
|
171
|
-
if (typeof window !== "undefined") {
|
|
172
|
-
window.addEventListener("beforeunload", () => {
|
|
173
|
-
this.loggerProvider.forceFlush();
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
document.addEventListener("visibilitychange", () => {
|
|
177
|
-
if (document.visibilityState === "hidden") {
|
|
178
|
-
this.loggerProvider.forceFlush();
|
|
179
|
-
}
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
this.logger.log(`Initialized with endpoint: ${endpoint}`);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Load anonymous ID from localStorage, or generate a new one
|
|
188
|
-
*/
|
|
189
|
-
private loadAnonymousId(): string {
|
|
190
|
-
if (typeof localStorage !== "undefined") {
|
|
191
|
-
const stored = localStorage.getItem(ANONYMOUS_ID_KEY);
|
|
192
|
-
if (stored) {
|
|
193
|
-
return stored;
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
const newId = generateAnonymousId();
|
|
197
|
-
this.persistAnonymousId(newId);
|
|
198
|
-
return newId;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Persist anonymous ID to localStorage
|
|
203
|
-
*/
|
|
204
|
-
private persistAnonymousId(id: string): void {
|
|
205
|
-
if (typeof localStorage !== "undefined") {
|
|
206
|
-
localStorage.setItem(ANONYMOUS_ID_KEY, id);
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* Load user ID from localStorage
|
|
212
|
-
*/
|
|
213
|
-
private loadUserId(): string | undefined {
|
|
214
|
-
if (typeof localStorage !== "undefined") {
|
|
215
|
-
return localStorage.getItem(USER_ID_KEY) || undefined;
|
|
216
|
-
}
|
|
217
|
-
return undefined;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
/**
|
|
221
|
-
* Persist user ID to localStorage
|
|
222
|
-
*/
|
|
223
|
-
private persistUserId(id: string): void {
|
|
224
|
-
if (typeof localStorage !== "undefined") {
|
|
225
|
-
localStorage.setItem(USER_ID_KEY, id);
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
* Load traits from localStorage
|
|
231
|
-
*/
|
|
232
|
-
private loadTraits(): Record<string, unknown> {
|
|
233
|
-
if (typeof localStorage !== "undefined") {
|
|
234
|
-
const stored = localStorage.getItem(TRAITS_KEY);
|
|
235
|
-
if (stored) {
|
|
236
|
-
try {
|
|
237
|
-
return JSON.parse(stored);
|
|
238
|
-
} catch {
|
|
239
|
-
return {};
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
return {};
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
/**
|
|
247
|
-
* Persist traits to localStorage
|
|
248
|
-
*/
|
|
249
|
-
private persistTraits(traits: Record<string, unknown>): void {
|
|
250
|
-
if (typeof localStorage !== "undefined") {
|
|
251
|
-
localStorage.setItem(TRAITS_KEY, JSON.stringify(traits));
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
/** Get gen_ai context from baggage */
|
|
256
|
-
private getGenAiFromContext(): GenAiContext {
|
|
257
|
-
const baggage = propagation.getBaggage(context.active());
|
|
258
|
-
return {
|
|
259
|
-
conversationId: baggage?.getEntry("gen_ai.conversation.id")?.value,
|
|
260
|
-
previousResponseId: baggage?.getEntry(
|
|
261
|
-
"gen_ai.request.previous_response_id",
|
|
262
|
-
)?.value,
|
|
263
|
-
agentName: baggage?.getEntry("gen_ai.agent.name")?.value,
|
|
264
|
-
agentId: baggage?.getEntry("gen_ai.agent.id")?.value,
|
|
265
|
-
};
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
/** Get identity from baggage or client instance */
|
|
269
|
-
private getIdentityFromContext(): IdentityContext {
|
|
270
|
-
const baggage = propagation.getBaggage(context.active());
|
|
271
|
-
return {
|
|
272
|
-
userId: baggage?.getEntry("identity.user_id")?.value || this.userId,
|
|
273
|
-
anonymousId:
|
|
274
|
-
baggage?.getEntry("identity.anonymous_id")?.value || this.anonymousId,
|
|
275
|
-
};
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
/**
|
|
279
|
-
* Get current timestamp as OpenTelemetry HrTime.
|
|
280
|
-
* Uses performance.now() for high-resolution timing in browsers.
|
|
281
|
-
*/
|
|
282
|
-
private getTimestamp(): [number, number] {
|
|
283
|
-
const timeOrigin = performance.timeOrigin;
|
|
284
|
-
const now = performance.now();
|
|
285
|
-
const epochMs = timeOrigin + now;
|
|
286
|
-
const seconds = Math.floor(epochMs / 1000);
|
|
287
|
-
const nanos = Math.floor((epochMs % 1000) * 1_000_000);
|
|
288
|
-
return [seconds, nanos];
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
/**
|
|
292
|
-
* Build log record attributes.
|
|
293
|
-
*/
|
|
294
|
-
private buildAttributes(
|
|
295
|
-
eventName: string,
|
|
296
|
-
options: {
|
|
297
|
-
properties?: Record<string, unknown>;
|
|
298
|
-
traits?: Record<string, unknown>;
|
|
299
|
-
conversationId?: string;
|
|
300
|
-
previousResponseId?: string;
|
|
301
|
-
eventId?: string;
|
|
302
|
-
} = {},
|
|
303
|
-
): LogAttributes {
|
|
304
|
-
const { properties, traits, conversationId, previousResponseId, eventId } =
|
|
305
|
-
options;
|
|
306
|
-
|
|
307
|
-
// Core fields
|
|
308
|
-
const attributes: LogAttributes = {
|
|
309
|
-
"event.name": eventName,
|
|
310
|
-
"event.id": eventId || generateEventId(),
|
|
311
|
-
};
|
|
312
|
-
|
|
313
|
-
// Browser context
|
|
314
|
-
if (typeof window !== "undefined") {
|
|
315
|
-
attributes["context.page.path"] = window.location.pathname;
|
|
316
|
-
attributes["context.page.url"] = window.location.href;
|
|
317
|
-
attributes["context.page.title"] = document.title;
|
|
318
|
-
attributes["context.page.referrer"] = document.referrer;
|
|
319
|
-
attributes["context.page.search"] = window.location.search;
|
|
320
|
-
attributes["context.userAgent"] = navigator.userAgent;
|
|
321
|
-
attributes["context.locale"] = navigator.language;
|
|
322
|
-
attributes["context.timezone"] =
|
|
323
|
-
Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
324
|
-
attributes["context.screen.width"] = window.screen.width;
|
|
325
|
-
attributes["context.screen.height"] = window.screen.height;
|
|
326
|
-
attributes["context.screen.density"] = window.devicePixelRatio;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
// Identity
|
|
330
|
-
const identity = this.getIdentityFromContext();
|
|
331
|
-
if (identity.userId) {
|
|
332
|
-
attributes["identity.user.id"] = identity.userId;
|
|
333
|
-
}
|
|
334
|
-
if (identity.anonymousId) {
|
|
335
|
-
attributes["identity.anonymous.id"] = identity.anonymousId;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
// Gen AI context (explicit params override baggage)
|
|
339
|
-
const genAi = this.getGenAiFromContext();
|
|
340
|
-
const finalConversationId = conversationId || genAi.conversationId;
|
|
341
|
-
const finalPreviousResponseId =
|
|
342
|
-
previousResponseId || genAi.previousResponseId;
|
|
343
|
-
|
|
344
|
-
if (finalConversationId) {
|
|
345
|
-
attributes["gen_ai.conversation.id"] = finalConversationId;
|
|
346
|
-
}
|
|
347
|
-
if (finalPreviousResponseId) {
|
|
348
|
-
attributes["gen_ai.request.previous_response_id"] =
|
|
349
|
-
finalPreviousResponseId;
|
|
350
|
-
}
|
|
351
|
-
if (genAi.agentName) {
|
|
352
|
-
attributes["gen_ai.agent.name"] = genAi.agentName;
|
|
353
|
-
}
|
|
354
|
-
if (genAi.agentId) {
|
|
355
|
-
attributes["gen_ai.agent.id"] = genAi.agentId;
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
// Flatten properties with "properties." prefix
|
|
359
|
-
if (properties) {
|
|
360
|
-
for (const [key, value] of Object.entries(properties)) {
|
|
361
|
-
if (value != null) {
|
|
362
|
-
const attrKey = `properties.${key}`;
|
|
363
|
-
if (typeof value === "object") {
|
|
364
|
-
attributes[attrKey] = JSON.stringify(value);
|
|
365
|
-
} else if (
|
|
366
|
-
typeof value === "string" ||
|
|
367
|
-
typeof value === "number" ||
|
|
368
|
-
typeof value === "boolean"
|
|
369
|
-
) {
|
|
370
|
-
attributes[attrKey] = value;
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
// Flatten traits with "context.traits." prefix
|
|
377
|
-
if (traits) {
|
|
378
|
-
for (const [key, value] of Object.entries(traits)) {
|
|
379
|
-
if (value != null) {
|
|
380
|
-
const attrKey = `context.traits.${key}`;
|
|
381
|
-
if (typeof value === "object") {
|
|
382
|
-
attributes[attrKey] = JSON.stringify(value);
|
|
383
|
-
} else if (
|
|
384
|
-
typeof value === "string" ||
|
|
385
|
-
typeof value === "number" ||
|
|
386
|
-
typeof value === "boolean"
|
|
387
|
-
) {
|
|
388
|
-
attributes[attrKey] = value;
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
return attributes;
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
/**
|
|
398
|
-
* Track a custom event.
|
|
399
|
-
*
|
|
400
|
-
* @param eventName - The event name (e.g., `"Button Clicked"`).
|
|
401
|
-
* @param properties - Optional properties to attach to the event.
|
|
402
|
-
* @param options - Optional overrides (e.g., custom `eventId`).
|
|
403
|
-
*
|
|
404
|
-
* @example
|
|
405
|
-
* ```ts
|
|
406
|
-
* client.track("Button Clicked", { buttonId: "submit" });
|
|
407
|
-
* ```
|
|
408
|
-
*/
|
|
409
|
-
track(
|
|
410
|
-
eventName: string,
|
|
411
|
-
properties?: Record<string, unknown>,
|
|
412
|
-
options?: { eventId?: string },
|
|
413
|
-
): void {
|
|
414
|
-
const attributes = this.buildAttributes(eventName, {
|
|
415
|
-
properties,
|
|
416
|
-
eventId: options?.eventId,
|
|
417
|
-
});
|
|
418
|
-
|
|
419
|
-
this.otelLogger.emit({
|
|
420
|
-
timestamp: this.getTimestamp(),
|
|
421
|
-
context: context.active(),
|
|
422
|
-
severityNumber: SeverityNumber.INFO,
|
|
423
|
-
severityText: "INFO",
|
|
424
|
-
attributes,
|
|
425
|
-
});
|
|
426
|
-
|
|
427
|
-
this.logger.log(`Tracked: ${eventName}`);
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
/**
|
|
431
|
-
* Track feedback on a message or response.
|
|
432
|
-
*
|
|
433
|
-
* gen_ai.conversation.id, gen_ai.request.previous_response_id, and agent context
|
|
434
|
-
* are automatically extracted from baggage if not provided explicitly.
|
|
435
|
-
*
|
|
436
|
-
* @param name - Feedback name/action (e.g., "thumbs_up", "thumbs_down")
|
|
437
|
-
* @param options - Optional feedback options
|
|
438
|
-
*
|
|
439
|
-
* @example
|
|
440
|
-
* ```typescript
|
|
441
|
-
* // Simple feedback
|
|
442
|
-
* client.feedback("thumbs_up");
|
|
443
|
-
*
|
|
444
|
-
* // With comments
|
|
445
|
-
* client.feedback("thumbs_down", { comments: "Off topic" });
|
|
446
|
-
*
|
|
447
|
-
* // With explicit gen_ai context
|
|
448
|
-
* client.feedback("thumbs_up", {
|
|
449
|
-
* conversationId: "conv_456",
|
|
450
|
-
* previousResponseId: "msg_123",
|
|
451
|
-
* });
|
|
452
|
-
*
|
|
453
|
-
* // With baggage context
|
|
454
|
-
* await client.withConversation("conv_456", undefined, async () => {
|
|
455
|
-
* client.feedback("thumbs_up"); // auto-includes conversation_id
|
|
456
|
-
* });
|
|
457
|
-
* ```
|
|
458
|
-
*/
|
|
459
|
-
feedback(name: string, options: FeedbackOptions = {}): void {
|
|
460
|
-
const { comments, conversationId, previousResponseId, eventId, ...extra } =
|
|
461
|
-
options;
|
|
462
|
-
|
|
463
|
-
const properties: Record<string, unknown> = { name, ...extra };
|
|
464
|
-
if (comments) {
|
|
465
|
-
properties.comments = comments;
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
const attributes = this.buildAttributes("introspection.feedback", {
|
|
469
|
-
properties,
|
|
470
|
-
conversationId: conversationId as string | undefined,
|
|
471
|
-
previousResponseId: previousResponseId as string | undefined,
|
|
472
|
-
eventId: eventId as string | undefined,
|
|
473
|
-
});
|
|
474
|
-
|
|
475
|
-
this.otelLogger.emit({
|
|
476
|
-
timestamp: this.getTimestamp(),
|
|
477
|
-
context: context.active(),
|
|
478
|
-
severityNumber: SeverityNumber.INFO,
|
|
479
|
-
severityText: "INFO",
|
|
480
|
-
attributes,
|
|
481
|
-
});
|
|
482
|
-
|
|
483
|
-
this.logger.log(`Feedback: ${name}`);
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
/**
|
|
487
|
-
* Identify a user and persist their traits to `localStorage`.
|
|
488
|
-
*
|
|
489
|
-
* @param userId - The user's unique identifier.
|
|
490
|
-
* @param traits - Optional user traits (email, name, plan, etc.).
|
|
491
|
-
* @param anonymousId - Optional anonymous ID to associate.
|
|
492
|
-
* @param eventId - Optional event ID (auto-generated if omitted).
|
|
493
|
-
*
|
|
494
|
-
* @example
|
|
495
|
-
* ```ts
|
|
496
|
-
* client.identify("user-123", { email: "user@example.com", plan: "pro" });
|
|
497
|
-
* ```
|
|
498
|
-
*/
|
|
499
|
-
identify(
|
|
500
|
-
userId: string,
|
|
501
|
-
traits?: UserTraits,
|
|
502
|
-
anonymousId?: string,
|
|
503
|
-
eventId?: string,
|
|
504
|
-
): void {
|
|
505
|
-
// Store and persist
|
|
506
|
-
this.userId = userId;
|
|
507
|
-
this.persistUserId(userId);
|
|
508
|
-
if (anonymousId) {
|
|
509
|
-
this.anonymousId = anonymousId;
|
|
510
|
-
this.persistAnonymousId(anonymousId);
|
|
511
|
-
}
|
|
512
|
-
if (traits) {
|
|
513
|
-
this.traits = { ...this.traits, ...traits };
|
|
514
|
-
this.persistTraits(this.traits);
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
const attributes = this.buildAttributes("identify", { traits, eventId });
|
|
518
|
-
|
|
519
|
-
this.otelLogger.emit({
|
|
520
|
-
timestamp: this.getTimestamp(),
|
|
521
|
-
context: context.active(),
|
|
522
|
-
severityNumber: SeverityNumber.INFO,
|
|
523
|
-
severityText: "INFO",
|
|
524
|
-
attributes,
|
|
525
|
-
});
|
|
526
|
-
|
|
527
|
-
this.logger.log(`Identified: ${userId}`);
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
/**
|
|
531
|
-
* Set the authenticated user ID and persist it to `localStorage`.
|
|
532
|
-
*
|
|
533
|
-
* @param userId - The user's unique identifier.
|
|
534
|
-
*
|
|
535
|
-
* @example
|
|
536
|
-
* ```ts
|
|
537
|
-
* client.setUserId("user-123");
|
|
538
|
-
* ```
|
|
539
|
-
*/
|
|
540
|
-
setUserId(userId: string): void {
|
|
541
|
-
this.userId = userId;
|
|
542
|
-
this.persistUserId(userId);
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
/**
|
|
546
|
-
* Override the anonymous ID and persist it to `localStorage`.
|
|
547
|
-
*
|
|
548
|
-
* @param anonymousId - The anonymous visitor identifier.
|
|
549
|
-
*
|
|
550
|
-
* @example
|
|
551
|
-
* ```ts
|
|
552
|
-
* client.setAnonymousId("anon-abc");
|
|
553
|
-
* ```
|
|
554
|
-
*/
|
|
555
|
-
setAnonymousId(anonymousId: string): void {
|
|
556
|
-
this.anonymousId = anonymousId;
|
|
557
|
-
this.persistAnonymousId(anonymousId);
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
/**
|
|
561
|
-
* Get the current anonymous visitor identifier.
|
|
562
|
-
*
|
|
563
|
-
* @returns The anonymous ID (auto-generated on first access if not set).
|
|
564
|
-
*
|
|
565
|
-
* @example
|
|
566
|
-
* ```ts
|
|
567
|
-
* const anonId = client.getAnonymousId();
|
|
568
|
-
* ```
|
|
569
|
-
*/
|
|
570
|
-
getAnonymousId(): string {
|
|
571
|
-
return this.anonymousId;
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
/**
|
|
575
|
-
* Create a context with baggage values set.
|
|
576
|
-
* Use with context.with() to run code in that context.
|
|
577
|
-
*
|
|
578
|
-
* Note: In browsers, automatic context propagation requires Zone.js.
|
|
579
|
-
* Without it, you must manually pass context through async operations.
|
|
580
|
-
*
|
|
581
|
-
* @param values - Key/value pairs to set as baggage
|
|
582
|
-
* @returns Context with baggage set
|
|
583
|
-
*
|
|
584
|
-
* @example
|
|
585
|
-
* ```typescript
|
|
586
|
-
* const ctx = client.createBaggageContext({
|
|
587
|
-
* "gen_ai.agent.name": "support-bot",
|
|
588
|
-
* "gen_ai.conversation.id": "conv_456"
|
|
589
|
-
* });
|
|
590
|
-
*
|
|
591
|
-
* await context.with(ctx, async () => {
|
|
592
|
-
* client.feedback("thumbs_up"); // picks up baggage
|
|
593
|
-
* });
|
|
594
|
-
* ```
|
|
595
|
-
*/
|
|
596
|
-
createBaggageContext(values: Record<string, string>) {
|
|
597
|
-
const ctx = context.active();
|
|
598
|
-
let bag = propagation.getBaggage(ctx) || propagation.createBaggage();
|
|
599
|
-
|
|
600
|
-
for (const [key, value] of Object.entries(values)) {
|
|
601
|
-
bag = bag.setEntry(key, { value });
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
return propagation.setBaggage(ctx, bag);
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
/**
|
|
608
|
-
* Helper to set multiple baggage values and run a callback.
|
|
609
|
-
*
|
|
610
|
-
* @param values - Key/value pairs to set as baggage.
|
|
611
|
-
* @param callback - Function to run with baggage context.
|
|
612
|
-
* @returns The return value of the callback.
|
|
613
|
-
*
|
|
614
|
-
* @example
|
|
615
|
-
* ```typescript
|
|
616
|
-
* await client.withBaggage({
|
|
617
|
-
* "gen_ai.conversation.id": "conv_456",
|
|
618
|
-
* "gen_ai.request.previous_response_id": "resp_123"
|
|
619
|
-
* }, async () => {
|
|
620
|
-
* client.feedback("thumbs_up");
|
|
621
|
-
* });
|
|
622
|
-
* ```
|
|
623
|
-
*/
|
|
624
|
-
async withBaggage<T>(
|
|
625
|
-
values: Record<string, string>,
|
|
626
|
-
callback: () => T | Promise<T>,
|
|
627
|
-
): Promise<T> {
|
|
628
|
-
const ctx = this.createBaggageContext(values);
|
|
629
|
-
return await context.with(ctx, callback);
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
/**
|
|
633
|
-
* Helper to set agent baggage and run a callback.
|
|
634
|
-
*
|
|
635
|
-
* @param agentName - Name of the agent.
|
|
636
|
-
* @param agentId - Optional unique identifier for the agent.
|
|
637
|
-
* @param callback - Function to run with agent context.
|
|
638
|
-
* @returns The return value of the callback.
|
|
639
|
-
*
|
|
640
|
-
* @example
|
|
641
|
-
* ```typescript
|
|
642
|
-
* await client.withAgent("support-bot", "agent_123", async () => {
|
|
643
|
-
* client.feedback("thumbs_up");
|
|
644
|
-
* });
|
|
645
|
-
* ```
|
|
646
|
-
*/
|
|
647
|
-
async withAgent<T>(
|
|
648
|
-
agentName: string,
|
|
649
|
-
agentId: string | undefined,
|
|
650
|
-
callback: () => T | Promise<T>,
|
|
651
|
-
): Promise<T> {
|
|
652
|
-
const values: Record<string, string> = {
|
|
653
|
-
"gen_ai.agent.name": agentName,
|
|
654
|
-
};
|
|
655
|
-
if (agentId) {
|
|
656
|
-
values["gen_ai.agent.id"] = agentId;
|
|
657
|
-
}
|
|
658
|
-
const ctx = this.createBaggageContext(values);
|
|
659
|
-
return await context.with(ctx, callback);
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
/**
|
|
663
|
-
* Helper to set conversation baggage and run a callback.
|
|
664
|
-
*
|
|
665
|
-
* @param conversationId - Unique identifier for the conversation (optional).
|
|
666
|
-
* @param previousResponseId - Previous response ID for conversation continuity (optional).
|
|
667
|
-
* @param callback - Function to run with conversation context.
|
|
668
|
-
* @returns The return value of the callback.
|
|
669
|
-
*
|
|
670
|
-
* @example
|
|
671
|
-
* ```typescript
|
|
672
|
-
* // Simple conversation context
|
|
673
|
-
* await client.withConversation("conv_456", undefined, async () => {
|
|
674
|
-
* client.feedback("thumbs_up");
|
|
675
|
-
* });
|
|
676
|
-
*
|
|
677
|
-
* // With previous response for conversation continuity
|
|
678
|
-
* await client.withConversation("conv_456", "resp_123", async () => {
|
|
679
|
-
* client.feedback("thumbs_up");
|
|
680
|
-
* });
|
|
681
|
-
*
|
|
682
|
-
* // Just previousResponseId when already in a conversation
|
|
683
|
-
* await client.withConversation("conv_456", undefined, async () => {
|
|
684
|
-
* await client.withConversation(undefined, "resp_1", async () => {
|
|
685
|
-
* client.feedback("thumbs_up");
|
|
686
|
-
* });
|
|
687
|
-
* });
|
|
688
|
-
* ```
|
|
689
|
-
*/
|
|
690
|
-
async withConversation<T>(
|
|
691
|
-
conversationId: string | undefined,
|
|
692
|
-
previousResponseId: string | undefined,
|
|
693
|
-
callback: () => T | Promise<T>,
|
|
694
|
-
): Promise<T> {
|
|
695
|
-
const values: Record<string, string> = {};
|
|
696
|
-
if (conversationId) {
|
|
697
|
-
values["gen_ai.conversation.id"] = conversationId;
|
|
698
|
-
}
|
|
699
|
-
if (previousResponseId) {
|
|
700
|
-
values["gen_ai.request.previous_response_id"] = previousResponseId;
|
|
701
|
-
}
|
|
702
|
-
const ctx = this.createBaggageContext(values);
|
|
703
|
-
return await context.with(ctx, callback);
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
/**
|
|
707
|
-
* Helper to set user ID baggage and run a callback.
|
|
708
|
-
*
|
|
709
|
-
* @param userId - User identifier.
|
|
710
|
-
* @param callback - Function to run with user context.
|
|
711
|
-
* @returns The return value of the callback.
|
|
712
|
-
*
|
|
713
|
-
* @example
|
|
714
|
-
* ```typescript
|
|
715
|
-
* await client.withUserId("user_123", async () => {
|
|
716
|
-
* client.feedback("thumbs_up");
|
|
717
|
-
* });
|
|
718
|
-
* ```
|
|
719
|
-
*/
|
|
720
|
-
async withUserId<T>(
|
|
721
|
-
userId: string,
|
|
722
|
-
callback: () => T | Promise<T>,
|
|
723
|
-
): Promise<T> {
|
|
724
|
-
const ctx = this.createBaggageContext({
|
|
725
|
-
"identity.user_id": userId,
|
|
726
|
-
});
|
|
727
|
-
return await context.with(ctx, callback);
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
/**
|
|
731
|
-
* Helper to set anonymous ID baggage and run a callback.
|
|
732
|
-
*
|
|
733
|
-
* @param anonymousId - Anonymous visitor identifier.
|
|
734
|
-
* @param callback - Function to run with anonymous context.
|
|
735
|
-
* @returns The return value of the callback.
|
|
736
|
-
*
|
|
737
|
-
* @example
|
|
738
|
-
* ```ts
|
|
739
|
-
* await client.withAnonymousId("anon-abc", async () => {
|
|
740
|
-
* client.feedback("thumbs_up");
|
|
741
|
-
* });
|
|
742
|
-
* ```
|
|
743
|
-
*/
|
|
744
|
-
async withAnonymousId<T>(
|
|
745
|
-
anonymousId: string,
|
|
746
|
-
callback: () => T | Promise<T>,
|
|
747
|
-
): Promise<T> {
|
|
748
|
-
const ctx = this.createBaggageContext({
|
|
749
|
-
"identity.anonymous_id": anonymousId,
|
|
750
|
-
});
|
|
751
|
-
return await context.with(ctx, callback);
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
/**
|
|
755
|
-
* Reset the client state, clearing user identity and generating a new
|
|
756
|
-
* anonymous ID. Equivalent to a logout operation.
|
|
757
|
-
*
|
|
758
|
-
* @example
|
|
759
|
-
* ```ts
|
|
760
|
-
* client.reset();
|
|
761
|
-
* ```
|
|
762
|
-
*/
|
|
763
|
-
reset(): void {
|
|
764
|
-
this.userId = undefined;
|
|
765
|
-
this.anonymousId = generateAnonymousId();
|
|
766
|
-
this.traits = {};
|
|
767
|
-
|
|
768
|
-
if (typeof localStorage !== "undefined") {
|
|
769
|
-
localStorage.removeItem(USER_ID_KEY);
|
|
770
|
-
localStorage.removeItem(TRAITS_KEY);
|
|
771
|
-
localStorage.setItem(ANONYMOUS_ID_KEY, this.anonymousId);
|
|
772
|
-
}
|
|
773
|
-
|
|
774
|
-
this.logger.log("Reset complete");
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
/**
|
|
778
|
-
* Flush all queued log records to the Introspection backend.
|
|
779
|
-
*
|
|
780
|
-
* @returns A promise that resolves once the flush completes.
|
|
781
|
-
*/
|
|
782
|
-
async flush(): Promise<void> {
|
|
783
|
-
await this.loggerProvider.forceFlush();
|
|
784
|
-
this.logger.log("Flushed pending log records");
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
/**
|
|
788
|
-
* Shut down the client, flushing all pending log records.
|
|
789
|
-
*
|
|
790
|
-
* @returns A promise that resolves once shutdown is complete.
|
|
791
|
-
*/
|
|
792
|
-
async shutdown(): Promise<void> {
|
|
793
|
-
await this.loggerProvider.shutdown();
|
|
794
|
-
this.logger.log("Shutdown complete");
|
|
795
|
-
}
|
|
796
|
-
}
|