@od-oneapp/analytics 2026.1.1301

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (184) hide show
  1. package/README.md +509 -0
  2. package/dist/ai-YMnynb-t.mjs +3347 -0
  3. package/dist/ai-YMnynb-t.mjs.map +1 -0
  4. package/dist/chunk-DQk6qfdC.mjs +18 -0
  5. package/dist/client-CTzJVFU5.mjs +9 -0
  6. package/dist/client-CTzJVFU5.mjs.map +1 -0
  7. package/dist/client-CcFTauAh.mjs +54 -0
  8. package/dist/client-CcFTauAh.mjs.map +1 -0
  9. package/dist/client-CeOLjbac.mjs +281 -0
  10. package/dist/client-CeOLjbac.mjs.map +1 -0
  11. package/dist/client-D339NFJS.mjs +267 -0
  12. package/dist/client-D339NFJS.mjs.map +1 -0
  13. package/dist/client-next.d.mts +62 -0
  14. package/dist/client-next.d.mts.map +1 -0
  15. package/dist/client-next.mjs +525 -0
  16. package/dist/client-next.mjs.map +1 -0
  17. package/dist/client.d.mts +30 -0
  18. package/dist/client.d.mts.map +1 -0
  19. package/dist/client.mjs +186 -0
  20. package/dist/client.mjs.map +1 -0
  21. package/dist/config-DPS6bSYo.d.mts +34 -0
  22. package/dist/config-DPS6bSYo.d.mts.map +1 -0
  23. package/dist/config-P6P5adJg.mjs +287 -0
  24. package/dist/config-P6P5adJg.mjs.map +1 -0
  25. package/dist/console-8bND3mMU.mjs +128 -0
  26. package/dist/console-8bND3mMU.mjs.map +1 -0
  27. package/dist/ecommerce-Cgu4wlux.mjs +993 -0
  28. package/dist/ecommerce-Cgu4wlux.mjs.map +1 -0
  29. package/dist/emitters-6-nKo8i-.mjs +208 -0
  30. package/dist/emitters-6-nKo8i-.mjs.map +1 -0
  31. package/dist/emitters-DldkVSPp.d.mts +12 -0
  32. package/dist/emitters-DldkVSPp.d.mts.map +1 -0
  33. package/dist/index-BfNWgfa5.d.mts +1494 -0
  34. package/dist/index-BfNWgfa5.d.mts.map +1 -0
  35. package/dist/index-BkIWe--N.d.mts +953 -0
  36. package/dist/index-BkIWe--N.d.mts.map +1 -0
  37. package/dist/index-jPzXRn52.d.mts +184 -0
  38. package/dist/index-jPzXRn52.d.mts.map +1 -0
  39. package/dist/manager-DvRRjza6.d.mts +76 -0
  40. package/dist/manager-DvRRjza6.d.mts.map +1 -0
  41. package/dist/posthog-bootstrap-CYfIy_WS.mjs +1769 -0
  42. package/dist/posthog-bootstrap-CYfIy_WS.mjs.map +1 -0
  43. package/dist/posthog-bootstrap-DWxFrxlt.d.mts +81 -0
  44. package/dist/posthog-bootstrap-DWxFrxlt.d.mts.map +1 -0
  45. package/dist/providers-http-client.d.mts +37 -0
  46. package/dist/providers-http-client.d.mts.map +1 -0
  47. package/dist/providers-http-client.mjs +320 -0
  48. package/dist/providers-http-client.mjs.map +1 -0
  49. package/dist/providers-http-server.d.mts +31 -0
  50. package/dist/providers-http-server.d.mts.map +1 -0
  51. package/dist/providers-http-server.mjs +297 -0
  52. package/dist/providers-http-server.mjs.map +1 -0
  53. package/dist/providers-http.d.mts +46 -0
  54. package/dist/providers-http.d.mts.map +1 -0
  55. package/dist/providers-http.mjs +4 -0
  56. package/dist/server-edge.d.mts +9 -0
  57. package/dist/server-edge.d.mts.map +1 -0
  58. package/dist/server-edge.mjs +373 -0
  59. package/dist/server-edge.mjs.map +1 -0
  60. package/dist/server-next.d.mts +67 -0
  61. package/dist/server-next.d.mts.map +1 -0
  62. package/dist/server-next.mjs +193 -0
  63. package/dist/server-next.mjs.map +1 -0
  64. package/dist/server.d.mts +10 -0
  65. package/dist/server.mjs +7 -0
  66. package/dist/service-cYtBBL8x.mjs +945 -0
  67. package/dist/service-cYtBBL8x.mjs.map +1 -0
  68. package/dist/shared.d.mts +16 -0
  69. package/dist/shared.d.mts.map +1 -0
  70. package/dist/shared.mjs +93 -0
  71. package/dist/shared.mjs.map +1 -0
  72. package/dist/types-BxBnNQ0V.d.mts +354 -0
  73. package/dist/types-BxBnNQ0V.d.mts.map +1 -0
  74. package/dist/types-CBvxUEaF.d.mts +216 -0
  75. package/dist/types-CBvxUEaF.d.mts.map +1 -0
  76. package/dist/types.d.mts +4 -0
  77. package/dist/types.mjs +0 -0
  78. package/dist/vercel-types-lwakUfoI.d.mts +102 -0
  79. package/dist/vercel-types-lwakUfoI.d.mts.map +1 -0
  80. package/package.json +129 -0
  81. package/src/client/index.ts +164 -0
  82. package/src/client/manager.ts +71 -0
  83. package/src/client/next/components.tsx +270 -0
  84. package/src/client/next/hooks.ts +217 -0
  85. package/src/client/next/manager.ts +141 -0
  86. package/src/client/next.ts +144 -0
  87. package/src/client-next.ts +101 -0
  88. package/src/client.ts +89 -0
  89. package/src/examples/ai-sdk-patterns.ts +583 -0
  90. package/src/examples/emitter-patterns.ts +476 -0
  91. package/src/examples/nextjs-emitter-patterns.tsx +403 -0
  92. package/src/next/app-router.tsx +564 -0
  93. package/src/next/client.ts +419 -0
  94. package/src/next/index.ts +84 -0
  95. package/src/next/middleware.ts +429 -0
  96. package/src/next/rsc.tsx +300 -0
  97. package/src/next/server.ts +253 -0
  98. package/src/next/types.d.ts +220 -0
  99. package/src/providers/base-provider.ts +419 -0
  100. package/src/providers/console/client.ts +10 -0
  101. package/src/providers/console/index.ts +152 -0
  102. package/src/providers/console/server.ts +6 -0
  103. package/src/providers/console/types.ts +15 -0
  104. package/src/providers/http/client.ts +464 -0
  105. package/src/providers/http/index.ts +30 -0
  106. package/src/providers/http/server.ts +396 -0
  107. package/src/providers/http/types.ts +135 -0
  108. package/src/providers/posthog/client.ts +518 -0
  109. package/src/providers/posthog/index.ts +11 -0
  110. package/src/providers/posthog/server.ts +329 -0
  111. package/src/providers/posthog/types.ts +104 -0
  112. package/src/providers/segment/client.ts +113 -0
  113. package/src/providers/segment/index.ts +11 -0
  114. package/src/providers/segment/server.ts +115 -0
  115. package/src/providers/segment/types.ts +51 -0
  116. package/src/providers/vercel/client.ts +102 -0
  117. package/src/providers/vercel/index.ts +11 -0
  118. package/src/providers/vercel/server.ts +89 -0
  119. package/src/providers/vercel/types.ts +27 -0
  120. package/src/server/index.ts +103 -0
  121. package/src/server/manager.ts +62 -0
  122. package/src/server/next.ts +210 -0
  123. package/src/server-edge.ts +442 -0
  124. package/src/server-next.ts +39 -0
  125. package/src/server.ts +106 -0
  126. package/src/shared/emitters/ai/README.md +981 -0
  127. package/src/shared/emitters/ai/events/agent.ts +130 -0
  128. package/src/shared/emitters/ai/events/artifacts.ts +167 -0
  129. package/src/shared/emitters/ai/events/chat.ts +126 -0
  130. package/src/shared/emitters/ai/events/chatbot-ecommerce.ts +133 -0
  131. package/src/shared/emitters/ai/events/completion.ts +103 -0
  132. package/src/shared/emitters/ai/events/content-generation.ts +347 -0
  133. package/src/shared/emitters/ai/events/conversation.ts +332 -0
  134. package/src/shared/emitters/ai/events/product-features.ts +1402 -0
  135. package/src/shared/emitters/ai/events/streaming.ts +114 -0
  136. package/src/shared/emitters/ai/events/tool.ts +93 -0
  137. package/src/shared/emitters/ai/index.ts +69 -0
  138. package/src/shared/emitters/ai/track-ai-sdk.ts +74 -0
  139. package/src/shared/emitters/ai/track-ai.ts +50 -0
  140. package/src/shared/emitters/ai/types.ts +1041 -0
  141. package/src/shared/emitters/ai/utils.ts +468 -0
  142. package/src/shared/emitters/ecommerce/events/cart-checkout.ts +106 -0
  143. package/src/shared/emitters/ecommerce/events/coupon.ts +49 -0
  144. package/src/shared/emitters/ecommerce/events/engagement.ts +61 -0
  145. package/src/shared/emitters/ecommerce/events/marketplace.ts +119 -0
  146. package/src/shared/emitters/ecommerce/events/order.ts +199 -0
  147. package/src/shared/emitters/ecommerce/events/product.ts +205 -0
  148. package/src/shared/emitters/ecommerce/events/registry.ts +123 -0
  149. package/src/shared/emitters/ecommerce/events/wishlist-sharing.ts +140 -0
  150. package/src/shared/emitters/ecommerce/index.ts +46 -0
  151. package/src/shared/emitters/ecommerce/track-ecommerce.ts +53 -0
  152. package/src/shared/emitters/ecommerce/types.ts +314 -0
  153. package/src/shared/emitters/ecommerce/utils.ts +216 -0
  154. package/src/shared/emitters/emitter-types.ts +974 -0
  155. package/src/shared/emitters/emitters.ts +292 -0
  156. package/src/shared/emitters/helpers.ts +419 -0
  157. package/src/shared/emitters/index.ts +66 -0
  158. package/src/shared/index.ts +142 -0
  159. package/src/shared/ingestion/index.ts +66 -0
  160. package/src/shared/ingestion/schemas.ts +386 -0
  161. package/src/shared/ingestion/service.ts +628 -0
  162. package/src/shared/node22-features.ts +848 -0
  163. package/src/shared/providers/console-provider.ts +160 -0
  164. package/src/shared/types/base-types.ts +54 -0
  165. package/src/shared/types/console-types.ts +19 -0
  166. package/src/shared/types/posthog-types.ts +131 -0
  167. package/src/shared/types/segment-types.ts +15 -0
  168. package/src/shared/types/types.ts +397 -0
  169. package/src/shared/types/vercel-types.ts +19 -0
  170. package/src/shared/utils/config-client.ts +19 -0
  171. package/src/shared/utils/config.ts +250 -0
  172. package/src/shared/utils/emitter-adapter.ts +212 -0
  173. package/src/shared/utils/manager.test.ts +36 -0
  174. package/src/shared/utils/manager.ts +1322 -0
  175. package/src/shared/utils/posthog-bootstrap.ts +136 -0
  176. package/src/shared/utils/posthog-client-utils.ts +48 -0
  177. package/src/shared/utils/posthog-next-utils.ts +282 -0
  178. package/src/shared/utils/posthog-server-utils.ts +210 -0
  179. package/src/shared/utils/rate-limit.ts +289 -0
  180. package/src/shared/utils/security.ts +545 -0
  181. package/src/shared/utils/validation-client.ts +161 -0
  182. package/src/shared/utils/validation.ts +399 -0
  183. package/src/shared.ts +155 -0
  184. package/src/types/index.ts +62 -0
