@tuteliq/sdk 2.1.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.
Files changed (105) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1034 -0
  3. package/dist/cjs/client.js +1283 -0
  4. package/dist/cjs/client.js.map +1 -0
  5. package/dist/cjs/constants.js +179 -0
  6. package/dist/cjs/constants.js.map +1 -0
  7. package/dist/cjs/errors.js +129 -0
  8. package/dist/cjs/errors.js.map +1 -0
  9. package/dist/cjs/index.js +37 -0
  10. package/dist/cjs/index.js.map +1 -0
  11. package/dist/cjs/types/account.js +6 -0
  12. package/dist/cjs/types/account.js.map +1 -0
  13. package/dist/cjs/types/analysis.js +6 -0
  14. package/dist/cjs/types/analysis.js.map +1 -0
  15. package/dist/cjs/types/guidance.js +3 -0
  16. package/dist/cjs/types/guidance.js.map +1 -0
  17. package/dist/cjs/types/index.js +28 -0
  18. package/dist/cjs/types/index.js.map +1 -0
  19. package/dist/cjs/types/media.js +6 -0
  20. package/dist/cjs/types/media.js.map +1 -0
  21. package/dist/cjs/types/policy.js +6 -0
  22. package/dist/cjs/types/policy.js.map +1 -0
  23. package/dist/cjs/types/pricing.js +6 -0
  24. package/dist/cjs/types/pricing.js.map +1 -0
  25. package/dist/cjs/types/reports.js +3 -0
  26. package/dist/cjs/types/reports.js.map +1 -0
  27. package/dist/cjs/types/safety.js +7 -0
  28. package/dist/cjs/types/safety.js.map +1 -0
  29. package/dist/cjs/types/voice-stream.js +6 -0
  30. package/dist/cjs/types/voice-stream.js.map +1 -0
  31. package/dist/cjs/types/webhooks.js +6 -0
  32. package/dist/cjs/types/webhooks.js.map +1 -0
  33. package/dist/cjs/utils/retry.js +64 -0
  34. package/dist/cjs/utils/retry.js.map +1 -0
  35. package/dist/cjs/voice-stream.js +184 -0
  36. package/dist/cjs/voice-stream.js.map +1 -0
  37. package/dist/client.d.ts +643 -0
  38. package/dist/client.d.ts.map +1 -0
  39. package/dist/client.js +1280 -0
  40. package/dist/client.js.map +1 -0
  41. package/dist/constants.d.ts +141 -0
  42. package/dist/constants.d.ts.map +1 -0
  43. package/dist/constants.js +176 -0
  44. package/dist/constants.js.map +1 -0
  45. package/dist/errors.d.ts +81 -0
  46. package/dist/errors.d.ts.map +1 -0
  47. package/dist/errors.js +116 -0
  48. package/dist/errors.js.map +1 -0
  49. package/dist/index.d.ts +6 -0
  50. package/dist/index.d.ts.map +1 -0
  51. package/dist/index.js +9 -0
  52. package/dist/index.js.map +1 -0
  53. package/dist/types/account.d.ts +161 -0
  54. package/dist/types/account.d.ts.map +1 -0
  55. package/dist/types/account.js +5 -0
  56. package/dist/types/account.js.map +1 -0
  57. package/dist/types/analysis.d.ts +41 -0
  58. package/dist/types/analysis.d.ts.map +1 -0
  59. package/dist/types/analysis.js +4 -0
  60. package/dist/types/analysis.js.map +1 -0
  61. package/dist/types/guidance.d.ts +35 -0
  62. package/dist/types/guidance.d.ts.map +1 -0
  63. package/dist/types/guidance.js +2 -0
  64. package/dist/types/guidance.js.map +1 -0
  65. package/dist/types/index.d.ts +231 -0
  66. package/dist/types/index.d.ts.map +1 -0
  67. package/dist/types/index.js +12 -0
  68. package/dist/types/index.js.map +1 -0
  69. package/dist/types/media.d.ts +121 -0
  70. package/dist/types/media.d.ts.map +1 -0
  71. package/dist/types/media.js +4 -0
  72. package/dist/types/media.js.map +1 -0
  73. package/dist/types/policy.d.ts +54 -0
  74. package/dist/types/policy.d.ts.map +1 -0
  75. package/dist/types/policy.js +5 -0
  76. package/dist/types/policy.js.map +1 -0
  77. package/dist/types/pricing.d.ts +53 -0
  78. package/dist/types/pricing.d.ts.map +1 -0
  79. package/dist/types/pricing.js +5 -0
  80. package/dist/types/pricing.js.map +1 -0
  81. package/dist/types/reports.d.ts +44 -0
  82. package/dist/types/reports.d.ts.map +1 -0
  83. package/dist/types/reports.js +2 -0
  84. package/dist/types/reports.js.map +1 -0
  85. package/dist/types/safety.d.ts +151 -0
  86. package/dist/types/safety.d.ts.map +1 -0
  87. package/dist/types/safety.js +4 -0
  88. package/dist/types/safety.js.map +1 -0
  89. package/dist/types/voice-stream.d.ts +97 -0
  90. package/dist/types/voice-stream.d.ts.map +1 -0
  91. package/dist/types/voice-stream.js +5 -0
  92. package/dist/types/voice-stream.js.map +1 -0
  93. package/dist/types/webhooks.d.ts +110 -0
  94. package/dist/types/webhooks.d.ts.map +1 -0
  95. package/dist/types/webhooks.js +4 -0
  96. package/dist/types/webhooks.js.map +1 -0
  97. package/dist/utils/retry.d.ts +17 -0
  98. package/dist/utils/retry.d.ts.map +1 -0
  99. package/dist/utils/retry.js +61 -0
  100. package/dist/utils/retry.js.map +1 -0
  101. package/dist/voice-stream.d.ts +16 -0
  102. package/dist/voice-stream.d.ts.map +1 -0
  103. package/dist/voice-stream.js +148 -0
  104. package/dist/voice-stream.js.map +1 -0
  105. package/package.json +81 -0
