tldrapi 0.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.
package/dist/client.js ADDED
@@ -0,0 +1,650 @@
1
+ "use strict";
2
+ /**
3
+ * TLDRapi HTTP client for Node 18+.
4
+ *
5
+ * Design:
6
+ * - Uses the platform's built-in `fetch` (Node 18+). No third-party
7
+ * HTTP dependency in the shipped package.
8
+ * - Retries 5xx and network errors with exponential backoff + jitter,
9
+ * default 3 attempts. 4xx and 429 are NEVER retried (429 auto-retry
10
+ * would burn credits + worsen the throttle; caller should respect
11
+ * the Retry-After exposed on RateLimitError).
12
+ * - Per-request timeout via AbortController.
13
+ * - Typed exceptions map 1:1 to server response shapes; see errors.ts.
14
+ */
15
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ var desc = Object.getOwnPropertyDescriptor(m, k);
18
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
19
+ desc = { enumerable: true, get: function() { return m[k]; } };
20
+ }
21
+ Object.defineProperty(o, k2, desc);
22
+ }) : (function(o, m, k, k2) {
23
+ if (k2 === undefined) k2 = k;
24
+ o[k2] = m[k];
25
+ }));
26
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
27
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
28
+ }) : function(o, v) {
29
+ o["default"] = v;
30
+ });
31
+ var __importStar = (this && this.__importStar) || (function () {
32
+ var ownKeys = function(o) {
33
+ ownKeys = Object.getOwnPropertyNames || function (o) {
34
+ var ar = [];
35
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
36
+ return ar;
37
+ };
38
+ return ownKeys(o);
39
+ };
40
+ return function (mod) {
41
+ if (mod && mod.__esModule) return mod;
42
+ var result = {};
43
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
44
+ __setModuleDefault(result, mod);
45
+ return result;
46
+ };
47
+ })();
48
+ Object.defineProperty(exports, "__esModule", { value: true });
49
+ exports.TLDRapi = exports.DEFAULT_RAPIDAPI_HOST = void 0;
50
+ const errors_1 = require("./errors");
51
+ const types_1 = require("./types");
52
+ const fs = __importStar(require("fs"));
53
+ const path = __importStar(require("path"));
54
+ // RapidAPI is the only auth path at launch. Customers subscribe to
55
+ // the TLDRapi listing on RapidAPI, get an X-RapidAPI-Key, and this SDK
56
+ // sends every request through the RapidAPI proxy. Direct-signup
57
+ // (bypass RapidAPI) is post-launch — when it ships we'll add a
58
+ // second constructor path.
59
+ exports.DEFAULT_RAPIDAPI_HOST = 'tldrapi-summarizer.p.rapidapi.com';
60
+ const DEFAULT_BASE_URL = `https://${exports.DEFAULT_RAPIDAPI_HOST}`;
61
+ const DEFAULT_TIMEOUT_MS = 60000;
62
+ const DEFAULT_RETRIES = 3;
63
+ const RETRY_BASE_MS = 500;
64
+ function sleep(ms) {
65
+ return new Promise((r) => setTimeout(r, ms));
66
+ }
67
+ function backoffMs(attempt) {
68
+ return RETRY_BASE_MS * 2 ** attempt + Math.random() * 200;
69
+ }
70
+ function validateTier(tier) {
71
+ if (tier === undefined || tier === null)
72
+ return undefined;
73
+ const t = String(tier).toLowerCase().trim();
74
+ if (!types_1.VALID_TIERS.includes(t)) {
75
+ throw new Error(`invalid tier ${JSON.stringify(tier)}; must be one of ${types_1.VALID_TIERS.join(',')}`);
76
+ }
77
+ return t;
78
+ }
79
+ function buildHeaders(rapidapiKey, rapidapiHost, tier, extra) {
80
+ const h = {
81
+ 'Content-Type': 'application/json',
82
+ 'User-Agent': 'tldrapi-node/0.1.0',
83
+ // RapidAPI expects Key + Host together — Key authenticates the
84
+ // customer, Host disambiguates which listing (RapidAPI proxies
85
+ // many APIs from the same p.rapidapi.com prefix).
86
+ 'X-RapidAPI-Key': rapidapiKey,
87
+ 'X-RapidAPI-Host': rapidapiHost,
88
+ };
89
+ if (tier)
90
+ h['X-Quality'] = tier;
91
+ if (extra)
92
+ for (const [k, v] of Object.entries(extra))
93
+ h[k] = v;
94
+ return h;
95
+ }
96
+ async function parseBody(resp) {
97
+ const ctype = resp.headers.get('content-type') ?? '';
98
+ if (ctype.includes('application/json')) {
99
+ try {
100
+ return await resp.json();
101
+ }
102
+ catch {
103
+ return await resp.text();
104
+ }
105
+ }
106
+ return await resp.text();
107
+ }
108
+ function extractCredits(headers) {
109
+ const out = {};
110
+ const map = {
111
+ 'x-credits-charged': 'charged',
112
+ 'x-credits-remaining': 'remaining',
113
+ 'x-credits-tier': 'tier',
114
+ };
115
+ headers.forEach((value, key) => {
116
+ const k = key.toLowerCase();
117
+ if (k in map)
118
+ out[map[k]] = value;
119
+ });
120
+ return out;
121
+ }
122
+ function buildSummarizeResult(body, headers) {
123
+ const usageBody = (body.usage ?? {});
124
+ const usage = {
125
+ inputTokens: Number(usageBody.input_tokens ?? 0) || 0,
126
+ outputTokens: Number(usageBody.output_tokens ?? 0) || 0,
127
+ totalCost: Number(usageBody.total_cost ?? 0) || 0,
128
+ modelUsed: String(usageBody.model_used ?? ''),
129
+ };
130
+ return {
131
+ summary: String(body.summary ?? ''),
132
+ sessionId: String(body.session_id ?? ''),
133
+ usage,
134
+ requestId: headers.get('x-request-id') ?? '',
135
+ credits: extractCredits(headers),
136
+ raw: body,
137
+ };
138
+ }
139
+ function classifyStatus(status) {
140
+ if (status >= 200 && status < 300)
141
+ return 'ok';
142
+ if (status >= 500)
143
+ return 'retry';
144
+ return 'raise';
145
+ }
146
+ function raiseForResponse(status, body, headers) {
147
+ const requestId = headers.get('x-request-id') ?? '';
148
+ const ra = headers.get('retry-after');
149
+ const retryAfterSeconds = ra ? parseInt(ra, 10) || 0 : 0;
150
+ throw (0, errors_1.errorFromResponse)(status, body, requestId, retryAfterSeconds);
151
+ }
152
+ class TLDRapi {
153
+ constructor(opts) {
154
+ if (!opts?.rapidapiKey) {
155
+ throw new Error('rapidapiKey is required (subscribe on RapidAPI to obtain one)');
156
+ }
157
+ this.rapidapiKey = opts.rapidapiKey;
158
+ this.rapidapiHost = opts.rapidapiHost ?? exports.DEFAULT_RAPIDAPI_HOST;
159
+ // baseUrl defaults to https://<rapidapiHost>. Pass baseUrl
160
+ // explicitly for staging / mock-server testing.
161
+ this.baseUrl = (opts.baseUrl ?? `https://${this.rapidapiHost}`).replace(/\/$/, '');
162
+ this.timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
163
+ this.retries = Math.max(0, Math.floor(opts.retries ?? DEFAULT_RETRIES));
164
+ this.fetchImpl = opts.fetchImpl ?? globalThis.fetch;
165
+ if (typeof this.fetchImpl !== 'function') {
166
+ throw new Error('no fetch available; run on Node 18+ or pass `fetchImpl` (e.g. from `node-fetch` or `undici`).');
167
+ }
168
+ }
169
+ async summarize(inputText, options = {}) {
170
+ if (!inputText || typeof inputText !== 'string') {
171
+ throw new Error('inputText must be a non-empty string');
172
+ }
173
+ const tier = validateTier(options.tier);
174
+ const headers = buildHeaders(this.rapidapiKey, this.rapidapiHost, tier, options.extraHeaders);
175
+ if (options.allowOverage)
176
+ headers['X-Allow-Overage'] = 'true';
177
+ const body = { input_text: inputText };
178
+ if (options.sessionId)
179
+ body.session_id = options.sessionId;
180
+ if (options.modelAlias)
181
+ body.model_alias = options.modelAlias;
182
+ if (options.config) {
183
+ // Serialize SummarizeConfig, dropping undefined fields and
184
+ // camelCase→snake_case for the wire (matches openapi.yaml).
185
+ const cfg = {};
186
+ const c = options.config;
187
+ if (c.modelAlias !== undefined)
188
+ cfg.model_alias = c.modelAlias;
189
+ if (c.temperature !== undefined)
190
+ cfg.temperature = c.temperature;
191
+ if (c.topP !== undefined)
192
+ cfg.top_p = c.topP;
193
+ if (c.maxOutputTokens !== undefined)
194
+ cfg.max_output_tokens = c.maxOutputTokens;
195
+ if (c.maxInputTokens !== undefined)
196
+ cfg.max_input_tokens = c.maxInputTokens;
197
+ if (Object.keys(cfg).length > 0)
198
+ body.config = cfg;
199
+ }
200
+ const resp = await this.request('POST', '/summarize', {
201
+ body: JSON.stringify(body),
202
+ headers,
203
+ timeoutMs: options.timeoutMs,
204
+ });
205
+ const parsed = await parseBody(resp);
206
+ if (resp.status >= 400)
207
+ raiseForResponse(resp.status, parsed, resp.headers);
208
+ if (!parsed || typeof parsed !== 'object') {
209
+ throw new errors_1.ServerError(`unexpected non-JSON summarize response: ${String(parsed).slice(0, 200)}`, {
210
+ statusCode: resp.status,
211
+ requestId: resp.headers.get('x-request-id') ?? '',
212
+ });
213
+ }
214
+ return buildSummarizeResult(parsed, resp.headers);
215
+ }
216
+ async rates() {
217
+ const headers = buildHeaders(this.rapidapiKey, this.rapidapiHost, undefined);
218
+ const resp = await this.request('GET', '/rates', { headers });
219
+ const parsed = await parseBody(resp);
220
+ if (resp.status >= 400)
221
+ raiseForResponse(resp.status, parsed, resp.headers);
222
+ const rec = (parsed && typeof parsed === 'object') ? parsed : {};
223
+ // Spec (openapi.yaml RatesResponse): tier ints live under
224
+ // `credits_per_call.{tier}`, timestamp is `credit_costs_updated_at`,
225
+ // and `history_url` is a top-level field. Pre-1.0 SDK read the
226
+ // wrong nesting and always returned the fallback ints.
227
+ const cpc = (rec.credits_per_call && typeof rec.credits_per_call === 'object')
228
+ ? rec.credits_per_call : {};
229
+ const updated = typeof rec.credit_costs_updated_at === 'string'
230
+ ? rec.credit_costs_updated_at : undefined;
231
+ return {
232
+ quick: Number(cpc.quick ?? 1) || 1,
233
+ standard: Number(cpc.standard ?? 5) || 5,
234
+ deep: Number(cpc.deep ?? 30) || 30,
235
+ premium: Number(cpc.premium ?? 110) || 110,
236
+ ultra: Number(cpc.ultra ?? 400) || 400,
237
+ creditCostsUpdatedAt: updated,
238
+ updatedAt: updated, // deprecated alias
239
+ historyUrl: typeof rec.history_url === 'string' ? rec.history_url : '',
240
+ raw: rec,
241
+ };
242
+ }
243
+ async usage() {
244
+ const headers = buildHeaders(this.rapidapiKey, this.rapidapiHost, undefined);
245
+ const resp = await this.request('GET', '/usage', { headers });
246
+ const parsed = await parseBody(resp);
247
+ if (resp.status >= 400)
248
+ raiseForResponse(resp.status, parsed, resp.headers);
249
+ const rec = (parsed && typeof parsed === 'object') ? parsed : {};
250
+ // Spec (openapi.yaml UsageResponse). Pre-1.0 SDK read
251
+ // period/calls/credits_charged/credits_remaining — none of which
252
+ // the server has ever emitted — so every field returned zero.
253
+ const limitsRec = (rec.limits && typeof rec.limits === 'object')
254
+ ? rec.limits : {};
255
+ const limits = {
256
+ perMinute: typeof limitsRec.per_minute === 'number' ? limitsRec.per_minute : undefined,
257
+ daily: typeof limitsRec.daily === 'number' ? limitsRec.daily : undefined,
258
+ credits: typeof limitsRec.credits === 'number' ? limitsRec.credits : undefined,
259
+ concurrent: typeof limitsRec.concurrent === 'number' ? limitsRec.concurrent : undefined,
260
+ };
261
+ return {
262
+ usageCount: Number(rec.usage_count ?? 0) || 0,
263
+ successfulRequests: Number(rec.successful_requests ?? 0) || 0,
264
+ failedRequests: Number(rec.failed_requests ?? 0) || 0,
265
+ averageResponseTimeMs: Number(rec.average_response_time_ms ?? 0) || 0,
266
+ endpointsUsed: (rec.endpoints_used && typeof rec.endpoints_used === 'object')
267
+ ? rec.endpoints_used : {},
268
+ errorRate: Number(rec.error_rate ?? 0) || 0,
269
+ plan: String(rec.plan ?? ''),
270
+ limits,
271
+ raw: rec,
272
+ };
273
+ }
274
+ // --- text-body /convert endpoints ---
275
+ async convertJsonToText(text, opts = {}) {
276
+ return this.convertText('/convert/json-to-text', text, opts);
277
+ }
278
+ async convertHtmlToText(text, opts = {}) {
279
+ return this.convertText('/convert/html-to-text', text, opts);
280
+ }
281
+ async convertMdToText(text, opts = {}) {
282
+ return this.convertText('/convert/md-to-text', text, opts);
283
+ }
284
+ async convertText(routePath, text, opts) {
285
+ if (!text || typeof text !== 'string')
286
+ throw new Error('text must be a non-empty string');
287
+ const headers = buildHeaders(this.rapidapiKey, this.rapidapiHost, undefined, opts.extraHeaders);
288
+ if (opts.allowOverage)
289
+ headers['X-Allow-Overage'] = 'true';
290
+ const resp = await this.request('POST', routePath, {
291
+ body: JSON.stringify({ text }),
292
+ headers,
293
+ timeoutMs: opts.timeoutMs,
294
+ });
295
+ const parsed = await parseBody(resp);
296
+ if (resp.status >= 400)
297
+ raiseForResponse(resp.status, parsed, resp.headers);
298
+ return buildConvertResult((parsed && typeof parsed === 'object') ? parsed : {}, resp.headers);
299
+ }
300
+ // --- file-upload /convert endpoints ---
301
+ async convertDocToText(file, opts = {}) {
302
+ return this.convertFile('/convert/doc-to-text', file, opts);
303
+ }
304
+ async convertDocToLatex(file, opts = {}) {
305
+ return this.convertFile('/convert/doc-to-latex', file, opts);
306
+ }
307
+ /** Deprecated alias for `convertDocToText`. Kept for backward compat. */
308
+ async convertDocxToText(file, opts = {}) {
309
+ return this.convertFile('/convert/docx-to-text', file, opts);
310
+ }
311
+ async convertFile(routePath, file, opts) {
312
+ const headers = buildHeaders(this.rapidapiKey, this.rapidapiHost, undefined, opts.extraHeaders);
313
+ if (opts.allowOverage)
314
+ headers['X-Allow-Overage'] = 'true';
315
+ // FormData must set Content-Type with the boundary itself.
316
+ delete headers['Content-Type'];
317
+ const form = await buildMultipart(file, opts.filename);
318
+ const resp = await this.request('POST', routePath, {
319
+ multipart: form,
320
+ headers,
321
+ timeoutMs: opts.timeoutMs,
322
+ });
323
+ const parsed = await parseBody(resp);
324
+ if (resp.status >= 400)
325
+ raiseForResponse(resp.status, parsed, resp.headers);
326
+ return buildConvertResult((parsed && typeof parsed === 'object') ? parsed : {}, resp.headers);
327
+ }
328
+ async convertPdfToLatex(file, opts = {}) {
329
+ const headers = buildHeaders(this.rapidapiKey, this.rapidapiHost, undefined, opts.extraHeaders);
330
+ if (opts.allowOverage)
331
+ headers['X-Allow-Overage'] = 'true';
332
+ if (opts.backend) {
333
+ if (!['auto', 'text', 'modal'].includes(opts.backend)) {
334
+ throw new Error('backend must be one of: auto, text, modal');
335
+ }
336
+ headers['X-PDF-Backend'] = opts.backend;
337
+ }
338
+ delete headers['Content-Type'];
339
+ const form = await buildMultipart(file, opts.filename ?? 'upload.pdf');
340
+ const resp = await this.request('POST', '/convert/pdf-to-latex', {
341
+ multipart: form,
342
+ headers,
343
+ timeoutMs: opts.timeoutMs,
344
+ });
345
+ const parsed = await parseBody(resp);
346
+ const rec = (parsed && typeof parsed === 'object') ? parsed : {};
347
+ if (resp.status === 202)
348
+ return buildPdfAsync(rec, resp.headers);
349
+ if (resp.status >= 400)
350
+ raiseForResponse(resp.status, parsed, resp.headers);
351
+ return buildPdfSync(rec, resp.headers);
352
+ }
353
+ async pdfStatus(jobId) {
354
+ if (!jobId)
355
+ throw new Error('jobId required');
356
+ const headers = buildHeaders(this.rapidapiKey, this.rapidapiHost, undefined);
357
+ const resp = await this.request('GET', `/convert/pdf-to-latex/status/${encodeURIComponent(jobId)}`, { headers });
358
+ const parsed = await parseBody(resp);
359
+ if (resp.status >= 400)
360
+ raiseForResponse(resp.status, parsed, resp.headers);
361
+ const rec = (parsed && typeof parsed === 'object') ? parsed : {};
362
+ const status = String(rec.status ?? '');
363
+ if (status === 'queued' || status === 'running')
364
+ return buildPdfAsync(rec, resp.headers);
365
+ return buildPdfSync(rec, resp.headers);
366
+ }
367
+ // --- rates history + usage range ---
368
+ async ratesHistory() {
369
+ const headers = buildHeaders(this.rapidapiKey, this.rapidapiHost, undefined);
370
+ const resp = await this.request('GET', '/rates/history', { headers });
371
+ const parsed = await parseBody(resp);
372
+ if (resp.status >= 400)
373
+ raiseForResponse(resp.status, parsed, resp.headers);
374
+ return buildRatesHistory((parsed && typeof parsed === 'object') ? parsed : {});
375
+ }
376
+ async usageRange(from, to) {
377
+ if (!from || !to)
378
+ throw new Error('from and to required (YYYY-MM-DD)');
379
+ const headers = buildHeaders(this.rapidapiKey, this.rapidapiHost, undefined);
380
+ const qs = `?from=${encodeURIComponent(from)}&to=${encodeURIComponent(to)}`;
381
+ const resp = await this.request('GET', `/usage/range${qs}`, { headers });
382
+ const parsed = await parseBody(resp);
383
+ if (resp.status >= 400)
384
+ raiseForResponse(resp.status, parsed, resp.headers);
385
+ return buildUsageRange((parsed && typeof parsed === 'object') ? parsed : {});
386
+ }
387
+ // --- custom prompts (Business/Enterprise) ---
388
+ async customPromptSubmit(voiceName, instruction, opts = {}) {
389
+ if (!voiceName || !instruction)
390
+ throw new Error('voiceName and instruction required');
391
+ const headers = buildHeaders(this.rapidapiKey, this.rapidapiHost, undefined, opts.extraHeaders);
392
+ if (opts.allowOverage)
393
+ headers['X-Allow-Overage'] = 'true';
394
+ const body = { voice_name: voiceName, instruction };
395
+ if (opts.sessionId)
396
+ body.session_id = opts.sessionId;
397
+ const resp = await this.request('POST', '/custom-prompts/submit', {
398
+ body: JSON.stringify(body),
399
+ headers,
400
+ });
401
+ const parsed = await parseBody(resp);
402
+ if (resp.status >= 400)
403
+ raiseForResponse(resp.status, parsed, resp.headers);
404
+ return buildCustomPromptResult((parsed && typeof parsed === 'object') ? parsed : {});
405
+ }
406
+ async customPromptsList(opts = {}) {
407
+ const headers = buildHeaders(this.rapidapiKey, this.rapidapiHost, undefined);
408
+ const body = {};
409
+ if (opts.sessionId)
410
+ body.session_id = opts.sessionId;
411
+ const resp = await this.request('POST', '/custom-prompts/list', {
412
+ body: JSON.stringify(body),
413
+ headers,
414
+ });
415
+ const parsed = await parseBody(resp);
416
+ if (resp.status >= 400)
417
+ raiseForResponse(resp.status, parsed, resp.headers);
418
+ return buildCustomPromptList((parsed && typeof parsed === 'object') ? parsed : {});
419
+ }
420
+ async customPromptGet(promptId) {
421
+ if (!promptId)
422
+ throw new Error('promptId required');
423
+ const headers = buildHeaders(this.rapidapiKey, this.rapidapiHost, undefined);
424
+ const resp = await this.request('GET', `/custom-prompts/${encodeURIComponent(promptId)}`, { headers });
425
+ const parsed = await parseBody(resp);
426
+ if (resp.status >= 400)
427
+ raiseForResponse(resp.status, parsed, resp.headers);
428
+ return buildCustomPromptDetail((parsed && typeof parsed === 'object') ? parsed : {});
429
+ }
430
+ async request(method, path, init = {}) {
431
+ const url = `${this.baseUrl}${path}`;
432
+ const timeoutMs = init.timeoutMs ?? this.timeoutMs;
433
+ let lastError;
434
+ for (let attempt = 0; attempt <= this.retries; attempt++) {
435
+ const controller = new AbortController();
436
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
437
+ try {
438
+ const body = (init.multipart ?? init.body);
439
+ const resp = await this.fetchImpl(url, {
440
+ method,
441
+ body,
442
+ headers: init.headers,
443
+ signal: controller.signal,
444
+ });
445
+ clearTimeout(timer);
446
+ const action = classifyStatus(resp.status);
447
+ if (action === 'retry' && attempt < this.retries) {
448
+ // Body must be consumed or discarded before retry
449
+ try {
450
+ await resp.text();
451
+ }
452
+ catch { /* ignore */ }
453
+ await sleep(backoffMs(attempt));
454
+ continue;
455
+ }
456
+ return resp;
457
+ }
458
+ catch (err) {
459
+ clearTimeout(timer);
460
+ const isAbort = err instanceof Error && (err.name === 'AbortError' || /aborted/i.test(err.message));
461
+ lastError = isAbort
462
+ ? new errors_1.TimeoutError(`request timed out after ${timeoutMs}ms`)
463
+ : new errors_1.NetworkError(err instanceof Error ? err.message : String(err));
464
+ if (attempt < this.retries) {
465
+ await sleep(backoffMs(attempt));
466
+ continue;
467
+ }
468
+ throw lastError;
469
+ }
470
+ }
471
+ // Unreachable
472
+ throw lastError ?? new errors_1.NetworkError('unknown transport failure');
473
+ }
474
+ }
475
+ exports.TLDRapi = TLDRapi;
476
+ // ─── response builders ────────────────────────────────────────────────
477
+ function buildConvertResult(rec, headers) {
478
+ return {
479
+ output: String(rec.output ?? ''),
480
+ outputFormat: String(rec.output_format ?? ''),
481
+ inputFormat: String(rec.input_format ?? ''),
482
+ inputBytes: Number(rec.input_bytes ?? 0) || 0,
483
+ outputChars: Number(rec.output_chars ?? 0) || 0,
484
+ elapsedMs: Number(rec.elapsed_ms ?? 0) || 0,
485
+ requestId: String(rec.request_id ?? headers.get('x-request-id') ?? ''),
486
+ warnings: Array.isArray(rec.warnings) ? rec.warnings : [],
487
+ raw: rec,
488
+ };
489
+ }
490
+ function buildPdfSync(rec, headers) {
491
+ return {
492
+ output: String(rec.output ?? ''),
493
+ outputFormat: String(rec.output_format ?? ''),
494
+ inputFormat: String(rec.input_format ?? 'pdf'),
495
+ inputBytes: Number(rec.input_bytes ?? 0) || 0,
496
+ pages: Number(rec.pages ?? 0) || 0,
497
+ elapsedMs: Number(rec.elapsed_ms ?? 0) || 0,
498
+ backend: String(rec.backend ?? ''),
499
+ requestId: String(rec.request_id ?? headers.get('x-request-id') ?? ''),
500
+ warnings: Array.isArray(rec.warnings) ? rec.warnings : [],
501
+ jobId: String(rec.job_id ?? ''),
502
+ status: String(rec.status ?? 'done'),
503
+ pollUrl: String(rec.poll_url ?? ''),
504
+ estimatedSeconds: Number(rec.estimated_seconds ?? 0) || 0,
505
+ raw: rec,
506
+ };
507
+ }
508
+ function buildPdfAsync(rec, headers) {
509
+ return {
510
+ output: '',
511
+ outputFormat: '',
512
+ inputFormat: 'pdf',
513
+ inputBytes: Number(rec.input_bytes ?? 0) || 0,
514
+ pages: Number(rec.pages ?? 0) || 0,
515
+ elapsedMs: 0,
516
+ backend: String(rec.backend ?? ''),
517
+ requestId: String(rec.request_id ?? headers.get('x-request-id') ?? ''),
518
+ warnings: Array.isArray(rec.warnings) ? rec.warnings : [],
519
+ jobId: String(rec.job_id ?? ''),
520
+ status: String(rec.status ?? 'queued'),
521
+ pollUrl: String(rec.poll_url ?? ''),
522
+ estimatedSeconds: Number(rec.estimated_seconds ?? 0) || 0,
523
+ raw: rec,
524
+ };
525
+ }
526
+ function buildRatesHistory(rec) {
527
+ const raw = Array.isArray(rec.history) ? rec.history : [];
528
+ const history = raw.map((h) => {
529
+ const r = (h && typeof h === 'object') ? h : {};
530
+ return {
531
+ changedAt: String(r.changed_at ?? ''),
532
+ tier: String(r.tier ?? ''),
533
+ creditsBefore: Number(r.credits_before ?? 0) || 0,
534
+ creditsAfter: Number(r.credits_after ?? 0) || 0,
535
+ reason: String(r.reason ?? ''),
536
+ operator: String(r.operator ?? ''),
537
+ };
538
+ });
539
+ return {
540
+ history,
541
+ rangeDays: Number(rec.range_days ?? 30) || 30,
542
+ totalChanges: Number(rec.total_changes ?? history.length) || history.length,
543
+ raw: rec,
544
+ };
545
+ }
546
+ function buildUsageRange(rec) {
547
+ const raw = Array.isArray(rec.daily) ? rec.daily : [];
548
+ const daily = raw.map((d) => {
549
+ const r = (d && typeof d === 'object') ? d : {};
550
+ return {
551
+ date: String(r.date ?? ''),
552
+ creditsUsed: Number(r.credits_used ?? 0) || 0,
553
+ callCount: Number(r.call_count ?? 0) || 0,
554
+ };
555
+ });
556
+ return {
557
+ from: String(rec.from ?? ''),
558
+ to: String(rec.to ?? ''),
559
+ creditsUsed: Number(rec.credits_used ?? 0) || 0,
560
+ daily,
561
+ raw: rec,
562
+ };
563
+ }
564
+ function buildCustomPromptResult(rec) {
565
+ const status = String(rec.status ?? '');
566
+ return {
567
+ id: String(rec.id ?? ''),
568
+ voiceName: String(rec.voice_name ?? ''),
569
+ status,
570
+ approved: status === 'approved' || rec.approved === true,
571
+ voiceReference: typeof rec.voice_reference === 'string' ? rec.voice_reference : undefined,
572
+ rejectionReason: typeof rec.rejection_reason === 'string' ? rec.rejection_reason : undefined,
573
+ updatedInPlace: rec.updated_in_place === true,
574
+ supersededIds: Array.isArray(rec.superseded_ids) ? rec.superseded_ids : [],
575
+ raw: rec,
576
+ };
577
+ }
578
+ function buildCustomPromptSummary(r) {
579
+ return {
580
+ id: String(r.id ?? ''),
581
+ voiceName: String(r.voice_name ?? ''),
582
+ status: String(r.status ?? ''),
583
+ voiceReference: typeof r.voice_reference === 'string' ? r.voice_reference : undefined,
584
+ approvedAlias: typeof r.approved_alias === 'string' ? r.approved_alias : undefined,
585
+ rejectionReason: typeof r.rejection_reason === 'string' ? r.rejection_reason : undefined,
586
+ submittedAt: String(r.submitted_at ?? ''),
587
+ reviewedAt: String(r.reviewed_at ?? ''),
588
+ };
589
+ }
590
+ function buildCustomPromptList(rec) {
591
+ const raw = Array.isArray(rec.custom_prompts) ? rec.custom_prompts : [];
592
+ return {
593
+ customerId: String(rec.customer_id ?? ''),
594
+ customPrompts: raw.map((p) => buildCustomPromptSummary((p && typeof p === 'object') ? p : {})),
595
+ raw: rec,
596
+ };
597
+ }
598
+ function buildCustomPromptDetail(rec) {
599
+ return {
600
+ id: String(rec.id ?? ''),
601
+ customerId: String(rec.customer_id ?? ''),
602
+ voiceName: String(rec.voice_name ?? ''),
603
+ instruction: String(rec.instruction ?? ''),
604
+ status: String(rec.status ?? ''),
605
+ voiceReference: typeof rec.voice_reference === 'string' ? rec.voice_reference : undefined,
606
+ approvedAlias: typeof rec.approved_alias === 'string' ? rec.approved_alias : undefined,
607
+ rejectionReason: typeof rec.rejection_reason === 'string' ? rec.rejection_reason : undefined,
608
+ judgeVerdictJson: String(rec.judge_verdict_json ?? ''),
609
+ submittedAt: String(rec.submitted_at ?? ''),
610
+ reviewedAt: String(rec.reviewed_at ?? ''),
611
+ raw: rec,
612
+ };
613
+ }
614
+ // ─── multipart upload ─────────────────────────────────────────────────
615
+ async function buildMultipart(file, filename) {
616
+ // Uses the platform FormData/Blob (Node 18+ has them globally). Files
617
+ // can be a filesystem path (string), a Buffer/Uint8Array, an
618
+ // ArrayBuffer, or a Blob. The multipart boundary is set by fetch itself
619
+ // — callers must NOT pre-set Content-Type on headers.
620
+ const form = new FormData();
621
+ let blob;
622
+ let name = filename ?? 'upload';
623
+ if (typeof file === 'string') {
624
+ // filesystem path
625
+ const buf = await fs.promises.readFile(file);
626
+ blob = new Blob([buf]);
627
+ if (!filename)
628
+ name = path.basename(file);
629
+ }
630
+ else if (file instanceof Blob) {
631
+ blob = file;
632
+ }
633
+ else if (file instanceof ArrayBuffer) {
634
+ blob = new Blob([file]);
635
+ }
636
+ else if (file instanceof Uint8Array) {
637
+ // Buffer is a Uint8Array subclass, so this branch catches both.
638
+ // Cast the buffer view to a plain ArrayBuffer for Blob's typing;
639
+ // at runtime Blob copies the bytes, so the underlying buffer type
640
+ // doesn't matter.
641
+ const view = file;
642
+ blob = new Blob([view.buffer.slice(view.byteOffset, view.byteOffset + view.byteLength)]);
643
+ }
644
+ else {
645
+ throw new Error('unsupported file input: expected string path, Buffer, Uint8Array, ArrayBuffer, or Blob');
646
+ }
647
+ form.append('file', blob, name);
648
+ return form;
649
+ }
650
+ //# sourceMappingURL=client.js.map