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