package/dist/client.js ADDED
@@ -0,0 +1,1280 @@
1
+ import { TuteliqError, AuthenticationError, RateLimitError, ValidationError, NotFoundError, ServerError, TimeoutError, NetworkError, QuotaExceededError, TierAccessError, } from './errors.js';
2
+ import { withRetry } from './utils/retry.js';
3
+ import { createVoiceStream } from './voice-stream.js';
4
+ /** Tuteliq API endpoint - locked to official server */
5
+ const API_BASE_URL = 'https://api.tuteliq.ai';
6
+ const DEFAULT_TIMEOUT = 30000;
7
+ const DEFAULT_RETRIES = 3;
8
+ const DEFAULT_RETRY_DELAY = 1000;
9
+ // Input limits to prevent abuse
10
+ const MAX_CONTENT_LENGTH = 50000; // 50KB max content
11
+ const MAX_MESSAGES_COUNT = 100; // Max messages per request
12
+ /**
13
+ * Tuteliq - AI-powered child safety analysis
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * import { Tuteliq } from '@tuteliq/sdk'
18
+ *
19
+ * const tuteliq = new Tuteliq(process.env.TUTELIQ_API_KEY)
20
+ *
21
+ * // Detect bullying
22
+ * const result = await tuteliq.detectBullying({
23
+ * content: "You're not welcome here",
24
+ * context: 'chat'
25
+ * })
26
+ *
27
+ * if (result.is_bullying) {
28
+ * console.log('Severity:', result.severity)
29
+ * console.log('Rationale:', result.rationale)
30
+ * }
31
+ *
32
+ * // Check usage
33
+ * console.log(tuteliq.usage) // { limit: 10000, used: 5234, remaining: 4766 }
34
+ * ```
35
+ */
36
+ export class Tuteliq {
37
+ apiKey;
38
+ timeout;
39
+ retries;
40
+ retryDelay;
41
+ _usage = null;
42
+ _rateLimit = null;
43
+ _lastRequestId = null;
44
+ _lastLatencyMs = null;
45
+ _usageWarning = null;
46
+ /**
47
+ * Create a new Tuteliq client
48
+ *
49
+ * @param apiKey - Your Tuteliq API key
50
+ * @param options - Optional configuration
51
+ *
52
+ * @example
53
+ * ```typescript
54
+ * // Simple usage
55
+ * const tuteliq = new Tuteliq('your-api-key')
56
+ *
57
+ * // With options
58
+ * const tuteliq = new Tuteliq('your-api-key', {
59
+ * timeout: 10000
60
+ * })
61
+ * ```
62
+ */
63
+ constructor(apiKey, options = {}) {
64
+ if (!apiKey || typeof apiKey !== 'string') {
65
+ throw new Error('API key is required and must be a string');
66
+ }
67
+ if (apiKey.length < 10) {
68
+ throw new Error('API key appears to be invalid (too short)');
69
+ }
70
+ this.apiKey = apiKey;
71
+ this.timeout = options.timeout ?? DEFAULT_TIMEOUT;
72
+ this.retries = options.retries ?? DEFAULT_RETRIES;
73
+ this.retryDelay = options.retryDelay ?? DEFAULT_RETRY_DELAY;
74
+ // Validate configuration
75
+ if (this.timeout < 1000 || this.timeout > 120000) {
76
+ throw new Error('Timeout must be between 1000ms and 120000ms');
77
+ }
78
+ if (this.retries < 0 || this.retries > 10) {
79
+ throw new Error('Retries must be between 0 and 10');
80
+ }
81
+ }
82
+ /**
83
+ * Get current monthly usage stats from the last request
84
+ */
85
+ get usage() {
86
+ return this._usage;
87
+ }
88
+ /**
89
+ * Get rate limit info from the last request (per-minute limits)
90
+ */
91
+ get rateLimit() {
92
+ return this._rateLimit;
93
+ }
94
+ /**
95
+ * Get usage warning message if usage is above 80%
96
+ */
97
+ get usageWarning() {
98
+ return this._usageWarning;
99
+ }
100
+ /**
101
+ * Get the request ID from the last request
102
+ */
103
+ get lastRequestId() {
104
+ return this._lastRequestId;
105
+ }
106
+ /**
107
+ * Get the latency from the last request in milliseconds
108
+ */
109
+ get lastLatencyMs() {
110
+ return this._lastLatencyMs;
111
+ }
112
+ /**
113
+ * Validate content length to prevent abuse
114
+ */
115
+ validateContent(content) {
116
+ if (!content || typeof content !== 'string') {
117
+ throw new ValidationError('Content is required and must be a string');
118
+ }
119
+ if (content.length > MAX_CONTENT_LENGTH) {
120
+ throw new ValidationError(`Content exceeds maximum length of ${MAX_CONTENT_LENGTH} characters`);
121
+ }
122
+ }
123
+ /**
124
+ * Validate messages array
125
+ */
126
+ validateMessages(messages) {
127
+ if (!Array.isArray(messages) || messages.length === 0) {
128
+ throw new ValidationError('Messages array is required and cannot be empty');
129
+ }
130
+ if (messages.length > MAX_MESSAGES_COUNT) {
131
+ throw new ValidationError(`Messages array exceeds maximum count of ${MAX_MESSAGES_COUNT}`);
132
+ }
133
+ }
134
+ static SDK_IDENTIFIER = 'Node SDK';
135
+ /**
136
+ * Resolves the platform string by appending the SDK identifier.
137
+ * - "MyApp" → "MyApp - Node SDK"
138
+ * - undefined → "Node SDK"
139
+ */
140
+ static resolvePlatform(platform) {
141
+ if (platform && platform.length > 0) {
142
+ return `${platform} - ${Tuteliq.SDK_IDENTIFIER}`;
143
+ }
144
+ return Tuteliq.SDK_IDENTIFIER;
145
+ }
146
+ /**
147
+ * Normalize context input to API format
148
+ */
149
+ normalizeContext(context) {
150
+ if (!context)
151
+ return { platform: Tuteliq.resolvePlatform() };
152
+ if (typeof context === 'string') {
153
+ return { platform: Tuteliq.resolvePlatform(context) };
154
+ }
155
+ return { ...context, platform: Tuteliq.resolvePlatform(context.platform) };
156
+ }
157
+ /**
158
+ * Make an authenticated request to the API
159
+ */
160
+ async request(method, path, body) {
161
+ const url = `${API_BASE_URL}${path}`;
162
+ const startTime = Date.now();
163
+ const controller = new AbortController();
164
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
165
+ try {
166
+ const response = await fetch(url, {
167
+ method,
168
+ headers: {
169
+ 'Content-Type': 'application/json',
170
+ 'Authorization': `Bearer ${this.apiKey}`,
171
+ },
172
+ body: body ? JSON.stringify(body) : undefined,
173
+ signal: controller.signal,
174
+ });
175
+ clearTimeout(timeoutId);
176
+ this._lastLatencyMs = Date.now() - startTime;
177
+ // Extract metadata from headers
178
+ this._lastRequestId = response.headers.get('x-request-id');
179
+ // Monthly usage headers (X-Monthly-*)
180
+ const monthlyLimit = response.headers.get('x-monthly-limit');
181
+ const monthlyUsed = response.headers.get('x-monthly-used');
182
+ const monthlyRemaining = response.headers.get('x-monthly-remaining');
183
+ if (monthlyLimit && monthlyUsed && monthlyRemaining) {
184
+ this._usage = {
185
+ limit: parseInt(monthlyLimit, 10),
186
+ used: parseInt(monthlyUsed, 10),
187
+ remaining: parseInt(monthlyRemaining, 10),
188
+ };
189
+ }
190
+ // Rate limit headers (X-RateLimit-*)
191
+ const rateLimitLimit = response.headers.get('x-ratelimit-limit');
192
+ const rateLimitRemaining = response.headers.get('x-ratelimit-remaining');
193
+ const rateLimitReset = response.headers.get('x-ratelimit-reset');
194
+ if (rateLimitLimit && rateLimitRemaining) {
195
+ this._rateLimit = {
196
+ limit: parseInt(rateLimitLimit, 10),
197
+ remaining: parseInt(rateLimitRemaining, 10),
198
+ reset: rateLimitReset ? parseInt(rateLimitReset, 10) : undefined,
199
+ };
200
+ }
201
+ // Usage warning header
202
+ this._usageWarning = response.headers.get('x-usage-warning');
203
+ // Handle error responses
204
+ if (!response.ok) {
205
+ const errorBody = await response.json().catch(() => ({}));
206
+ this.handleErrorResponse(response.status, errorBody, response.headers);
207
+ }
208
+ return await response.json();
209
+ }
210
+ catch (error) {
211
+ clearTimeout(timeoutId);
212
+ this._lastLatencyMs = Date.now() - startTime;
213
+ if (error instanceof TuteliqError) {
214
+ throw error;
215
+ }
216
+ if (error instanceof Error) {
217
+ if (error.name === 'AbortError') {
218
+ throw new TimeoutError(`Request timed out after ${this.timeout}ms`);
219
+ }
220
+ // Detect network errors across runtimes (Node, browsers, edge)
221
+ if (error instanceof TypeError ||
222
+ error.name === 'TypeError' ||
223
+ error.message.includes('fetch') ||
224
+ error.message.includes('network') ||
225
+ error.message.includes('ECONNREFUSED') ||
226
+ error.message.includes('ECONNRESET') ||
227
+ error.message.includes('ENOTFOUND') ||
228
+ error.message.includes('ERR_NETWORK') ||
229
+ error.message.includes('Failed to fetch') ||
230
+ error.message.includes('Network request failed')) {
231
+ throw new NetworkError(error.message);
232
+ }
233
+ }
234
+ throw new TuteliqError(error instanceof Error ? error.message : 'Unknown error occurred');
235
+ }
236
+ }
237
+ /**
238
+ * Handle error responses from the API
239
+ */
240
+ handleErrorResponse(status, body, headers) {
241
+ const message = body.error?.message || 'Unknown error';
242
+ const code = body.error?.code;
243
+ const details = body.error?.details;
244
+ const suggestion = body.error?.suggestion;
245
+ const links = body.error?.links;
246
+ switch (status) {
247
+ case 400:
248
+ throw new ValidationError(message, details, { code, suggestion, links });
249
+ case 401:
250
+ throw new AuthenticationError(message, { code, suggestion, links });
251
+ case 403:
252
+ throw new TierAccessError(message, { code, suggestion, links });
253
+ case 404:
254
+ throw new NotFoundError(message, { code, suggestion, links });
255
+ case 429: {
256
+ const retryAfter = headers.get('retry-after');
257
+ if (code === 'RATE_2003' || code === 'QUOTA_EXCEEDED') {
258
+ throw new QuotaExceededError(message, { code, suggestion, links });
259
+ }
260
+ throw new RateLimitError(message, retryAfter ? parseInt(retryAfter, 10) : undefined, { code, suggestion, links });
261
+ }
262
+ default:
263
+ if (status >= 500) {
264
+ throw new ServerError(message, status, { code, suggestion, links });
265
+ }
266
+ throw new TuteliqError(message, status, details, { code, suggestion, links });
267
+ }
268
+ }
269
+ /**
270
+ * Make a request with retry logic
271
+ */
272
+ async requestWithRetry(method, path, body) {
273
+ return withRetry(() => this.request(method, path, body), {
274
+ maxRetries: this.retries,
275
+ initialDelay: this.retryDelay,
276
+ });
277
+ }
278
+ /**
279
+ * Make an authenticated multipart request to the API
280
+ */
281
+ async multipartRequest(path, formData) {
282
+ const url = `${API_BASE_URL}${path}`;
283
+ const startTime = Date.now();
284
+ const controller = new AbortController();
285
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
286
+ try {
287
+ const response = await fetch(url, {
288
+ method: 'POST',
289
+ headers: {
290
+ 'Authorization': `Bearer ${this.apiKey}`,
291
+ },
292
+ body: formData,
293
+ signal: controller.signal,
294
+ });
295
+ clearTimeout(timeoutId);
296
+ this._lastLatencyMs = Date.now() - startTime;
297
+ // Extract metadata from headers
298
+ this._lastRequestId = response.headers.get('x-request-id');
299
+ const monthlyLimit = response.headers.get('x-monthly-limit');
300
+ const monthlyUsed = response.headers.get('x-monthly-used');
301
+ const monthlyRemaining = response.headers.get('x-monthly-remaining');
302
+ if (monthlyLimit && monthlyUsed && monthlyRemaining) {
303
+ this._usage = {
304
+ limit: parseInt(monthlyLimit, 10),
305
+ used: parseInt(monthlyUsed, 10),
306
+ remaining: parseInt(monthlyRemaining, 10),
307
+ };
308
+ }
309
+ const rateLimitLimit = response.headers.get('x-ratelimit-limit');
310
+ const rateLimitRemaining = response.headers.get('x-ratelimit-remaining');
311
+ const rateLimitReset = response.headers.get('x-ratelimit-reset');
312
+ if (rateLimitLimit && rateLimitRemaining) {
313
+ this._rateLimit = {
314
+ limit: parseInt(rateLimitLimit, 10),
315
+ remaining: parseInt(rateLimitRemaining, 10),
316
+ reset: rateLimitReset ? parseInt(rateLimitReset, 10) : undefined,
317
+ };
318
+ }
319
+ this._usageWarning = response.headers.get('x-usage-warning');
320
+ if (!response.ok) {
321
+ const errorBody = await response.json().catch(() => ({}));
322
+ this.handleErrorResponse(response.status, errorBody, response.headers);
323
+ }
324
+ return await response.json();
325
+ }
326
+ catch (error) {
327
+ clearTimeout(timeoutId);
328
+ this._lastLatencyMs = Date.now() - startTime;
329
+ if (error instanceof TuteliqError) {
330
+ throw error;
331
+ }
332
+ if (error instanceof Error) {
333
+ if (error.name === 'AbortError') {
334
+ throw new TimeoutError(`Request timed out after ${this.timeout}ms`);
335
+ }
336
+ if (error instanceof TypeError ||
337
+ error.name === 'TypeError' ||
338
+ error.message.includes('fetch') ||
339
+ error.message.includes('network') ||
340
+ error.message.includes('ECONNREFUSED') ||
341
+ error.message.includes('ECONNRESET') ||
342
+ error.message.includes('ENOTFOUND') ||
343
+ error.message.includes('ERR_NETWORK') ||
344
+ error.message.includes('Failed to fetch') ||
345
+ error.message.includes('Network request failed')) {
346
+ throw new NetworkError(error.message);
347
+ }
348
+ }
349
+ throw new TuteliqError(error instanceof Error ? error.message : 'Unknown error occurred');
350
+ }
351
+ }
352
+ // =========================================================================
353
+ // Safety Detection Methods
354
+ // =========================================================================
355
+ /**
356
+ * Detect bullying in content
357
+ *
358
+ * @example
359
+ * ```typescript
360
+ * const result = await tuteliq.detectBullying({
361
+ * content: "Nobody likes you, loser",
362
+ * context: 'chat'
363
+ * })
364
+ *
365
+ * if (result.is_bullying && result.severity === 'high') {
366
+ * console.log('High severity bullying detected')
367
+ * console.log('Rationale:', result.rationale)
368
+ * }
369
+ * ```
370
+ */
371
+ async detectBullying(input) {
372
+ this.validateContent(input.content);
373
+ return this.requestWithRetry('POST', '/api/v1/safety/bullying', {
374
+ text: input.content,
375
+ context: this.normalizeContext(input.context),
376
+ ...(input.external_id && { external_id: input.external_id }),
377
+ ...(input.customer_id && { customer_id: input.customer_id }),
378
+ ...(input.metadata && { metadata: input.metadata }),
379
+ });
380
+ }
381
+ /**
382
+ * Detect grooming patterns in a conversation
383
+ *
384
+ * @example
385
+ * ```typescript
386
+ * const result = await tuteliq.detectGrooming({
387
+ * messages: [
388
+ * { role: 'adult', content: "Don't tell your parents" },
389
+ * { role: 'child', content: "Ok" }
390
+ * ],
391
+ * childAge: 12
392
+ * })
393
+ *
394
+ * if (result.grooming_risk === 'high') {
395
+ * console.log('Flags:', result.flags)
396
+ * }
397
+ * ```
398
+ */
399
+ async detectGrooming(input) {
400
+ this.validateMessages(input.messages);
401
+ return this.requestWithRetry('POST', '/api/v1/safety/grooming', {
402
+ messages: input.messages.map(m => ({
403
+ sender_role: m.role,
404
+ text: m.content,
405
+ })),
406
+ context: {
407
+ child_age: input.childAge,
408
+ ...this.normalizeContext(input.context),
409
+ },
410
+ ...(input.external_id && { external_id: input.external_id }),
411
+ ...(input.customer_id && { customer_id: input.customer_id }),
412
+ ...(input.metadata && { metadata: input.metadata }),
413
+ });
414
+ }
415
+ /**
416
+ * Detect unsafe content (self-harm, violence, hate speech, etc.)
417
+ *
418
+ * @example
419
+ * ```typescript
420
+ * const result = await tuteliq.detectUnsafe({
421
+ * content: "I want to hurt myself"
422
+ * })
423
+ *
424
+ * if (result.unsafe && result.categories.includes('self_harm')) {
425
+ * console.log('Show crisis resources')
426
+ * }
427
+ * ```
428
+ */
429
+ async detectUnsafe(input) {
430
+ this.validateContent(input.content);
431
+ return this.requestWithRetry('POST', '/api/v1/safety/unsafe', {
432
+ text: input.content,
433
+ context: this.normalizeContext(input.context),
434
+ ...(input.external_id && { external_id: input.external_id }),
435
+ ...(input.customer_id && { customer_id: input.customer_id }),
436
+ ...(input.metadata && { metadata: input.metadata }),
437
+ });
438
+ }
439
+ async analyze(contentOrInput, context) {
440
+ const input = typeof contentOrInput === 'string'
441
+ ? { content: contentOrInput, context }
442
+ : contentOrInput;
443
+ const include = input.include || ['bullying', 'unsafe'];
444
+ // Run detections in parallel
445
+ const promises = [];
446
+ const types = [];
447
+ if (include.includes('bullying')) {
448
+ types.push('bullying');
449
+ promises.push(this.detectBullying({
450
+ content: input.content,
451
+ context: input.context,
452
+ external_id: input.external_id,
453
+ customer_id: input.customer_id,
454
+ metadata: input.metadata,
455
+ }));
456
+ }
457
+ if (include.includes('unsafe')) {
458
+ types.push('unsafe');
459
+ promises.push(this.detectUnsafe({
460
+ content: input.content,
461
+ context: input.context,
462
+ external_id: input.external_id,
463
+ customer_id: input.customer_id,
464
+ metadata: input.metadata,
465
+ }));
466
+ }
467
+ const results = await Promise.all(promises);
468
+ // Combine results
469
+ let bullyingResult;
470
+ let unsafeResult;
471
+ let maxRiskScore = 0;
472
+ results.forEach((result, i) => {
473
+ if (types[i] === 'bullying') {
474
+ bullyingResult = result;
475
+ maxRiskScore = Math.max(maxRiskScore, bullyingResult.risk_score);
476
+ }
477
+ else if (types[i] === 'unsafe') {
478
+ unsafeResult = result;
479
+ maxRiskScore = Math.max(maxRiskScore, unsafeResult.risk_score);
480
+ }
481
+ });
482
+ // Determine risk level
483
+ let risk_level = 'safe';
484
+ if (maxRiskScore >= 0.9)
485
+ risk_level = 'critical';
486
+ else if (maxRiskScore >= 0.7)
487
+ risk_level = 'high';
488
+ else if (maxRiskScore >= 0.5)
489
+ risk_level = 'medium';
490
+ else if (maxRiskScore >= 0.3)
491
+ risk_level = 'low';
492
+ // Build summary
493
+ const findings = [];
494
+ if (bullyingResult?.is_bullying) {
495
+ findings.push(`Bullying detected (${bullyingResult.severity})`);
496
+ }
497
+ if (unsafeResult?.unsafe) {
498
+ findings.push(`Unsafe content: ${unsafeResult.categories.join(', ')}`);
499
+ }
500
+ const summary = findings.length > 0
501
+ ? findings.join('. ')
502
+ : 'No safety concerns detected.';
503
+ // Determine recommended action
504
+ let recommended_action = 'none';
505
+ if (bullyingResult?.recommended_action === 'immediate_intervention' ||
506
+ unsafeResult?.recommended_action === 'immediate_intervention') {
507
+ recommended_action = 'immediate_intervention';
508
+ }
509
+ else if (bullyingResult?.recommended_action === 'flag_for_moderator' ||
510
+ unsafeResult?.recommended_action === 'flag_for_moderator') {
511
+ recommended_action = 'flag_for_moderator';
512
+ }
513
+ else if (bullyingResult?.recommended_action === 'monitor' ||
514
+ unsafeResult?.recommended_action === 'monitor') {
515
+ recommended_action = 'monitor';
516
+ }
517
+ return {
518
+ risk_level,
519
+ risk_score: maxRiskScore,
520
+ summary,
521
+ bullying: bullyingResult,
522
+ unsafe: unsafeResult,
523
+ recommended_action,
524
+ ...(input.external_id && { external_id: input.external_id }),
525
+ ...(input.customer_id && { customer_id: input.customer_id }),
526
+ ...(input.metadata && { metadata: input.metadata }),
527
+ };
528
+ }
529
+ // =========================================================================
530
+ // Analysis Methods
531
+ // =========================================================================
532
+ /**
533
+ * Analyze emotions in content or conversation
534
+ *
535
+ * @example
536
+ * ```typescript
537
+ * const result = await tuteliq.analyzeEmotions({
538
+ * content: "I'm so stressed about everything"
539
+ * })
540
+ *
541
+ * console.log('Emotions:', result.dominant_emotions)
542
+ * console.log('Trend:', result.trend)
543
+ * ```
544
+ */
545
+ async analyzeEmotions(input) {
546
+ if (input.content) {
547
+ this.validateContent(input.content);
548
+ }
549
+ else if (input.messages) {
550
+ this.validateMessages(input.messages);
551
+ }
552
+ else {
553
+ throw new ValidationError('Either content or messages is required');
554
+ }
555
+ const body = {};
556
+ if (input.content) {
557
+ body.messages = [{ sender: 'user', text: input.content }];
558
+ }
559
+ else if (input.messages) {
560
+ body.messages = input.messages.map(m => ({
561
+ sender: m.sender,
562
+ text: m.content,
563
+ }));
564
+ }
565
+ if (input.context) {
566
+ body.context = this.normalizeContext(input.context);
567
+ }
568
+ if (input.external_id) {
569
+ body.external_id = input.external_id;
570
+ }
571
+ if (input.customer_id) {
572
+ body.customer_id = input.customer_id;
573
+ }
574
+ if (input.metadata) {
575
+ body.metadata = input.metadata;
576
+ }
577
+ return this.requestWithRetry('POST', '/api/v1/analysis/emotions', body);
578
+ }
579
+ // =========================================================================
580
+ // Guidance Methods
581
+ // =========================================================================
582
+ /**
583
+ * Get age-appropriate action guidance for a situation
584
+ *
585
+ * @example
586
+ * ```typescript
587
+ * const plan = await tuteliq.getActionPlan({
588
+ * situation: 'Someone is spreading rumors about me',
589
+ * childAge: 12,
590
+ * audience: 'child'
591
+ * })
592
+ *
593
+ * console.log('Steps:', plan.steps)
594
+ * ```
595
+ */
596
+ async getActionPlan(input) {
597
+ if (!input.situation || typeof input.situation !== 'string') {
598
+ throw new ValidationError('Situation description is required');
599
+ }
600
+ this.validateContent(input.situation);
601
+ return this.requestWithRetry('POST', '/api/v1/guidance/action-plan', {
602
+ role: input.audience || 'parent',
603
+ situation: input.situation,
604
+ child_age: input.childAge,
605
+ severity: input.severity,
606
+ ...(input.external_id && { external_id: input.external_id }),
607
+ ...(input.customer_id && { customer_id: input.customer_id }),
608
+ ...(input.metadata && { metadata: input.metadata }),
609
+ });
610
+ }
611
+ // =========================================================================
612
+ // Report Methods
613
+ // =========================================================================
614
+ /**
615
+ * Generate an incident report from messages
616
+ *
617
+ * @example
618
+ * ```typescript
619
+ * const report = await tuteliq.generateReport({
620
+ * messages: [
621
+ * { sender: 'user1', content: 'Harmful message' },
622
+ * { sender: 'child', content: 'Response' }
623
+ * ],
624
+ * childAge: 14
625
+ * })
626
+ *
627
+ * console.log('Summary:', report.summary)
628
+ * console.log('Risk:', report.risk_level)
629
+ * console.log('Next steps:', report.recommended_next_steps)
630
+ * ```
631
+ */
632
+ async generateReport(input) {
633
+ this.validateMessages(input.messages);
634
+ return this.requestWithRetry('POST', '/api/v1/reports/incident', {
635
+ messages: input.messages.map(m => ({
636
+ sender: m.sender,
637
+ text: m.content,
638
+ })),
639
+ meta: {
640
+ child_age: input.childAge,
641
+ ...input.incident,
642
+ },
643
+ ...(input.external_id && { external_id: input.external_id }),
644
+ ...(input.customer_id && { customer_id: input.customer_id }),
645
+ ...(input.metadata && { metadata: input.metadata }),
646
+ });
647
+ }
648
+ // =========================================================================
649
+ // Policy Methods
650
+ // =========================================================================
651
+ /**
652
+ * Get the current policy configuration
653
+ *
654
+ * @example
655
+ * ```typescript
656
+ * const policy = await tuteliq.getPolicy()
657
+ * console.log('Bullying enabled:', policy.config?.bullying.enabled)
658
+ * ```
659
+ */
660
+ async getPolicy() {
661
+ return this.requestWithRetry('GET', '/api/v1/policy');
662
+ }
663
+ /**
664
+ * Update the policy configuration
665
+ *
666
+ * @example
667
+ * ```typescript
668
+ * await tuteliq.setPolicy({
669
+ * bullying: {
670
+ * enabled: true,
671
+ * minRiskScoreToFlag: 0.5
672
+ * }
673
+ * })
674
+ * ```
675
+ */
676
+ async setPolicy(config) {
677
+ return this.requestWithRetry('PUT', '/api/v1/policy', { config });
678
+ }
679
+ // =========================================================================
680
+ // Batch Methods
681
+ // =========================================================================
682
+ /**
683
+ * Analyze multiple items in a single batch request
684
+ *
685
+ * @example
686
+ * ```typescript
687
+ * const result = await tuteliq.batch({
688
+ * items: [
689
+ * { type: 'bullying', content: 'Message 1' },
690
+ * { type: 'unsafe', content: 'Message 2' },
691
+ * ],
692
+ * parallel: true
693
+ * })
694
+ *
695
+ * console.log('Success:', result.summary.successful)
696
+ * console.log('Failed:', result.summary.failed)
697
+ * ```
698
+ */
699
+ async batch(input) {
700
+ if (!input.items || input.items.length === 0) {
701
+ throw new ValidationError('Items array is required and cannot be empty');
702
+ }
703
+ if (input.items.length > 25) {
704
+ throw new ValidationError('Maximum 25 items per batch request');
705
+ }
706
+ return this.requestWithRetry('POST', '/api/v1/batch/analyze', {
707
+ items: input.items.map(item => {
708
+ if (item.type === 'grooming') {
709
+ return {
710
+ type: item.type,
711
+ messages: item.messages.map(m => ({
712
+ sender_role: m.role,
713
+ text: m.content,
714
+ })),
715
+ context: {
716
+ ...(item.childAge != null && { child_age: item.childAge }),
717
+ ...this.normalizeContext(item.context),
718
+ },
719
+ external_id: item.external_id,
720
+ };
721
+ }
722
+ return {
723
+ type: item.type,
724
+ text: item.content,
725
+ context: this.normalizeContext(item.context),
726
+ external_id: item.external_id,
727
+ };
728
+ }),
729
+ options: {
730
+ parallel: input.parallel ?? true,
731
+ continue_on_error: input.continueOnError ?? true,
732
+ },
733
+ });
734
+ }
735
+ // =========================================================================
736
+ // Usage Methods
737
+ // =========================================================================
738
+ /**
739
+ * Get usage summary for the current billing period
740
+ *
741
+ * @example
742
+ * ```typescript
743
+ * const summary = await tuteliq.getUsageSummary()
744
+ * console.log('Used:', summary.messages_used)
745
+ * console.log('Limit:', summary.message_limit)
746
+ * console.log('Percent:', summary.usage_percentage)
747
+ * ```
748
+ */
749
+ async getUsageSummary() {
750
+ return this.requestWithRetry('GET', '/api/v1/usage/summary');
751
+ }
752
+ /**
753
+ * Get current rate limit quota status
754
+ *
755
+ * @example
756
+ * ```typescript
757
+ * const quota = await tuteliq.getQuota()
758
+ * console.log('Rate limit:', quota.rate_limit)
759
+ * console.log('Remaining this minute:', quota.remaining)
760
+ * ```
761
+ */
762
+ async getQuota() {
763
+ return this.requestWithRetry('GET', '/api/v1/usage/quota');
764
+ }
765
+ // =========================================================================
766
+ // Account Management (GDPR)
767
+ // =========================================================================
768
+ /**
769
+ * Delete all data associated with your account (GDPR Article 17 — Right to Erasure)
770
+ *
771
+ * This permanently deletes all user data including API keys, usage logs,
772
+ * incidents, emotional records, grooming assessments, and safety goals.
773
+ *
774
+ * @example
775
+ * ```typescript
776
+ * const result = await tuteliq.deleteAccountData()
777
+ * console.log(result.message) // "All user data has been deleted"
778
+ * console.log(result.deleted_count) // 42
779
+ * ```
780
+ */
781
+ async deleteAccountData() {
782
+ return this.requestWithRetry('DELETE', '/api/v1/account/data');
783
+ }
784
+ /**
785
+ * Export all data associated with your account (GDPR Article 20 — Right to Data Portability)
786
+ *
787
+ * Returns a JSON export of all stored data grouped by collection.
788
+ *
789
+ * @example
790
+ * ```typescript
791
+ * const data = await tuteliq.exportAccountData()
792
+ * console.log(data.userId)
793
+ * console.log(data.exportedAt)
794
+ * console.log(Object.keys(data.data)) // ['api_keys', 'incidents', ...]
795
+ * ```
796
+ */
797
+ async exportAccountData() {
798
+ return this.requestWithRetry('GET', '/api/v1/account/export');
799
+ }
800
+ /**
801
+ * Record user consent (GDPR Article 7)
802
+ *
803
+ * Creates an immutable consent record for audit trail.
804
+ *
805
+ * @example
806
+ * ```typescript
807
+ * const result = await tuteliq.recordConsent({
808
+ * consent_type: 'child_safety_monitoring',
809
+ * version: '1.0'
810
+ * })
811
+ * ```
812
+ */
813
+ async recordConsent(input) {
814
+ return this.requestWithRetry('POST', '/api/v1/account/consent', input);
815
+ }
816
+ /**
817
+ * Get current consent status (GDPR Article 7)
818
+ *
819
+ * Returns the latest consent record per type.
820
+ *
821
+ * @example
822
+ * ```typescript
823
+ * // Get all consent statuses
824
+ * const all = await tuteliq.getConsentStatus()
825
+ *
826
+ * // Get specific consent type
827
+ * const monitoring = await tuteliq.getConsentStatus('child_safety_monitoring')
828
+ * ```
829
+ */
830
+ async getConsentStatus(type) {
831
+ const query = type ? `?type=${type}` : '';
832
+ return this.requestWithRetry('GET', `/api/v1/account/consent${query}`);
833
+ }
834
+ /**
835
+ * Withdraw consent (GDPR Article 7.3)
836
+ *
837
+ * Creates a withdrawal record. Does not delete consent history.
838
+ *
839
+ * @example
840
+ * ```typescript
841
+ * const result = await tuteliq.withdrawConsent('marketing')
842
+ * ```
843
+ */
844
+ async withdrawConsent(type) {
845
+ return this.requestWithRetry('DELETE', `/api/v1/account/consent/${type}`);
846
+ }
847
+ /**
848
+ * Rectify user data (GDPR Article 16 — Right to Rectification)
849
+ *
850
+ * Updates allowlisted fields on a specific document.
851
+ *
852
+ * @example
853
+ * ```typescript
854
+ * const result = await tuteliq.rectifyData({
855
+ * collection: 'incidents',
856
+ * document_id: 'abc123',
857
+ * fields: { summary: 'Corrected summary' }
858
+ * })
859
+ * ```
860
+ */
861
+ async rectifyData(input) {
862
+ return this.requestWithRetry('PATCH', '/api/v1/account/data', input);
863
+ }
864
+ /**
865
+ * Get audit logs (GDPR Article 15 — Right of Access)
866
+ *
867
+ * Returns the user's audit trail of all data operations.
868
+ *
869
+ * @example
870
+ * ```typescript
871
+ * // Get all audit logs
872
+ * const logs = await tuteliq.getAuditLogs()
873
+ *
874
+ * // Filter by action
875
+ * const exports = await tuteliq.getAuditLogs({ action: 'data_export', limit: 10 })
876
+ * ```
877
+ */
878
+ async getAuditLogs(options) {
879
+ const params = new URLSearchParams();
880
+ if (options?.action)
881
+ params.set('action', options.action);
882
+ if (options?.limit)
883
+ params.set('limit', String(options.limit));
884
+ const query = params.toString() ? `?${params.toString()}` : '';
885
+ return this.requestWithRetry('GET', `/api/v1/account/audit-logs${query}`);
886
+ }
887
+ // =========================================================================
888
+ // Breach Management (GDPR Article 33/34)
889
+ // =========================================================================
890
+ /**
891
+ * Log a new data breach
892
+ *
893
+ * @example
894
+ * ```typescript
895
+ * const result = await tuteliq.logBreach({
896
+ * title: 'Unauthorized access to user data',
897
+ * description: 'A third-party service exposed user emails',
898
+ * severity: 'high',
899
+ * affected_user_ids: ['user-1', 'user-2'],
900
+ * data_categories: ['email', 'name'],
901
+ * reported_by: 'security-team'
902
+ * })
903
+ * ```
904
+ */
905
+ async logBreach(input) {
906
+ return this.requestWithRetry('POST', '/api/v1/admin/breach', input);
907
+ }
908
+ /**
909
+ * List data breaches
910
+ *
911
+ * @example
912
+ * ```typescript
913
+ * // List all breaches
914
+ * const all = await tuteliq.listBreaches()
915
+ *
916
+ * // Filter by status
917
+ * const active = await tuteliq.listBreaches({ status: 'investigating', limit: 10 })
918
+ * ```
919
+ */
920
+ async listBreaches(options) {
921
+ const params = new URLSearchParams();
922
+ if (options?.status)
923
+ params.set('status', options.status);
924
+ if (options?.limit)
925
+ params.set('limit', String(options.limit));
926
+ const query = params.toString() ? `?${params.toString()}` : '';
927
+ return this.requestWithRetry('GET', `/api/v1/admin/breach${query}`);
928
+ }
929
+ /**
930
+ * Get a single breach by ID
931
+ *
932
+ * @example
933
+ * ```typescript
934
+ * const result = await tuteliq.getBreach('breach-123')
935
+ * console.log(result.breach.status)
936
+ * ```
937
+ */
938
+ async getBreach(id) {
939
+ return this.requestWithRetry('GET', `/api/v1/admin/breach/${id}`);
940
+ }
941
+ /**
942
+ * Update a breach's status and notification status
943
+ *
944
+ * @example
945
+ * ```typescript
946
+ * const result = await tuteliq.updateBreachStatus('breach-123', {
947
+ * status: 'contained',
948
+ * notification_status: 'users_notified',
949
+ * notes: 'All affected users have been notified via email'
950
+ * })
951
+ * ```
952
+ */
953
+ async updateBreachStatus(id, input) {
954
+ return this.requestWithRetry('PATCH', `/api/v1/admin/breach/${id}`, input);
955
+ }
956
+ // =========================================================================
957
+ // Media Analysis Methods
958
+ // =========================================================================
959
+ /**
960
+ * Analyze voice/audio for safety concerns
961
+ *
962
+ * Transcribes the audio via Whisper, then runs safety analysis on the transcript.
963
+ *
964
+ * @example
965
+ * ```typescript
966
+ * import { readFileSync } from 'fs'
967
+ *
968
+ * const result = await tuteliq.analyzeVoice({
969
+ * file: readFileSync('recording.mp3'),
970
+ * filename: 'recording.mp3',
971
+ * analysisType: 'all'
972
+ * })
973
+ *
974
+ * console.log('Transcript:', result.transcription.text)
975
+ * console.log('Risk:', result.overall_severity)
976
+ * ```
977
+ */
978
+ async analyzeVoice(input) {
979
+ if (!input.file) {
980
+ throw new ValidationError('Audio file is required');
981
+ }
982
+ if (!input.filename) {
983
+ throw new ValidationError('Filename is required');
984
+ }
985
+ const formData = new FormData();
986
+ if (Buffer.isBuffer(input.file)) {
987
+ formData.append('file', new Blob([input.file]), input.filename);
988
+ }
989
+ else {
990
+ formData.append('file', input.file, input.filename);
991
+ }
992
+ if (input.analysisType)
993
+ formData.append('analysis_type', input.analysisType);
994
+ if (input.fileId)
995
+ formData.append('file_id', input.fileId);
996
+ if (input.external_id)
997
+ formData.append('external_id', input.external_id);
998
+ if (input.customer_id)
999
+ formData.append('customer_id', input.customer_id);
1000
+ if (input.ageGroup)
1001
+ formData.append('age_group', input.ageGroup);
1002
+ if (input.language)
1003
+ formData.append('language', input.language);
1004
+ formData.append('platform', Tuteliq.resolvePlatform(input.platform));
1005
+ if (input.childAge != null)
1006
+ formData.append('child_age', String(input.childAge));
1007
+ if (input.metadata)
1008
+ formData.append('metadata', JSON.stringify(input.metadata));
1009
+ return withRetry(() => this.multipartRequest('/api/v1/safety/voice', formData), { maxRetries: this.retries, initialDelay: this.retryDelay });
1010
+ }
1011
+ /**
1012
+ * Analyze an image for safety concerns
1013
+ *
1014
+ * Uses vision AI for visual content classification and OCR text extraction,
1015
+ * then runs safety analysis on any extracted text.
1016
+ *
1017
+ * @example
1018
+ * ```typescript
1019
+ * import { readFileSync } from 'fs'
1020
+ *
1021
+ * const result = await tuteliq.analyzeImage({
1022
+ * file: readFileSync('screenshot.png'),
1023
+ * filename: 'screenshot.png',
1024
+ * analysisType: 'all'
1025
+ * })
1026
+ *
1027
+ * console.log('Visual:', result.vision.visual_description)
1028
+ * console.log('OCR text:', result.vision.extracted_text)
1029
+ * console.log('Risk:', result.overall_severity)
1030
+ * ```
1031
+ */
1032
+ async analyzeImage(input) {
1033
+ if (!input.file) {
1034
+ throw new ValidationError('Image file is required');
1035
+ }
1036
+ if (!input.filename) {
1037
+ throw new ValidationError('Filename is required');
1038
+ }
1039
+ const formData = new FormData();
1040
+ if (Buffer.isBuffer(input.file)) {
1041
+ formData.append('file', new Blob([input.file]), input.filename);
1042
+ }
1043
+ else {
1044
+ formData.append('file', input.file, input.filename);
1045
+ }
1046
+ if (input.analysisType)
1047
+ formData.append('analysis_type', input.analysisType);
1048
+ if (input.fileId)
1049
+ formData.append('file_id', input.fileId);
1050
+ if (input.external_id)
1051
+ formData.append('external_id', input.external_id);
1052
+ if (input.customer_id)
1053
+ formData.append('customer_id', input.customer_id);
1054
+ if (input.ageGroup)
1055
+ formData.append('age_group', input.ageGroup);
1056
+ formData.append('platform', Tuteliq.resolvePlatform(input.platform));
1057
+ if (input.metadata)
1058
+ formData.append('metadata', JSON.stringify(input.metadata));
1059
+ return withRetry(() => this.multipartRequest('/api/v1/safety/image', formData), { maxRetries: this.retries, initialDelay: this.retryDelay });
1060
+ }
1061
+ // =========================================================================
1062
+ // Webhook Methods
1063
+ // =========================================================================
1064
+ /**
1065
+ * List all webhooks for your account
1066
+ *
1067
+ * @example
1068
+ * ```typescript
1069
+ * const { webhooks } = await tuteliq.listWebhooks()
1070
+ * webhooks.forEach(w => console.log(w.name, w.is_active))
1071
+ * ```
1072
+ */
1073
+ async listWebhooks() {
1074
+ return this.requestWithRetry('GET', '/api/v1/webhooks');
1075
+ }
1076
+ /**
1077
+ * Create a new webhook
1078
+ *
1079
+ * The returned `secret` is only shown once — store it securely for
1080
+ * signature verification.
1081
+ *
1082
+ * @example
1083
+ * ```typescript
1084
+ * import { WebhookEventType } from '@tuteliq/sdk'
1085
+ *
1086
+ * const result = await tuteliq.createWebhook({
1087
+ * name: 'Safety Alerts',
1088
+ * url: 'https://example.com/webhooks/tuteliq',
1089
+ * events: [WebhookEventType.INCIDENT_CRITICAL, WebhookEventType.GROOMING_DETECTED]
1090
+ * })
1091
+ *
1092
+ * console.log('Secret:', result.secret) // Store this securely!
1093
+ * ```
1094
+ */
1095
+ async createWebhook(input) {
1096
+ return this.requestWithRetry('POST', '/api/v1/webhooks', {
1097
+ name: input.name,
1098
+ url: input.url,
1099
+ events: input.events,
1100
+ ...(input.headers && { headers: input.headers }),
1101
+ });
1102
+ }
1103
+ /**
1104
+ * Update an existing webhook
1105
+ *
1106
+ * @example
1107
+ * ```typescript
1108
+ * const result = await tuteliq.updateWebhook('webhook-123', {
1109
+ * name: 'Updated Name',
1110
+ * isActive: false
1111
+ * })
1112
+ * ```
1113
+ */
1114
+ async updateWebhook(id, input) {
1115
+ return this.requestWithRetry('PUT', `/api/v1/webhooks/${id}`, {
1116
+ ...(input.name !== undefined && { name: input.name }),
1117
+ ...(input.url !== undefined && { url: input.url }),
1118
+ ...(input.events !== undefined && { events: input.events }),
1119
+ ...(input.isActive !== undefined && { is_active: input.isActive }),
1120
+ ...(input.headers !== undefined && { headers: input.headers }),
1121
+ });
1122
+ }
1123
+ /**
1124
+ * Delete a webhook
1125
+ *
1126
+ * @example
1127
+ * ```typescript
1128
+ * await tuteliq.deleteWebhook('webhook-123')
1129
+ * ```
1130
+ */
1131
+ async deleteWebhook(id) {
1132
+ return this.requestWithRetry('DELETE', `/api/v1/webhooks/${id}`);
1133
+ }
1134
+ /**
1135
+ * Send a test payload to a webhook
1136
+ *
1137
+ * @example
1138
+ * ```typescript
1139
+ * const result = await tuteliq.testWebhook('webhook-123')
1140
+ * console.log('Success:', result.success)
1141
+ * console.log('Latency:', result.latency_ms, 'ms')
1142
+ * ```
1143
+ */
1144
+ async testWebhook(id) {
1145
+ return this.requestWithRetry('POST', '/api/v1/webhooks/test', { webhook_id: id });
1146
+ }
1147
+ /**
1148
+ * Regenerate a webhook's signing secret
1149
+ *
1150
+ * The old secret is immediately invalidated.
1151
+ *
1152
+ * @example
1153
+ * ```typescript
1154
+ * const { secret } = await tuteliq.regenerateWebhookSecret('webhook-123')
1155
+ * // Update your verification logic with the new secret
1156
+ * ```
1157
+ */
1158
+ async regenerateWebhookSecret(id) {
1159
+ return this.requestWithRetry('POST', `/api/v1/webhooks/${id}/regenerate-secret`);
1160
+ }
1161
+ // =========================================================================
1162
+ // Pricing Methods
1163
+ // =========================================================================
1164
+ /**
1165
+ * Get public pricing plans (no authentication required)
1166
+ *
1167
+ * @example
1168
+ * ```typescript
1169
+ * const { plans } = await tuteliq.getPricing()
1170
+ * plans.forEach(p => console.log(p.name, p.price))
1171
+ * ```
1172
+ */
1173
+ async getPricing() {
1174
+ return this.requestWithRetry('GET', '/api/v1/pricing');
1175
+ }
1176
+ /**
1177
+ * Get detailed pricing plans with monthly/yearly prices
1178
+ *
1179
+ * @example
1180
+ * ```typescript
1181
+ * const { plans } = await tuteliq.getPricingDetails()
1182
+ * plans.forEach(p => console.log(p.name, p.price_monthly, p.api_calls_per_month))
1183
+ * ```
1184
+ */
1185
+ async getPricingDetails() {
1186
+ return this.requestWithRetry('GET', '/api/v1/pricing/details');
1187
+ }
1188
+ // =========================================================================
1189
+ // Additional Usage Methods
1190
+ // =========================================================================
1191
+ /**
1192
+ * Get usage history for the past N days
1193
+ *
1194
+ * @param days - Number of days (1-30, defaults to 7)
1195
+ *
1196
+ * @example
1197
+ * ```typescript
1198
+ * const { days } = await tuteliq.getUsageHistory(14)
1199
+ * days.forEach(d => console.log(d.date, d.total_requests))
1200
+ * ```
1201
+ */
1202
+ async getUsageHistory(days) {
1203
+ const params = new URLSearchParams();
1204
+ if (days != null)
1205
+ params.set('days', String(days));
1206
+ const query = params.toString() ? `?${params.toString()}` : '';
1207
+ return this.requestWithRetry('GET', `/api/v1/usage/history${query}`);
1208
+ }
1209
+ /**
1210
+ * Get usage broken down by tool/endpoint
1211
+ *
1212
+ * @param date - Date in YYYY-MM-DD format (defaults to today)
1213
+ *
1214
+ * @example
1215
+ * ```typescript
1216
+ * const result = await tuteliq.getUsageByTool()
1217
+ * console.log('Tools:', result.tools)
1218
+ * console.log('Endpoints:', result.endpoints)
1219
+ * ```
1220
+ */
1221
+ async getUsageByTool(date) {
1222
+ const params = new URLSearchParams();
1223
+ if (date)
1224
+ params.set('date', date);
1225
+ const query = params.toString() ? `?${params.toString()}` : '';
1226
+ return this.requestWithRetry('GET', `/api/v1/usage/by-tool${query}`);
1227
+ }
1228
+ /**
1229
+ * Get monthly usage, limits, and upgrade recommendations
1230
+ *
1231
+ * @example
1232
+ * ```typescript
1233
+ * const monthly = await tuteliq.getUsageMonthly()
1234
+ * console.log('Used:', monthly.usage.used, '/', monthly.usage.limit)
1235
+ * console.log('Days left:', monthly.billing.days_remaining)
1236
+ *
1237
+ * if (monthly.recommendations?.should_upgrade) {
1238
+ * console.log('Consider upgrading to', monthly.recommendations.suggested_tier)
1239
+ * }
1240
+ * ```
1241
+ */
1242
+ async getUsageMonthly() {
1243
+ return this.requestWithRetry('GET', '/api/v1/usage/monthly');
1244
+ }
1245
+ // =========================================================================
1246
+ // Voice Streaming
1247
+ // =========================================================================
1248
+ /**
1249
+ * Open a real-time voice streaming session over WebSocket.
1250
+ *
1251
+ * Requires the `ws` package as an optional peer dependency:
1252
+ * ```bash
1253
+ * npm install ws
1254
+ * ```
1255
+ *
1256
+ * @example
1257
+ * ```typescript
1258
+ * const session = client.voiceStream(
1259
+ * { intervalSeconds: 10, analysisTypes: ['bullying', 'unsafe'] },
1260
+ * {
1261
+ * onTranscription: (e) => console.log('Transcript:', e.text),
1262
+ * onAlert: (e) => console.log('Alert:', e.category, e.severity),
1263
+ * }
1264
+ * );
1265
+ *
1266
+ * // Send audio chunks as they arrive
1267
+ * session.sendAudio(audioBuffer);
1268
+ *
1269
+ * // End session and get summary
1270
+ * const summary = await session.end();
1271
+ * console.log('Risk:', summary.overall_risk);
1272
+ * ```
1273
+ */
1274
+ voiceStream(config, handlers) {
1275
+ return createVoiceStream(this.apiKey, config, handlers);
1276
+ }
1277
+ }
1278
+ // Legacy export for backwards compatibility
1279
+ export { Tuteliq as TuteliqClient };
1280
+ //# sourceMappingURL=client.js.map