@@ -0,0 +1,468 @@
1
+ /**
2
+ * @fileoverview Utility functions for AI event tracking
3
+ * Utility functions for AI event tracking
4
+ */
5
+
6
+ import type { BaseAIProperties, ModelConfigProperties, TokenUsageProperties } from './types';
7
+
8
+ /**
9
+ * Remove undefined and null values from an object
10
+ */
11
+ export function cleanProperties<T extends Record<string, any>>(props: T): Partial<T> {
12
+ const cleaned: Partial<T> = {};
13
+
14
+ for (const key in props) {
15
+ if (!Object.prototype.hasOwnProperty.call(props, key)) continue;
16
+ const value = props[key];
17
+ if (value !== undefined && value !== null) {
18
+ cleaned[key] = value;
19
+ }
20
+ }
21
+
22
+ return cleaned;
23
+ }
24
+
25
+ /**
26
+ * Validate that required properties are present
27
+ */
28
+ export function validateRequiredProperties<T extends Record<string, any>>(
29
+ properties: T,
30
+ required: (keyof T)[],
31
+ ): void {
32
+ const missing: string[] = [];
33
+
34
+ for (const key of required) {
35
+ if (properties[key] === undefined || properties[key] === null) {
36
+ missing.push(String(key));
37
+ }
38
+ }
39
+
40
+ if (missing.length > 0) {
41
+ throw new Error(`Missing required properties: ${missing.join(', ')}`);
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Normalize base AI operation properties
47
+ */
48
+ export function normalizeBaseProperties(props: BaseAIProperties): BaseAIProperties {
49
+ return {
50
+ model_id: props.model_id,
51
+ agent_id: props.agent_id,
52
+ agent_name: props.agent_name,
53
+ conversation_id: props.conversation_id,
54
+ session_id: props.session_id,
55
+ user_id: props.user_id,
56
+ operation_id: props.operation_id,
57
+ model_provider: props.model_provider,
58
+ };
59
+ }
60
+
61
+ /**
62
+ * Normalize token usage properties
63
+ */
64
+ export function normalizeTokenUsage(props: TokenUsageProperties): TokenUsageProperties {
65
+ const normalized: TokenUsageProperties = {};
66
+
67
+ if (props.input_tokens !== undefined) {
68
+ normalized.input_tokens = Math.max(0, props.input_tokens);
69
+ }
70
+
71
+ if (props.output_tokens !== undefined) {
72
+ normalized.output_tokens = Math.max(0, props.output_tokens);
73
+ }
74
+
75
+ if (props.total_tokens !== undefined) {
76
+ normalized.total_tokens = Math.max(0, props.total_tokens);
77
+ } else if (normalized.input_tokens !== undefined && normalized.output_tokens !== undefined) {
78
+ // Calculate total from already-normalized values (clamped to 0)
79
+ normalized.total_tokens = normalized.input_tokens + normalized.output_tokens;
80
+ }
81
+
82
+ if (props.cost !== undefined) {
83
+ normalized.cost = Math.max(0, props.cost);
84
+ normalized.cost_currency = props.cost_currency ?? 'USD';
85
+ }
86
+
87
+ return normalized;
88
+ }
89
+
90
+ /**
91
+ * Normalize model configuration properties
92
+ */
93
+ export function normalizeModelConfig(props: ModelConfigProperties): ModelConfigProperties {
94
+ const normalized: ModelConfigProperties = {};
95
+
96
+ if (props.temperature !== undefined) {
97
+ normalized.temperature = Math.max(0, Math.min(2, props.temperature));
98
+ }
99
+
100
+ if (props.max_tokens !== undefined) {
101
+ normalized.max_tokens = Math.max(1, props.max_tokens);
102
+ }
103
+
104
+ if (props.max_output_tokens !== undefined) {
105
+ normalized.max_output_tokens = Math.max(1, props.max_output_tokens);
106
+ }
107
+
108
+ if (props.top_p !== undefined) {
109
+ normalized.top_p = Math.max(0, Math.min(1, props.top_p));
110
+ }
111
+
112
+ if (props.frequency_penalty !== undefined) {
113
+ normalized.frequency_penalty = Math.max(-2, Math.min(2, props.frequency_penalty));
114
+ }
115
+
116
+ if (props.presence_penalty !== undefined) {
117
+ normalized.presence_penalty = Math.max(-2, Math.min(2, props.presence_penalty));
118
+ }
119
+
120
+ if (props.seed !== undefined) {
121
+ normalized.seed = props.seed;
122
+ }
123
+
124
+ if (props.stop_sequences !== undefined) {
125
+ normalized.stop_sequences = props.stop_sequences;
126
+ }
127
+
128
+ return normalized;
129
+ }
130
+
131
+ /**
132
+ * Calculate cost based on token usage and model pricing
133
+ */
134
+ export function calculateCost(
135
+ usage: TokenUsageProperties,
136
+ pricing: {
137
+ input_price_per_1k?: number;
138
+ output_price_per_1k?: number;
139
+ },
140
+ ): number {
141
+ let cost = 0;
142
+
143
+ if (usage.input_tokens && pricing.input_price_per_1k) {
144
+ cost += (usage.input_tokens / 1000) * pricing.input_price_per_1k;
145
+ }
146
+
147
+ if (usage.output_tokens && pricing.output_price_per_1k) {
148
+ cost += (usage.output_tokens / 1000) * pricing.output_price_per_1k;
149
+ }
150
+
151
+ return cost;
152
+ }
153
+
154
+ /**
155
+ * Extract model provider from model ID
156
+ */
157
+ export function extractModelProvider(
158
+ modelId: string,
159
+ ): 'anthropic' | 'openai' | 'google' | 'perplexity' | 'other' {
160
+ const lowercaseId = modelId.toLowerCase();
161
+
162
+ if (lowercaseId.includes('claude') || lowercaseId.includes('anthropic')) {
163
+ return 'anthropic';
164
+ }
165
+
166
+ if (lowercaseId.includes('gpt') || lowercaseId.includes('openai')) {
167
+ return 'openai';
168
+ }
169
+
170
+ if (lowercaseId.includes('gemini') || lowercaseId.includes('google')) {
171
+ return 'google';
172
+ }
173
+
174
+ if (lowercaseId.includes('pplx') || lowercaseId.includes('perplexity')) {
175
+ return 'perplexity';
176
+ }
177
+
178
+ return 'other';
179
+ }
180
+
181
+ /**
182
+ * Generate a unique operation ID
183
+ */
184
+ export function generateOperationId(prefix = 'ai'): string {
185
+ const timestamp = Date.now();
186
+ const random = Math.random().toString(36).slice(2, 9);
187
+ return `${prefix}_${timestamp}_${random}`;
188
+ }
189
+
190
+ /**
191
+ * Sanitize error message for tracking
192
+ */
193
+ export function sanitizeErrorMessage(error: Error | string): string {
194
+ const message = typeof error === 'string' ? error : error.message;
195
+
196
+ // Remove potentially sensitive information
197
+ // Matches: "API key sk-xxx", "api_key: xxx", "api-keys=xxx", etc.
198
+ return message
199
+ .replaceAll(/api[\s_\-]?keys?[\s:=]+[\w-]+/gi, 'api_key=***')
200
+ .replaceAll(/tokens?[\s:=]+[\w-]+/gi, 'token=***')
201
+ .replaceAll(/passwords?[\s:=]+[\w-]+/gi, 'password=***')
202
+ .replaceAll(/secrets?[\s:=]+[\w-]+/gi, 'secret=***')
203
+ .slice(0, 500); // Limit length
204
+ }
205
+
206
+ /**
207
+ * Calculate metrics for completion events
208
+ */
209
+ export function calculateCompletionMetrics(
210
+ startTime: number,
211
+ endTime: number,
212
+ tokenUsage?: TokenUsageProperties,
213
+ ): {
214
+ duration_ms: number;
215
+ tokens_per_second?: number;
216
+ } {
217
+ const duration_ms = endTime - startTime;
218
+ const metrics: { duration_ms: number; tokens_per_second?: number } = {
219
+ duration_ms,
220
+ };
221
+
222
+ if (tokenUsage?.total_tokens && duration_ms > 0) {
223
+ metrics.tokens_per_second = (tokenUsage.total_tokens / duration_ms) * 1000;
224
+ }
225
+
226
+ return metrics;
227
+ }
228
+
229
+ /**
230
+ * Detect intent from chat message
231
+ */
232
+ export function detectIntent(
233
+ message: string,
234
+ ):
235
+ | 'product-search'
236
+ | 'product-inquiry'
237
+ | 'product-comparison'
238
+ | 'cart-management'
239
+ | 'checkout-help'
240
+ | 'order-tracking'
241
+ | 'returns-exchanges'
242
+ | 'support-question'
243
+ | 'general-inquiry'
244
+ | 'complaint'
245
+ | 'feedback' {
246
+ const lowercase = message.toLowerCase();
247
+
248
+ // Order matters: more specific patterns (like order-tracking, complaint) should come
249
+ // before patterns with broad keywords (like product-comparison with 'or')
250
+ const intentPatterns = {
251
+ // Put order-tracking early since "order" contains "or" which could match comparison
252
+ 'order-tracking': [
253
+ 'track',
254
+ 'order status',
255
+ 'shipping',
256
+ 'delivery',
257
+ 'where is my',
258
+ 'when will',
259
+ 'my order',
260
+ ],
261
+ // Put complaint before comparison since "disappointed" could match patterns in other intents
262
+ complaint: ['disappointed', 'angry', 'frustrated', 'terrible', 'worst', 'complain', 'unhappy'],
263
+ 'returns-exchanges': ['return', 'refund', 'exchange', 'cancel order', 'send back'],
264
+ 'product-search': [
265
+ 'looking for',
266
+ 'search for',
267
+ 'find',
268
+ 'show me',
269
+ 'need',
270
+ 'want to buy',
271
+ 'shopping for',
272
+ ],
273
+ 'product-inquiry': [
274
+ 'tell me about',
275
+ 'details',
276
+ 'specs',
277
+ 'features',
278
+ 'price',
279
+ 'available',
280
+ 'in stock',
281
+ ],
282
+ 'product-comparison': [
283
+ 'compare',
284
+ 'difference between',
285
+ 'which is better',
286
+ 'vs',
287
+ ' or ', // Use space-bounded 'or' to avoid matching 'order', 'for', etc.
288
+ 'better than',
289
+ ],
290
+ 'cart-management': ['add to cart', 'remove from cart', 'cart', 'shopping bag', 'my items'],
291
+ 'checkout-help': ['checkout', 'payment', 'pay', 'purchase', 'buy now', 'complete order'],
292
+ feedback: ['review', 'feedback', 'rate', 'experience', 'satisfied'],
293
+ };
294
+
295
+ for (const [intent, patterns] of Object.entries(intentPatterns)) {
296
+ if (patterns.some(pattern => lowercase.includes(pattern))) {
297
+ return intent as any;
298
+ }
299
+ }
300
+
301
+ return 'general-inquiry';
302
+ }
303
+
304
+ /**
305
+ * Detect sentiment from message
306
+ */
307
+ export function detectSentiment(message: string): 'positive' | 'negative' | 'neutral' | 'mixed' {
308
+ const lowercase = message.toLowerCase();
309
+
310
+ const positiveWords = [
311
+ 'great',
312
+ 'love',
313
+ 'excellent',
314
+ 'perfect',
315
+ 'amazing',
316
+ 'wonderful',
317
+ 'awesome',
318
+ 'fantastic',
319
+ 'helpful',
320
+ 'thank',
321
+ ];
322
+ const negativeWords = [
323
+ 'bad',
324
+ 'terrible',
325
+ 'awful',
326
+ 'disappointed',
327
+ 'angry',
328
+ 'frustrated',
329
+ 'annoyed',
330
+ 'useless',
331
+ 'worst',
332
+ 'hate',
333
+ ];
334
+
335
+ const positiveCount = positiveWords.filter(word => lowercase.includes(word)).length;
336
+ const negativeCount = negativeWords.filter(word => lowercase.includes(word)).length;
337
+
338
+ if (positiveCount > 0 && negativeCount > 0) {
339
+ return 'mixed';
340
+ }
341
+
342
+ if (positiveCount > negativeCount) {
343
+ return 'positive';
344
+ }
345
+
346
+ if (negativeCount > positiveCount) {
347
+ return 'negative';
348
+ }
349
+
350
+ return 'neutral';
351
+ }
352
+
353
+ /**
354
+ * Truncate array to max length for tracking
355
+ */
356
+ export function truncateArray<T>(arr: T[], maxLength = 10): T[] {
357
+ return arr.slice(0, maxLength);
358
+ }
359
+
360
+ /**
361
+ * Safe JSON stringify for complex objects
362
+ */
363
+ export function safeStringify(obj?: unknown, maxLength = 1000): string {
364
+ try {
365
+ if (obj === undefined) return '{}';
366
+ const str = JSON.stringify(obj);
367
+ return str.length > maxLength ? `${str.slice(0, Math.max(0, maxLength))}...` : str;
368
+ } catch {
369
+ return '[Unable to stringify]';
370
+ }
371
+ }
372
+
373
+ /**
374
+ * Calculate conversation quality score
375
+ */
376
+ export function calculateQualityScore(metrics: {
377
+ message_count: number;
378
+ avg_response_time_ms?: number;
379
+ misunderstanding_count?: number;
380
+ clarification_count?: number;
381
+ satisfaction_score?: number;
382
+ }): number {
383
+ let score = 100;
384
+
385
+ // Penalize for too few messages (abandoned)
386
+ if (metrics.message_count < 3) {
387
+ score -= 20;
388
+ }
389
+
390
+ // Penalize for slow responses
391
+ if (metrics.avg_response_time_ms && metrics.avg_response_time_ms > 3000) {
392
+ score -= 10;
393
+ }
394
+
395
+ // Penalize for misunderstandings
396
+ if (metrics.misunderstanding_count) {
397
+ score -= metrics.misunderstanding_count * 5;
398
+ }
399
+
400
+ // Penalize for excessive clarifications
401
+ if (metrics.clarification_count && metrics.clarification_count > 2) {
402
+ score -= (metrics.clarification_count - 2) * 3;
403
+ }
404
+
405
+ // Boost for high satisfaction
406
+ if (metrics.satisfaction_score && metrics.satisfaction_score >= 4) {
407
+ score += 10;
408
+ }
409
+
410
+ return Math.max(0, Math.min(100, score));
411
+ }
412
+
413
+ /**
414
+ * Detect if message needs clarification
415
+ */
416
+ export function needsClarification(message: string): boolean {
417
+ const lowercase = message.toLowerCase();
418
+
419
+ const ambiguousPatterns = [
420
+ 'it',
421
+ 'that one',
422
+ 'this',
423
+ 'them',
424
+ 'those',
425
+ 'something like',
426
+ 'kind of',
427
+ 'sort of',
428
+ 'maybe',
429
+ 'not sure',
430
+ ];
431
+
432
+ return ambiguousPatterns.some(pattern => lowercase.includes(pattern));
433
+ }
434
+
435
+ /**
436
+ * Extract product mentions from message
437
+ */
438
+ export function extractProductMentions(message: string): string[] {
439
+ const mentions: string[] = [];
440
+
441
+ // Common product categories
442
+ const categories = [
443
+ 'laptop',
444
+ 'phone',
445
+ 'tablet',
446
+ 'watch',
447
+ 'headphones',
448
+ 'shoes',
449
+ 'shirt',
450
+ 'pants',
451
+ 'dress',
452
+ 'jacket',
453
+ ];
454
+
455
+ const lowercase = message.toLowerCase();
456
+ for (const category of categories) {
457
+ if (lowercase.includes(category)) {
458
+ mentions.push(category);
459
+ }
460
+ }
461
+
462
+ return mentions;
463
+ }
464
+
465
+ /**
466
+ * Alias for detectIntent for chatbot-specific contexts
467
+ */
468
+ export const detectChatbotIntent = detectIntent;
@@ -0,0 +1,106 @@
1
+ /**
2
+ * @fileoverview Cart and checkout-related ecommerce events
3
+ * Cart and checkout-related ecommerce events
4
+ */
5
+
6
+ import { trackEcommerce } from '../track-ecommerce';
7
+ import {
8
+ type CartAbandonmentProperties,
9
+ type CartProperties,
10
+ type CartUpdateProperties,
11
+ type CheckoutProgressProperties,
12
+ ECOMMERCE_EVENTS,
13
+ type EcommerceEventSpec,
14
+ } from '../types';
15
+ import {
16
+ cleanProperties,
17
+ normalizeProductProperties,
18
+ normalizeProducts,
19
+ validateRequiredProperties,
20
+ } from '../utils';
21
+
22
+ import type { EmitterOptions, EmitterTrackPayload } from '../../emitter-types';
23
+
24
+ /**
25
+ * Track cart updates (add, remove, update) with a single event
26
+ */
27
+ export function cartUpdated(
28
+ properties: CartUpdateProperties,
29
+ options?: EmitterOptions,
30
+ ): EmitterTrackPayload {
31
+ const { cart_id, action, cart_total, product, quantity_change } = properties;
32
+ const normalizedProduct = normalizeProductProperties(product);
33
+
34
+ validateRequiredProperties(properties, ['action']);
35
+ validateRequiredProperties(normalizedProduct, ['product_id']);
36
+
37
+ const eventSpec: EcommerceEventSpec = {
38
+ name: ECOMMERCE_EVENTS.CART_UPDATED,
39
+ category: 'ecommerce',
40
+ properties: cleanProperties({
41
+ action,
42
+ ...normalizedProduct,
43
+ cart_id,
44
+ cart_total,
45
+ quantity_change,
46
+ }),
47
+ requiredProperties: ['action', 'product_id'],
48
+ };
49
+
50
+ return trackEcommerce(eventSpec, options);
51
+ }
52
+
53
+ /**
54
+ * Track when a user views their shopping cart
55
+ */
56
+ export function cartViewed(properties: CartProperties): EcommerceEventSpec<CartProperties> {
57
+ const normalizedProps: CartProperties = {
58
+ cart_id: properties.cart_id,
59
+ products: properties.products ? normalizeProducts(properties.products) : undefined,
60
+ };
61
+
62
+ return {
63
+ name: ECOMMERCE_EVENTS.CART_VIEWED,
64
+ category: 'ecommerce',
65
+ properties: cleanProperties(normalizedProps),
66
+ requiredProperties: [],
67
+ };
68
+ }
69
+
70
+ /**
71
+ * Track when a cart is abandoned
72
+ */
73
+ export function cartAbandoned(properties: CartAbandonmentProperties): EcommerceEventSpec {
74
+ validateRequiredProperties(properties, ['cart_id', 'cart_value']);
75
+
76
+ const normalizedProps = {
77
+ ...properties,
78
+ products: normalizeProducts(properties.products),
79
+ };
80
+
81
+ return {
82
+ name: ECOMMERCE_EVENTS.CART_ABANDONED,
83
+ category: 'ecommerce',
84
+ properties: cleanProperties(normalizedProps),
85
+ requiredProperties: ['cart_id', 'cart_value'],
86
+ };
87
+ }
88
+
89
+ /**
90
+ * Track checkout progression with a single event
91
+ */
92
+ export function checkoutProgressed(properties: CheckoutProgressProperties): EcommerceEventSpec {
93
+ validateRequiredProperties(properties, ['step', 'step_name', 'action']);
94
+
95
+ const normalizedProps = {
96
+ ...properties,
97
+ products: properties.products ? normalizeProducts(properties.products) : undefined,
98
+ };
99
+
100
+ return {
101
+ name: ECOMMERCE_EVENTS.CHECKOUT_PROGRESSED,
102
+ category: 'ecommerce',
103
+ properties: cleanProperties(normalizedProps),
104
+ requiredProperties: ['step', 'step_name', 'action'],
105
+ };
106
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * @fileoverview Coupon-related ecommerce events
3
+ * Coupon-related ecommerce events
4
+ */
5
+
6
+ import { type CouponProperties, ECOMMERCE_EVENTS, type EcommerceEventSpec } from '../types';
7
+ import { cleanProperties } from '../utils';
8
+
9
+ /**
10
+ * Track when a coupon is successfully applied
11
+ */
12
+ export function couponApplied(properties: CouponProperties): EcommerceEventSpec<CouponProperties> {
13
+ const normalizedProps: CouponProperties = {
14
+ cart_id: properties.cart_id,
15
+ coupon_id: properties.coupon_id,
16
+ order_id: properties.order_id,
17
+ coupon_name: properties.coupon_name,
18
+ discount: properties.discount,
19
+ reason: properties.reason,
20
+ };
21
+
22
+ return {
23
+ name: ECOMMERCE_EVENTS.COUPON_APPLIED,
24
+ category: 'ecommerce',
25
+ properties: cleanProperties(normalizedProps),
26
+ requiredProperties: [],
27
+ };
28
+ }
29
+
30
+ /**
31
+ * Track when a coupon is removed
32
+ */
33
+ export function couponRemoved(properties: CouponProperties): EcommerceEventSpec<CouponProperties> {
34
+ const normalizedProps: CouponProperties = {
35
+ cart_id: properties.cart_id,
36
+ coupon_id: properties.coupon_id,
37
+ order_id: properties.order_id,
38
+ coupon_name: properties.coupon_name,
39
+ discount: properties.discount,
40
+ reason: properties.reason,
41
+ };
42
+
43
+ return {
44
+ name: ECOMMERCE_EVENTS.COUPON_REMOVED,
45
+ category: 'ecommerce',
46
+ properties: cleanProperties(normalizedProps),
47
+ requiredProperties: [],
48
+ };
49
+ }
@@ -0,0 +1,61 @@
1
+ /**
2
+ * @fileoverview Customer engagement events for ecommerce
3
+ * Customer engagement events for ecommerce
4
+ * Track user interactions that indicate interest but aren't direct purchases
5
+ */
6
+
7
+ import { type BaseProductProperties, ECOMMERCE_EVENTS, type EcommerceEventSpec } from '../types';
8
+ import { cleanProperties, normalizeProductProperties, validateRequiredProperties } from '../utils';
9
+
10
+ /**
11
+ * Track when a user sets a price alert for a product
12
+ */
13
+ export function priceAlertSet(
14
+ properties: BaseProductProperties & {
15
+ threshold_price: number;
16
+ notification_method?: 'email' | 'sms' | 'push';
17
+ currency?: string;
18
+ },
19
+ ): EcommerceEventSpec {
20
+ const { currency, notification_method, threshold_price, ...productProps } = properties;
21
+ const normalizedProduct = normalizeProductProperties(productProps);
22
+ validateRequiredProperties(normalizedProduct, ['product_id']);
23
+ validateRequiredProperties({ threshold_price }, ['threshold_price']);
24
+
25
+ return {
26
+ name: ECOMMERCE_EVENTS.PRICE_ALERT_SET,
27
+ category: 'ecommerce',
28
+ properties: cleanProperties({
29
+ ...normalizedProduct,
30
+ action_type: 'price_alert',
31
+ currency,
32
+ notification_method,
33
+ threshold_price,
34
+ }),
35
+ requiredProperties: ['product_id', 'threshold_price'],
36
+ };
37
+ }
38
+
39
+ /**
40
+ * Track when a user requests notification for out-of-stock items
41
+ */
42
+ export function backInStockRequested(
43
+ properties: BaseProductProperties & {
44
+ notification_method?: 'email' | 'sms' | 'push';
45
+ },
46
+ ): EcommerceEventSpec {
47
+ const { notification_method, ...productProps } = properties;
48
+ const normalizedProduct = normalizeProductProperties(productProps);
49
+ validateRequiredProperties(normalizedProduct, ['product_id']);
50
+
51
+ return {
52
+ name: ECOMMERCE_EVENTS.BACK_IN_STOCK_REQUESTED,
53
+ category: 'ecommerce',
54
+ properties: cleanProperties({
55
+ ...normalizedProduct,
56
+ action_type: 'back_in_stock',
57
+ notification_method,
58
+ }),
59
+ requiredProperties: ['product_id'],
60
+ };
61
+ }