@puppetry.com/sdk 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.
@@ -0,0 +1,1638 @@
1
+ import {
2
+ PuppetryAuthError,
3
+ PuppetryError,
4
+ PuppetryNetworkError,
5
+ PuppetryRateLimitError,
6
+ PuppetryTimeoutError,
7
+ puppetryErrorData
8
+ } from "./chunk-QK5VPBL7.mjs";
9
+
10
+ // src/client.ts
11
+ var DEFAULT_BASE_URL = "https://www.puppetry.com/api/v1";
12
+ var DEFAULT_TIMEOUT = 3e4;
13
+ var DEFAULT_MAX_RETRIES = 2;
14
+ var DEFAULT_RETRY_DELAY_MS = 500;
15
+ var SDK_VERSION = "0.1.0";
16
+ var IDEMPOTENCY_KEY_MAX_LENGTH = 200;
17
+ var EMPTY_HEADERS = { get: () => null };
18
+ var VIDEO_STATUS_ALIASES = {
19
+ active: "processing",
20
+ complete: "completed",
21
+ completed: "completed",
22
+ delayed: "queued",
23
+ done: "completed",
24
+ error: "failed",
25
+ errored: "failed",
26
+ failed: "failed",
27
+ failure: "failed",
28
+ finished: "completed",
29
+ "in-progress": "processing",
30
+ in_progress: "processing",
31
+ paused: "queued",
32
+ prioritized: "queued",
33
+ process: "processing",
34
+ processing: "processing",
35
+ queued: "queued",
36
+ rendering: "processing",
37
+ retrying: "processing",
38
+ running: "processing",
39
+ started: "processing",
40
+ success: "completed",
41
+ succeeded: "completed",
42
+ wait: "queued",
43
+ waiting: "queued",
44
+ "waiting-children": "queued"
45
+ };
46
+ function normalizeVideoStatusAlias(status) {
47
+ if (typeof status !== "string") return void 0;
48
+ return VIDEO_STATUS_ALIASES[status.trim().toLowerCase()];
49
+ }
50
+ function normalizeVideoJobSource(source) {
51
+ return source === "text" || source === "audio" ? source : void 0;
52
+ }
53
+ var Puppetry = class {
54
+ constructor(config) {
55
+ // ── Videos ─────────────────────────────────────────────────────────
56
+ /**
57
+ * Create a video from text (text-to-speech + lip sync).
58
+ *
59
+ * @example
60
+ * ```ts
61
+ * const job = await puppetry.videos.createFromText({
62
+ * text: "Hello, world!",
63
+ * image_url: "https://example.com/photo.jpg",
64
+ * voice_id: "puppetry-af_heart",
65
+ * });
66
+ * ```
67
+ */
68
+ this.videos = {
69
+ createFromText: async (params) => this.requestWithResponse(
70
+ "POST",
71
+ "/videos/text",
72
+ this.textVideoBody(params),
73
+ {
74
+ idempotencyKey: this.idempotencyKeyFromParams(params)
75
+ }
76
+ ).then(({ data, headers }) => this.normalizeVideoJob(data, headers)),
77
+ createFromAudio: async (params) => this.requestWithResponse(
78
+ "POST",
79
+ "/videos/audio",
80
+ this.audioVideoBody(params),
81
+ {
82
+ idempotencyKey: this.idempotencyKeyFromParams(params)
83
+ }
84
+ ).then(({ data, headers }) => this.normalizeVideoJob(data, headers)),
85
+ get: async (jobId) => this.getWithResponse(this.videoJobPath(jobId)).then(
86
+ ({ data, headers }) => this.normalizeVideoJob(data, headers)
87
+ ),
88
+ /**
89
+ * Return the current Developer API video readiness contract.
90
+ *
91
+ * Use this before reserving uploads or queueing a video when an agent needs
92
+ * to fail fast on credits or active slot limits.
93
+ */
94
+ getReadiness: () => this.getVideoGenerationReadiness(),
95
+ /**
96
+ * Assert that this API key can create a video right now.
97
+ *
98
+ * Throws PuppetryRateLimitError with retryAfter for active-slot blockers,
99
+ * and PuppetryError(402) for credit blockers.
100
+ */
101
+ ensureReady: () => this.ensureVideoGenerationReady(),
102
+ /**
103
+ * Wait for a video job to complete, polling at the given interval.
104
+ *
105
+ * @param jobId - The job ID to watch
106
+ * @param options.intervalMs - Polling interval (default: 2000)
107
+ * @param options.timeoutMs - Max wait time (default: 300000 = 5 min)
108
+ * @returns The completed VideoJob
109
+ * @throws PuppetryTimeoutError if the job doesn't complete in time
110
+ */
111
+ waitForCompletion: async (jobId, options) => {
112
+ const interval = options?.intervalMs ?? 2e3;
113
+ const timeout = options?.timeoutMs ?? 3e5;
114
+ const deadline = Date.now() + timeout;
115
+ const initialDelayMs = this.optionDelayMs(options?.initialDelayMs);
116
+ let currentJobId = jobId;
117
+ if (initialDelayMs !== void 0 && initialDelayMs > 0) {
118
+ await this.sleepWithinDeadline(initialDelayMs, deadline);
119
+ }
120
+ while (Date.now() < deadline) {
121
+ try {
122
+ const { data: job, headers } = await this.getWithResponse(
123
+ this.videoJobPath(currentJobId)
124
+ );
125
+ const normalizedJob = this.normalizeVideoJob(job, headers);
126
+ currentJobId = this.videoJobIdFromStatusUrl(
127
+ normalizedJob.statusUrl ?? normalizedJob.status_url
128
+ ) ?? normalizedJob.id;
129
+ await options?.onPoll?.(normalizedJob);
130
+ if (normalizedJob.status === "completed" || normalizedJob.status === "failed") {
131
+ return normalizedJob;
132
+ }
133
+ const delayMs = this.pollingDelayMs(normalizedJob, headers, interval);
134
+ await this.sleep(
135
+ Math.min(delayMs, Math.max(deadline - Date.now(), 0))
136
+ );
137
+ } catch (err) {
138
+ if (!(err instanceof PuppetryError)) throw err;
139
+ const delayMs = this.retryableStatusErrorDelayMs(err, interval);
140
+ if (delayMs === void 0) throw err;
141
+ const retryingEvent = this.videoRetryingEvent(err, currentJobId);
142
+ currentJobId = retryingEvent.jobId;
143
+ await options?.onRetrying?.(retryingEvent);
144
+ await this.sleepWithinDeadline(delayMs, deadline);
145
+ }
146
+ }
147
+ throw new PuppetryTimeoutError(timeout);
148
+ },
149
+ /**
150
+ * Stream progress-like events by polling the live v1 job status endpoint.
151
+ *
152
+ * @param jobId - The job ID to stream
153
+ * @param options.intervalMs - Polling interval (default: 2000)
154
+ * @param options.timeoutMs - Max stream time (default: 300000 = 5 min)
155
+ * @returns AsyncGenerator yielding VideoEvent objects
156
+ *
157
+ * @example
158
+ * ```ts
159
+ * for await (const event of puppetry.videos.stream("job-123")) {
160
+ * console.log(`${event.type}: ${event.progress}%`);
161
+ * if (event.type === "completed") break;
162
+ * }
163
+ * ```
164
+ */
165
+ stream: (jobId, options) => this.streamPolledVideoEvents(jobId, options)
166
+ };
167
+ // ── Jobs ───────────────────────────────────────────────────────────
168
+ this.jobs = {
169
+ /**
170
+ * Fetch a generated video job by ID.
171
+ *
172
+ * This is an alias for `videos.get()` because Puppetry's public v1 API
173
+ * exposes generated video job state under `/videos/{jobId}`.
174
+ */
175
+ get: (jobId) => this.videos.get(jobId),
176
+ /**
177
+ * Wait for a generated video job to complete, polling at the given interval.
178
+ */
179
+ waitForCompletion: (jobId, options) => this.videos.waitForCompletion(jobId, options)
180
+ };
181
+ // ── Voices ─────────────────────────────────────────────────────────
182
+ this.voices = {
183
+ list: (params) => {
184
+ const query = new URLSearchParams();
185
+ if (params?.language) query.set("language", params.language);
186
+ if (params?.gender) query.set("gender", params.gender);
187
+ if (params?.cursor) query.set("cursor", params.cursor);
188
+ if (params?.limit) query.set("limit", String(params.limit));
189
+ const qs = query.toString();
190
+ return this.get(`/voices${qs ? "?" + qs : ""}`);
191
+ },
192
+ listPuppetry: () => this.get("/voices/puppetry")
193
+ };
194
+ // ── Text to Speech ───────────────────────────────────────────────────
195
+ this.tts = {
196
+ createPuppetry: (params) => this.post(
197
+ "/tts/puppetry",
198
+ params
199
+ )
200
+ };
201
+ // ── Uploads ───────────────────────────────────────────────────────────
202
+ this.uploads = {
203
+ /**
204
+ * Create a signed PUT URL for hosting audio that can be used by Puppetry API
205
+ * workflows.
206
+ */
207
+ createAudioUrl: async (params) => this.post(
208
+ "/uploads/audio-url",
209
+ this.compactBody({
210
+ mime_type: this.resolveUploadStringAliases([
211
+ { name: "mime_type", value: params.mime_type },
212
+ { name: "mimeType", value: params.mimeType },
213
+ { name: "contentType", value: params.contentType }
214
+ ]),
215
+ content_length: this.resolveNumberAliases([
216
+ { name: "content_length", value: params.content_length },
217
+ { name: "contentLength", value: params.contentLength },
218
+ { name: "sizeBytes", value: params.sizeBytes },
219
+ { name: "file_size", value: params.file_size },
220
+ { name: "fileSize", value: params.fileSize }
221
+ ]),
222
+ require_video_readiness: params.require_video_readiness ?? params.requireVideoReadiness
223
+ }),
224
+ { idempotencyKey: this.idempotencyKeyFromParams(params) }
225
+ ).then((upload) => this.normalizeAudioUpload(upload)),
226
+ /**
227
+ * Create a signed audio upload URL and PUT the provided bytes to it.
228
+ *
229
+ * The returned `read_url` can be passed to `videos.createFromAudio()`.
230
+ */
231
+ uploadAudio: async (params) => {
232
+ const upload = await this.uploads.createAudioUrl(params);
233
+ const controller = new AbortController();
234
+ const timer = setTimeout(() => controller.abort(), this.timeout);
235
+ try {
236
+ const res = await this.fetchFn(upload.upload_url, {
237
+ method: upload.method,
238
+ headers: upload.headers,
239
+ body: params.body,
240
+ signal: controller.signal
241
+ });
242
+ if (!res.ok) {
243
+ throw new PuppetryError(
244
+ res.status,
245
+ "upload_failed",
246
+ res.statusText || "Failed to upload audio bytes"
247
+ );
248
+ }
249
+ } catch (err) {
250
+ if (err instanceof PuppetryError) throw err;
251
+ if (this.isAbortError(err)) {
252
+ throw new PuppetryTimeoutError(this.timeout);
253
+ }
254
+ if (this.isNetworkError(err)) {
255
+ throw new PuppetryNetworkError(this.transportErrorMessage(err));
256
+ }
257
+ throw err;
258
+ } finally {
259
+ clearTimeout(timer);
260
+ }
261
+ return this.normalizeAudioUpload(upload);
262
+ }
263
+ };
264
+ // ── Puppets ────────────────────────────────────────────────────────
265
+ this.puppets = {
266
+ list: (params) => {
267
+ const query = new URLSearchParams();
268
+ if (params?.cursor) query.set("cursor", params.cursor);
269
+ if (params?.limit) query.set("limit", String(params.limit));
270
+ const qs = query.toString();
271
+ return this.get(
272
+ `/puppets${qs ? "?" + qs : ""}`
273
+ );
274
+ },
275
+ create: (params) => this.post(
276
+ "/puppets",
277
+ params
278
+ ),
279
+ delete: (puppetId) => this.delete(`/puppets/${encodeURIComponent(puppetId)}`)
280
+ };
281
+ // ── Usage ──────────────────────────────────────────────────────────
282
+ this.usage = {
283
+ get: async () => this.normalizeUsage(await this.get("/usage"))
284
+ };
285
+ // ── Quota ──────────────────────────────────────────────────────────
286
+ this.quota = {
287
+ get: () => this.usage.get()
288
+ };
289
+ const apiKey = config.apiKey.trim();
290
+ if (!apiKey) {
291
+ throw new PuppetryAuthError("apiKey is required");
292
+ }
293
+ this.apiKey = apiKey;
294
+ this.baseUrl = (config.baseUrl || DEFAULT_BASE_URL).replace(/\/+$/, "");
295
+ this.timeout = config.timeout || DEFAULT_TIMEOUT;
296
+ this.maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;
297
+ this.retryDelayMs = config.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS;
298
+ this.onRetry = config.onRetry;
299
+ this.fetchFn = config.fetch || globalThis.fetch;
300
+ }
301
+ // ── HTTP Layer ─────────────────────────────────────────────────────
302
+ async requestWithResponse(method, path, body, options) {
303
+ const url = `${this.baseUrl}${path}`;
304
+ const requestBody = body ? JSON.stringify(body) : void 0;
305
+ const headers = {
306
+ Authorization: `Bearer ${this.apiKey}`,
307
+ "Content-Type": "application/json",
308
+ "User-Agent": `@puppetry.com/sdk/${SDK_VERSION}`
309
+ };
310
+ const idempotencyKey = this.normalizeIdempotencyKey(
311
+ options?.idempotencyKey
312
+ );
313
+ if (idempotencyKey) {
314
+ headers["Idempotency-Key"] = idempotencyKey;
315
+ }
316
+ for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
317
+ const controller = new AbortController();
318
+ const timer = setTimeout(() => controller.abort(), this.timeout);
319
+ try {
320
+ const res = await this.fetchFn(url, {
321
+ method,
322
+ headers,
323
+ body: requestBody,
324
+ signal: controller.signal
325
+ });
326
+ if (!res.ok) {
327
+ const errorBody = await res.json().catch(() => ({
328
+ error: "unknown",
329
+ message: res.statusText
330
+ }));
331
+ const responseHeaders = this.responseHeaders(res);
332
+ const retryAfter = this.responseRetryAfter(res.headers, errorBody);
333
+ const errorOptions = {
334
+ ...this.videoCreditErrorOptions(responseHeaders),
335
+ ...this.videoStatusErrorOptions(
336
+ method,
337
+ path,
338
+ responseHeaders,
339
+ retryAfter
340
+ )
341
+ };
342
+ const isVideoStatusRetry = this.isRetryableVideoStatusError(
343
+ method,
344
+ path,
345
+ errorBody
346
+ );
347
+ if (!isVideoStatusRetry && this.shouldRetry(method, res.status, Boolean(idempotencyKey)) && attempt < this.maxRetries) {
348
+ const delayMs = this.retryDelay(attempt, retryAfter);
349
+ const retryError = res.status === 429 ? PuppetryRateLimitError.fromRateLimitResponse(errorBody, {
350
+ ...errorOptions
351
+ }) : PuppetryError.fromResponse(
352
+ res.status,
353
+ errorBody,
354
+ errorOptions
355
+ );
356
+ await this.notifyRetry(
357
+ this.requestRetryEvent(
358
+ method,
359
+ path,
360
+ url,
361
+ idempotencyKey,
362
+ attempt,
363
+ delayMs,
364
+ retryError
365
+ )
366
+ );
367
+ await this.sleep(delayMs);
368
+ continue;
369
+ }
370
+ if (res.status === 401)
371
+ throw new PuppetryAuthError(errorBody.message);
372
+ if (res.status === 429) {
373
+ throw PuppetryRateLimitError.fromRateLimitResponse(errorBody, {
374
+ ...errorOptions
375
+ });
376
+ }
377
+ throw PuppetryError.fromResponse(res.status, errorBody, errorOptions);
378
+ }
379
+ if (res.status === 204) {
380
+ return { data: void 0, headers: this.responseHeaders(res) };
381
+ }
382
+ return {
383
+ data: await res.json(),
384
+ headers: this.responseHeaders(res)
385
+ };
386
+ } catch (err) {
387
+ if (err instanceof PuppetryError) throw err;
388
+ if (this.isAbortError(err)) {
389
+ if (this.shouldRetryTransport(method, Boolean(idempotencyKey), attempt)) {
390
+ const delayMs = this.retryDelay(attempt);
391
+ await this.notifyRetry(
392
+ this.requestRetryEvent(
393
+ method,
394
+ path,
395
+ url,
396
+ idempotencyKey,
397
+ attempt,
398
+ delayMs,
399
+ new PuppetryTimeoutError(this.timeout)
400
+ )
401
+ );
402
+ await this.sleep(delayMs);
403
+ continue;
404
+ }
405
+ throw new PuppetryTimeoutError(this.timeout);
406
+ }
407
+ if (this.isNetworkError(err)) {
408
+ if (this.shouldRetryTransport(method, Boolean(idempotencyKey), attempt)) {
409
+ const delayMs = this.retryDelay(attempt);
410
+ await this.notifyRetry(
411
+ this.requestRetryEvent(
412
+ method,
413
+ path,
414
+ url,
415
+ idempotencyKey,
416
+ attempt,
417
+ delayMs,
418
+ new PuppetryNetworkError(this.transportErrorMessage(err))
419
+ )
420
+ );
421
+ await this.sleep(delayMs);
422
+ continue;
423
+ }
424
+ throw new PuppetryNetworkError(this.transportErrorMessage(err));
425
+ }
426
+ throw err;
427
+ } finally {
428
+ clearTimeout(timer);
429
+ }
430
+ }
431
+ throw new PuppetryError(
432
+ 500,
433
+ "retry_exhausted",
434
+ "Request retries exhausted"
435
+ );
436
+ }
437
+ requestRetryEvent(method, path, url, idempotencyKey, attempt, delayMs, err) {
438
+ return {
439
+ method,
440
+ path,
441
+ url,
442
+ ...idempotencyKey ? {
443
+ idempotency_key: idempotencyKey,
444
+ idempotencyKey
445
+ } : {},
446
+ attempt: attempt + 1,
447
+ maxRetries: this.maxRetries,
448
+ delayMs,
449
+ ...puppetryErrorData(err)
450
+ };
451
+ }
452
+ async notifyRetry(event) {
453
+ if (!this.onRetry) return;
454
+ try {
455
+ await this.onRetry(event);
456
+ } catch {
457
+ }
458
+ }
459
+ async request(method, path, body, options) {
460
+ return (await this.requestWithResponse(method, path, body, options)).data;
461
+ }
462
+ get(path) {
463
+ return this.request("GET", path);
464
+ }
465
+ getWithResponse(path) {
466
+ return this.requestWithResponse("GET", path);
467
+ }
468
+ post(path, body, options) {
469
+ return this.request("POST", path, body, options);
470
+ }
471
+ delete(path) {
472
+ return this.request("DELETE", path);
473
+ }
474
+ shouldRetry(method, status, hasIdempotencyKey) {
475
+ if (status !== 429 && status < 500) return false;
476
+ return this.isRetrySafeRequest(method, hasIdempotencyKey);
477
+ }
478
+ isRetryableVideoStatusError(method, path, body) {
479
+ if (!this.isVideoStatusRequest(method, path)) return false;
480
+ return body.retryable === true || body.details?.retryable === true;
481
+ }
482
+ isVideoStatusRequest(method, path) {
483
+ if (method.toUpperCase() !== "GET") return false;
484
+ const pathname = path.split("?")[0];
485
+ return /^\/videos\/[^/]+\/?$/.test(pathname);
486
+ }
487
+ shouldRetryTransport(method, hasIdempotencyKey, attempt) {
488
+ return attempt < this.maxRetries && this.isRetrySafeRequest(method, hasIdempotencyKey);
489
+ }
490
+ isRetrySafeRequest(method, hasIdempotencyKey) {
491
+ const normalizedMethod = method.toUpperCase();
492
+ if (normalizedMethod === "GET" || normalizedMethod === "HEAD" || normalizedMethod === "OPTIONS") {
493
+ return true;
494
+ }
495
+ return hasIdempotencyKey;
496
+ }
497
+ isAbortError(err) {
498
+ return typeof err === "object" && err !== null && "name" in err && err.name === "AbortError";
499
+ }
500
+ isNetworkError(err) {
501
+ return err instanceof TypeError;
502
+ }
503
+ transportErrorMessage(err) {
504
+ if (err instanceof Error && err.message) return err.message;
505
+ return "Network request failed";
506
+ }
507
+ parseRetryAfter(value) {
508
+ if (!value) return void 0;
509
+ const trimmedValue = value.trim();
510
+ if (/^\d+$/.test(trimmedValue)) {
511
+ const seconds = Number(trimmedValue);
512
+ return Number.isSafeInteger(seconds) ? seconds : void 0;
513
+ }
514
+ const retryAtMs = Date.parse(trimmedValue);
515
+ if (!Number.isFinite(retryAtMs)) return void 0;
516
+ return Math.max(0, Math.ceil((retryAtMs - Date.now()) / 1e3));
517
+ }
518
+ responseRetryAfter(headers, body) {
519
+ return this.parseRetryAfter(this.readHeader(headers, "Retry-After")) ?? this.retryAfterFromErrorBody(body) ?? this.retryAfterFromDetails(body.details);
520
+ }
521
+ retryAfterFromErrorBody(body) {
522
+ return this.normalizeRetryAfterSeconds(
523
+ body.retry_after_seconds ?? body.retryAfterSeconds ?? body.retryAfter
524
+ );
525
+ }
526
+ retryAfterFromDetails(details) {
527
+ const directRetryAfter = this.normalizeRetryAfterSeconds(
528
+ details?.retry_after_seconds ?? details?.retryAfterSeconds ?? details?.retryAfter
529
+ );
530
+ if (directRetryAfter !== void 0) return directRetryAfter;
531
+ const nestedReadiness = details && typeof details === "object" ? details.video_generation ?? details.videoGeneration : void 0;
532
+ if (!nestedReadiness || typeof nestedReadiness !== "object") {
533
+ return void 0;
534
+ }
535
+ const readiness = nestedReadiness;
536
+ return this.normalizeRetryAfterSeconds(
537
+ readiness.retry_after_seconds ?? readiness.retryAfterSeconds ?? readiness.retryAfter
538
+ );
539
+ }
540
+ normalizeRetryAfterSeconds(value) {
541
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
542
+ return void 0;
543
+ }
544
+ return value;
545
+ }
546
+ retryDelay(attempt, retryAfterSeconds) {
547
+ if (retryAfterSeconds !== void 0) {
548
+ return retryAfterSeconds * 1e3;
549
+ }
550
+ return this.retryDelayMs * 2 ** attempt;
551
+ }
552
+ sleep(ms) {
553
+ if (ms <= 0) return Promise.resolve();
554
+ return new Promise((resolve) => setTimeout(resolve, ms));
555
+ }
556
+ responseHeaders(response) {
557
+ return response.headers ?? EMPTY_HEADERS;
558
+ }
559
+ readHeader(headers, name) {
560
+ const reader = headers;
561
+ return reader?.get?.(name) ?? reader?.get?.(name.toLowerCase()) ?? null;
562
+ }
563
+ readResponseHeader(headers, name) {
564
+ return headers?.get?.(name) ?? headers?.get?.(name.toLowerCase()) ?? null;
565
+ }
566
+ compactBody(body) {
567
+ return Object.fromEntries(
568
+ Object.entries(body).filter(([, value]) => value !== void 0)
569
+ );
570
+ }
571
+ normalizeIdempotencyKey(value) {
572
+ if (value === void 0) return void 0;
573
+ const key = value.trim();
574
+ if (!key) {
575
+ throw new PuppetryError(
576
+ 400,
577
+ "invalid_request",
578
+ "Idempotency-Key must not be blank"
579
+ );
580
+ }
581
+ if (key.length > IDEMPOTENCY_KEY_MAX_LENGTH) {
582
+ throw new PuppetryError(
583
+ 400,
584
+ "invalid_request",
585
+ `Idempotency-Key must be ${IDEMPOTENCY_KEY_MAX_LENGTH} characters or fewer`
586
+ );
587
+ }
588
+ return key;
589
+ }
590
+ idempotencyKeyFromParams(params) {
591
+ const aliases = [
592
+ { name: "idempotencyKey", value: params.idempotencyKey },
593
+ { name: "idempotency_key", value: params.idempotency_key }
594
+ ];
595
+ const provided = aliases.filter((alias) => alias.value !== void 0 && alias.value !== null).map((alias) => {
596
+ if (typeof alias.value !== "string") {
597
+ throw new PuppetryError(
598
+ 400,
599
+ "invalid_request",
600
+ `${alias.name} must be a string`
601
+ );
602
+ }
603
+ return { ...alias, value: alias.value };
604
+ });
605
+ const first = provided[0];
606
+ if (!first) return void 0;
607
+ const conflictingAlias = provided.find(
608
+ (alias) => alias.value !== first.value
609
+ );
610
+ if (conflictingAlias) {
611
+ throw new PuppetryError(
612
+ 400,
613
+ "invalid_request",
614
+ `Pass one of ${aliases.map((alias) => alias.name).join(", ")}, not conflicting values`
615
+ );
616
+ }
617
+ return first.value;
618
+ }
619
+ resolveAlias(preferred, alias, preferredName, aliasName) {
620
+ if (preferred && alias && preferred !== alias) {
621
+ throw new PuppetryError(
622
+ 400,
623
+ "invalid_request",
624
+ `Pass either ${preferredName} or ${aliasName}, not conflicting values`
625
+ );
626
+ }
627
+ return preferred ?? alias;
628
+ }
629
+ resolveRequiredStringAliases(aliases) {
630
+ const provided = aliases.flatMap((alias) => {
631
+ if (alias.value === void 0 || alias.value === null) return [];
632
+ if (typeof alias.value !== "string") {
633
+ throw new PuppetryError(
634
+ 400,
635
+ "invalid_request",
636
+ `${alias.name} must be a string`
637
+ );
638
+ }
639
+ const value = alias.value.trim();
640
+ if (!value) {
641
+ throw new PuppetryError(
642
+ 400,
643
+ "invalid_request",
644
+ `${alias.name} must not be blank`
645
+ );
646
+ }
647
+ return [{ name: alias.name, value }];
648
+ });
649
+ const first = provided[0];
650
+ if (!first) return void 0;
651
+ const conflictingAlias = provided.find(
652
+ (alias) => alias.value !== first.value
653
+ );
654
+ if (conflictingAlias) {
655
+ throw new PuppetryError(
656
+ 400,
657
+ "invalid_request",
658
+ `Pass one of ${aliases.map((alias) => alias.name).join(", ")}, not conflicting values`
659
+ );
660
+ }
661
+ return first.value;
662
+ }
663
+ resolveOptionalStringAliases(aliases) {
664
+ const provided = aliases.flatMap((alias) => {
665
+ if (alias.value === void 0 || alias.value === null) return [];
666
+ if (typeof alias.value !== "string") {
667
+ throw new PuppetryError(
668
+ 400,
669
+ "invalid_request",
670
+ `${alias.name} must be a string`
671
+ );
672
+ }
673
+ const value = alias.value.trim();
674
+ return value ? [{ name: alias.name, value }] : [];
675
+ });
676
+ const first = provided[0];
677
+ if (!first) return void 0;
678
+ const conflictingAlias = provided.find(
679
+ (alias) => alias.value !== first.value
680
+ );
681
+ if (conflictingAlias) {
682
+ throw new PuppetryError(
683
+ 400,
684
+ "invalid_request",
685
+ `Pass one of ${aliases.map((alias) => alias.name).join(", ")}, not conflicting values`
686
+ );
687
+ }
688
+ return first.value;
689
+ }
690
+ resolveUploadStringAliases(aliases) {
691
+ const provided = aliases.flatMap((alias) => {
692
+ if (alias.value === void 0 || alias.value === null) return [];
693
+ if (typeof alias.value !== "string") {
694
+ throw new PuppetryError(
695
+ 400,
696
+ "invalid_request",
697
+ `${alias.name} must be a string`
698
+ );
699
+ }
700
+ const value = alias.value.trim();
701
+ return value ? [{ name: alias.name, value }] : [];
702
+ });
703
+ const first = provided[0];
704
+ if (!first) return void 0;
705
+ const conflictingAlias = provided.find(
706
+ (alias) => alias.value !== first.value
707
+ );
708
+ if (conflictingAlias) {
709
+ throw new PuppetryError(
710
+ 400,
711
+ "invalid_request",
712
+ `Pass one of ${aliases.map((alias) => alias.name).join(", ")}, not conflicting values`
713
+ );
714
+ }
715
+ return first.value;
716
+ }
717
+ resolveNumberAliases(aliases) {
718
+ const provided = aliases.flatMap((alias) => {
719
+ if (alias.value === void 0 || alias.value === null) return [];
720
+ if (typeof alias.value === "string" && alias.value.trim() === "") {
721
+ return [];
722
+ }
723
+ if (typeof alias.value !== "number") {
724
+ throw new PuppetryError(
725
+ 400,
726
+ "invalid_request",
727
+ `${alias.name} must be a number`
728
+ );
729
+ }
730
+ return [{ name: alias.name, value: alias.value }];
731
+ });
732
+ const first = provided[0];
733
+ if (!first) return void 0;
734
+ const conflictingAlias = provided.find(
735
+ (alias) => alias.value !== first.value
736
+ );
737
+ if (conflictingAlias) {
738
+ throw new PuppetryError(
739
+ 400,
740
+ "invalid_request",
741
+ `Pass one of ${aliases.map((alias) => alias.name).join(", ")}, not conflicting values`
742
+ );
743
+ }
744
+ return first.value;
745
+ }
746
+ firstDefinedNullable(...values) {
747
+ for (const value of values) {
748
+ if (value !== void 0 && value !== null) return value;
749
+ }
750
+ return values.some((value) => value === null) ? null : void 0;
751
+ }
752
+ textVideoBody(params) {
753
+ return this.compactBody({
754
+ image_url: this.resolveRequiredStringAliases([
755
+ { name: "image_url", value: params.image_url },
756
+ { name: "imageUrl", value: params.imageUrl },
757
+ { name: "image", value: params.image },
758
+ { name: "portrait", value: params.portrait },
759
+ { name: "portrait_url", value: params.portrait_url },
760
+ { name: "portraitUrl", value: params.portraitUrl },
761
+ { name: "avatar", value: params.avatar },
762
+ { name: "avatarUrl", value: params.avatarUrl },
763
+ { name: "avatar_url", value: params.avatar_url },
764
+ { name: "photo", value: params.photo },
765
+ { name: "photoUrl", value: params.photoUrl },
766
+ { name: "photo_url", value: params.photo_url }
767
+ ]),
768
+ text: this.resolveRequiredStringAliases([
769
+ { name: "text", value: params.text },
770
+ { name: "script", value: params.script },
771
+ { name: "prompt", value: params.prompt }
772
+ ]),
773
+ voice_id: this.resolveOptionalStringAliases([
774
+ { name: "voice_id", value: params.voice_id },
775
+ { name: "voiceId", value: params.voiceId },
776
+ { name: "voice", value: params.voice }
777
+ ]),
778
+ language: params.language,
779
+ expressiveness: params.expressiveness
780
+ });
781
+ }
782
+ audioVideoBody(params) {
783
+ return this.compactBody({
784
+ image_url: this.resolveRequiredStringAliases([
785
+ { name: "image_url", value: params.image_url },
786
+ { name: "imageUrl", value: params.imageUrl },
787
+ { name: "image", value: params.image },
788
+ { name: "portrait", value: params.portrait },
789
+ { name: "portrait_url", value: params.portrait_url },
790
+ { name: "portraitUrl", value: params.portraitUrl },
791
+ { name: "avatar", value: params.avatar },
792
+ { name: "avatarUrl", value: params.avatarUrl },
793
+ { name: "avatar_url", value: params.avatar_url },
794
+ { name: "photo", value: params.photo },
795
+ { name: "photoUrl", value: params.photoUrl },
796
+ { name: "photo_url", value: params.photo_url }
797
+ ]),
798
+ audio_url: this.resolveRequiredStringAliases([
799
+ { name: "audio_url", value: params.audio_url },
800
+ { name: "audioUrl", value: params.audioUrl },
801
+ { name: "audio", value: params.audio }
802
+ ]),
803
+ expressiveness: params.expressiveness
804
+ });
805
+ }
806
+ videoJobPath(jobId) {
807
+ return `/videos/${encodeURIComponent(jobId)}`;
808
+ }
809
+ normalizeVideoJob(job, headers) {
810
+ const jobId = job.id ?? job.jobId ?? job.job_id ?? job.task_id ?? job.taskId;
811
+ if (!jobId) {
812
+ throw new PuppetryError(
813
+ 502,
814
+ "invalid_response",
815
+ "Puppetry API returned a video job without an id"
816
+ );
817
+ }
818
+ const retryAfter = job.retryAfter ?? job.retryAfterSeconds ?? job.retry_after_seconds ?? this.parseRetryAfter(this.readResponseHeader(headers, "Retry-After"));
819
+ const nextPollAt = job.nextPollAt ?? job.next_poll_at ?? this.videoNextPollAtHeader(headers);
820
+ const videoUrl = this.firstDefinedNullable(
821
+ job.videoUrl,
822
+ job.video_url,
823
+ job.resultUrl,
824
+ job.result_url,
825
+ job.downloadUrl,
826
+ job.download_url,
827
+ job.outputUrl,
828
+ job.output_url,
829
+ job.url
830
+ );
831
+ const durationSeconds = this.firstDefinedNullable(
832
+ job.durationSeconds,
833
+ job.duration_seconds,
834
+ job.duration
835
+ );
836
+ const retryable = typeof job.retryable === "boolean" ? job.retryable : void 0;
837
+ const retryBlockedReason = job.retryBlockedReason ?? job.retry_blocked_reason;
838
+ const retryBlockCode = job.retryBlockCode ?? job.retry_block_code;
839
+ const diagnostics = job.diagnostics ?? this.videoJobResultDiagnostics(job.result);
840
+ const createdAt = job.createdAt ?? job.created_at;
841
+ const expiresAt = job.expiresAt ?? job.expires_at;
842
+ const completedAt = this.firstDefinedNullable(
843
+ job.completedAt,
844
+ job.completed_at
845
+ );
846
+ const thumbnailUrl = this.firstDefinedNullable(
847
+ job.thumbnailUrl,
848
+ job.thumbnail_url
849
+ );
850
+ const { statusUrl, contentLocation } = this.videoJobPollLocationAliases(job, headers);
851
+ const statusCredits = this.videoStatusCredits(job.credits);
852
+ const creationCredits = this.videoCreationCredits(job, headers);
853
+ const operationId = job.operationId ?? job.operation_id ?? this.videoOperationIdHeader(headers);
854
+ const requestId = job.requestId ?? job.request_id ?? this.videoRequestIdHeader(headers);
855
+ const idempotentReplay = job.idempotentReplay ?? job.idempotent_replay;
856
+ const source = normalizeVideoJobSource(job.source) ?? normalizeVideoJobSource(job.request_source) ?? normalizeVideoJobSource(job.requestSource);
857
+ const status = normalizeVideoStatusAlias(job.status) ?? normalizeVideoStatusAlias(job.state) ?? normalizeVideoStatusAlias(job.task_status) ?? normalizeVideoStatusAlias(job.taskStatus) ?? "processing";
858
+ const progress = this.videoProgress(job);
859
+ const {
860
+ credits: _credits,
861
+ creation_credits: _creationCredits,
862
+ creationCredits: _creationCreditsCamel,
863
+ ...jobWithoutCreditAliases
864
+ } = job;
865
+ return this.attachVideoJobHelpers({
866
+ ...jobWithoutCreditAliases,
867
+ id: jobId,
868
+ job_id: job.job_id ?? jobId,
869
+ jobId: job.jobId ?? jobId,
870
+ operation_id: job.operation_id ?? operationId ?? jobId,
871
+ operationId: job.operationId ?? operationId ?? jobId,
872
+ ...requestId !== void 0 ? { request_id: requestId, requestId } : {},
873
+ task_id: job.task_id ?? jobId,
874
+ taskId: job.taskId ?? jobId,
875
+ status,
876
+ state: normalizeVideoStatusAlias(job.state) ?? status,
877
+ task_status: normalizeVideoStatusAlias(job.task_status) ?? status,
878
+ taskStatus: normalizeVideoStatusAlias(job.taskStatus) ?? status,
879
+ ...source !== void 0 ? { source, request_source: source, requestSource: source } : {},
880
+ ...progress !== void 0 ? {
881
+ progress,
882
+ percent: job.percent ?? progress,
883
+ percentage: job.percentage ?? progress,
884
+ progress_percent: job.progress_percent ?? progress,
885
+ progressPercent: job.progressPercent ?? progress
886
+ } : {},
887
+ ...createdAt !== void 0 ? { created_at: createdAt, createdAt } : {},
888
+ ...expiresAt !== void 0 ? { expires_at: expiresAt, expiresAt } : {},
889
+ ...completedAt !== void 0 ? { completed_at: completedAt, completedAt } : {},
890
+ ...videoUrl !== void 0 ? {
891
+ url: videoUrl,
892
+ video_url: videoUrl,
893
+ videoUrl,
894
+ result_url: videoUrl,
895
+ resultUrl: videoUrl,
896
+ download_url: videoUrl,
897
+ downloadUrl: videoUrl,
898
+ output_url: videoUrl,
899
+ outputUrl: videoUrl
900
+ } : {},
901
+ ...thumbnailUrl !== void 0 ? { thumbnail_url: thumbnailUrl, thumbnailUrl } : {},
902
+ duration: job.duration ?? durationSeconds,
903
+ duration_seconds: job.duration_seconds ?? durationSeconds,
904
+ durationSeconds,
905
+ ...statusUrl !== void 0 ? { status_url: statusUrl, statusUrl } : {},
906
+ ...contentLocation !== void 0 ? { content_location: contentLocation, contentLocation } : {},
907
+ ...statusCredits !== void 0 ? { credits: statusCredits } : {},
908
+ ...creationCredits !== void 0 ? { creation_credits: creationCredits, creationCredits } : {},
909
+ ...idempotentReplay !== void 0 ? { idempotent_replay: idempotentReplay, idempotentReplay } : {},
910
+ ...retryable !== void 0 ? { retryable } : {},
911
+ ...retryBlockedReason ? {
912
+ retry_blocked_reason: retryBlockedReason,
913
+ retryBlockedReason
914
+ } : {},
915
+ ...retryBlockCode ? {
916
+ retry_block_code: retryBlockCode,
917
+ retryBlockCode
918
+ } : {},
919
+ ...diagnostics ? { diagnostics } : {},
920
+ ...retryAfter !== void 0 ? { retryAfter } : {},
921
+ ...retryAfter !== void 0 ? { retry_after_seconds: retryAfter } : {},
922
+ ...retryAfter !== void 0 ? { retryAfterSeconds: retryAfter } : {},
923
+ ...nextPollAt !== void 0 ? { next_poll_at: nextPollAt, nextPollAt } : {}
924
+ });
925
+ }
926
+ videoJobStatusUrlHeader(headers) {
927
+ return this.videoLocationHeader(headers) ?? this.videoContentLocationHeader(headers);
928
+ }
929
+ videoJobPollLocationAliases(job, headers) {
930
+ const bodyStatusUrl = job.statusUrl ?? job.status_url;
931
+ const bodyContentLocation = job.contentLocation ?? job.content_location;
932
+ const headerLocation = this.videoLocationHeader(headers);
933
+ const headerContentLocation = this.videoContentLocationHeader(headers);
934
+ return {
935
+ statusUrl: bodyStatusUrl ?? bodyContentLocation ?? headerLocation ?? headerContentLocation,
936
+ contentLocation: bodyContentLocation ?? bodyStatusUrl ?? headerContentLocation ?? headerLocation
937
+ };
938
+ }
939
+ videoLocationHeader(headers) {
940
+ const value = headers?.get("Location") ?? headers?.get("location");
941
+ if (!value) return void 0;
942
+ const trimmed = value.trim();
943
+ return trimmed ? trimmed : void 0;
944
+ }
945
+ videoContentLocationHeader(headers) {
946
+ const value = headers?.get("Content-Location") ?? headers?.get("content-location");
947
+ if (!value) return void 0;
948
+ const trimmed = value.trim();
949
+ return trimmed ? trimmed : void 0;
950
+ }
951
+ videoOperationIdHeader(headers) {
952
+ const value = headers?.get("Puppetry-Operation-Id") ?? headers?.get("puppetry-operation-id") ?? headers?.get("X-Puppetry-Operation-Id") ?? headers?.get("x-puppetry-operation-id");
953
+ if (!value) return void 0;
954
+ const trimmed = value.trim();
955
+ return trimmed ? trimmed : void 0;
956
+ }
957
+ videoRequestIdHeader(headers) {
958
+ const value = headers?.get("Puppetry-Request-Id") ?? headers?.get("puppetry-request-id") ?? headers?.get("X-Puppetry-Request-Id") ?? headers?.get("x-puppetry-request-id");
959
+ if (!value) return void 0;
960
+ const trimmed = value.trim();
961
+ return trimmed ? trimmed : void 0;
962
+ }
963
+ videoNextPollAtHeader(headers) {
964
+ const value = headers?.get("X-Puppetry-Next-Poll-At") ?? headers?.get("x-puppetry-next-poll-at");
965
+ if (!value) return void 0;
966
+ const trimmed = value.trim();
967
+ return trimmed ? trimmed : void 0;
968
+ }
969
+ videoStatusErrorOptions(method, path, headers, retryAfter) {
970
+ const statusUrl = this.videoJobStatusUrlHeader(headers);
971
+ const contentLocation = this.videoContentLocationHeader(headers);
972
+ const jobId = this.videoJobIdFromStatusUrl(statusUrl ?? contentLocation);
973
+ const operationId = this.videoOperationIdHeader(headers);
974
+ const requestId = this.videoRequestIdHeader(headers);
975
+ const nextPollAt = this.videoNextPollAtHeader(headers);
976
+ const creationCredits = this.isVideoStatusRequest(method, path) ? this.videoCreationCreditsFromHeaders(headers) : void 0;
977
+ return {
978
+ retryAfter,
979
+ ...jobId ? { id: jobId, job_id: jobId, jobId } : {},
980
+ ...statusUrl ? { status_url: statusUrl, statusUrl } : {},
981
+ ...contentLocation ? { content_location: contentLocation, contentLocation } : {},
982
+ ...operationId ? { operation_id: operationId, operationId } : {},
983
+ ...requestId ? { request_id: requestId, requestId } : {},
984
+ ...nextPollAt ? { next_poll_at: nextPollAt, nextPollAt } : {},
985
+ ...creationCredits ? { creation_credits: creationCredits, creationCredits } : {}
986
+ };
987
+ }
988
+ videoCreditErrorOptions(headers) {
989
+ const requiredCredits = this.videoCreditHeaderNumber(
990
+ headers,
991
+ "X-Puppetry-Credits-Required"
992
+ );
993
+ const creditsRemaining = this.videoCreditHeaderNumber(
994
+ headers,
995
+ "X-Puppetry-Credits-Remaining"
996
+ );
997
+ return {
998
+ ...requiredCredits !== void 0 ? { required_credits: requiredCredits, requiredCredits } : {},
999
+ ...creditsRemaining !== void 0 ? {
1000
+ credits_remaining: creditsRemaining,
1001
+ creditsRemaining,
1002
+ remaining_credits: creditsRemaining,
1003
+ remainingCredits: creditsRemaining
1004
+ } : {}
1005
+ };
1006
+ }
1007
+ videoCreditHeaderNumber(headers, name) {
1008
+ const raw = this.readResponseHeader(headers, name);
1009
+ if (raw === null) return void 0;
1010
+ const trimmed = raw.trim();
1011
+ if (!/^\d+$/.test(trimmed)) return void 0;
1012
+ const value = Number(trimmed);
1013
+ return Number.isSafeInteger(value) ? value : void 0;
1014
+ }
1015
+ videoJobIdFromStatusUrl(statusUrl) {
1016
+ if (!statusUrl) return void 0;
1017
+ try {
1018
+ const url = new URL(statusUrl, this.baseUrl);
1019
+ const match = url.pathname.match(/\/videos\/([^/]+)\/?$/);
1020
+ if (!match) return void 0;
1021
+ return decodeURIComponent(match[1]);
1022
+ } catch {
1023
+ return void 0;
1024
+ }
1025
+ }
1026
+ retryableStatusErrorJobId(err) {
1027
+ return err.jobId ?? this.videoJobIdFromStatusUrl(err.statusUrl ?? err.status_url);
1028
+ }
1029
+ videoJobResultDiagnostics(result) {
1030
+ if (!result || typeof result !== "object" || Array.isArray(result)) {
1031
+ return void 0;
1032
+ }
1033
+ return result.diagnostics;
1034
+ }
1035
+ videoStatusCredits(credits) {
1036
+ if (!credits || typeof credits !== "object") return void 0;
1037
+ if (typeof credits.charged !== "number" || !Number.isFinite(credits.charged)) {
1038
+ return void 0;
1039
+ }
1040
+ return {
1041
+ charged: credits.charged,
1042
+ remaining: typeof credits.remaining === "number" && Number.isFinite(credits.remaining) ? credits.remaining : null
1043
+ };
1044
+ }
1045
+ videoCreationCredits(job, headers) {
1046
+ const raw = job.creationCredits ?? job.creation_credits;
1047
+ if (raw && typeof raw === "object") {
1048
+ if (typeof raw.charged !== "number" || !Number.isFinite(raw.charged)) {
1049
+ return this.videoCreationCreditsFromHeaders(headers);
1050
+ }
1051
+ const remainingAfterDebit = raw.remainingAfterDebit ?? raw.remaining_after_debit ?? null;
1052
+ const normalizedRemainingAfterDebit = typeof remainingAfterDebit === "number" && Number.isFinite(remainingAfterDebit) ? remainingAfterDebit : null;
1053
+ return {
1054
+ charged: raw.charged,
1055
+ remaining_after_debit: normalizedRemainingAfterDebit,
1056
+ remainingAfterDebit: normalizedRemainingAfterDebit
1057
+ };
1058
+ }
1059
+ return this.videoCreationCreditsFromHeaders(headers);
1060
+ }
1061
+ videoCreationCreditsFromHeaders(headers) {
1062
+ const charged = this.videoCreditHeaderNumber(
1063
+ headers,
1064
+ "X-Puppetry-Credits-Required"
1065
+ );
1066
+ if (charged === void 0) return void 0;
1067
+ const remainingAfterDebit = this.videoCreditHeaderNumber(
1068
+ headers,
1069
+ "X-Puppetry-Credits-Remaining"
1070
+ ) ?? null;
1071
+ return {
1072
+ charged,
1073
+ remaining_after_debit: remainingAfterDebit,
1074
+ remainingAfterDebit
1075
+ };
1076
+ }
1077
+ videoProgress(job) {
1078
+ const progress = job.progress ?? job.percent ?? job.percentage ?? job.progressPercent ?? job.progress_percent;
1079
+ return typeof progress === "number" || progress === null ? progress : void 0;
1080
+ }
1081
+ attachVideoJobHelpers(job) {
1082
+ const sdkJob = job;
1083
+ Object.defineProperty(sdkJob, "waitForCompletion", {
1084
+ value: (options) => {
1085
+ if (sdkJob.status === "completed" || sdkJob.status === "failed") {
1086
+ return Promise.resolve(sdkJob);
1087
+ }
1088
+ return this.videos.waitForCompletion(
1089
+ this.videoJobIdFromStatusUrl(sdkJob.statusUrl ?? sdkJob.status_url) ?? sdkJob.id,
1090
+ this.waitOptionsWithInitialDelay(
1091
+ options,
1092
+ sdkJob.retryAfter,
1093
+ sdkJob.nextPollAt ?? sdkJob.next_poll_at
1094
+ )
1095
+ );
1096
+ },
1097
+ enumerable: false,
1098
+ configurable: true
1099
+ });
1100
+ return sdkJob;
1101
+ }
1102
+ waitOptionsWithInitialDelay(options, retryAfterSeconds, nextPollAt) {
1103
+ if (options?.initialDelayMs !== void 0) return options;
1104
+ const initialDelayMs = this.retryAfterDelayMs(retryAfterSeconds) ?? this.nextPollDelayMs(nextPollAt);
1105
+ return initialDelayMs === void 0 ? options : { ...options, initialDelayMs };
1106
+ }
1107
+ retryAfterDelayMs(value) {
1108
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
1109
+ return void 0;
1110
+ }
1111
+ return value * 1e3;
1112
+ }
1113
+ optionDelayMs(value) {
1114
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
1115
+ return void 0;
1116
+ }
1117
+ return value;
1118
+ }
1119
+ nextPollDelayMs(value) {
1120
+ if (typeof value !== "string" || value.trim() === "") {
1121
+ return void 0;
1122
+ }
1123
+ const timestamp = Date.parse(value);
1124
+ if (!Number.isFinite(timestamp)) {
1125
+ return void 0;
1126
+ }
1127
+ return Math.max(timestamp - Date.now(), 0);
1128
+ }
1129
+ async sleepWithinDeadline(ms, deadline) {
1130
+ const remainingMs = Math.max(deadline - Date.now(), 0);
1131
+ await this.sleep(Math.min(ms, remainingMs));
1132
+ }
1133
+ pollingDelayMs(job, headers, intervalMs) {
1134
+ const retryAfterSeconds = this.parseRetryAfter(this.readResponseHeader(headers, "Retry-After")) ?? job.retryAfter;
1135
+ const retryAfterDelayMs = this.retryAfterDelayMs(retryAfterSeconds);
1136
+ if (retryAfterDelayMs !== void 0) {
1137
+ return retryAfterDelayMs;
1138
+ }
1139
+ return this.nextPollDelayMs(job.nextPollAt ?? job.next_poll_at) ?? intervalMs;
1140
+ }
1141
+ retryableStatusErrorDelayMs(err, intervalMs) {
1142
+ if (err.retryable !== true) return void 0;
1143
+ return this.retryAfterDelayMs(err.retryAfter) ?? this.nextPollDelayMs(err.nextPollAt ?? err.next_poll_at) ?? intervalMs;
1144
+ }
1145
+ normalizeUsage(usage) {
1146
+ const videoCredits = usage.video_credits ?? usage.videoCredits;
1147
+ if (!videoCredits) {
1148
+ return usage;
1149
+ }
1150
+ const rawCurrentApiKey = videoCredits.current_api_key ?? videoCredits.currentApiKey ?? {
1151
+ used: 0,
1152
+ refunded: 0,
1153
+ net_used: 0,
1154
+ transactions: 0
1155
+ };
1156
+ const currentApiKeyNetUsed = rawCurrentApiKey.net_used ?? rawCurrentApiKey.netUsed ?? 0;
1157
+ const currentApiKey = {
1158
+ ...rawCurrentApiKey,
1159
+ keyId: rawCurrentApiKey.keyId ?? rawCurrentApiKey.key_id,
1160
+ keyPrefix: rawCurrentApiKey.keyPrefix ?? rawCurrentApiKey.key_prefix,
1161
+ net_used: currentApiKeyNetUsed,
1162
+ netUsed: rawCurrentApiKey.netUsed ?? currentApiKeyNetUsed
1163
+ };
1164
+ const videoCreditNetUsed = videoCredits.net_used ?? videoCredits.netUsed ?? 0;
1165
+ const resetAt = videoCredits.reset_at ?? videoCredits.resetAt;
1166
+ const normalizedVideoCredits = {
1167
+ ...videoCredits,
1168
+ reset_at: resetAt,
1169
+ resetAt: videoCredits.resetAt ?? resetAt,
1170
+ net_used: videoCreditNetUsed,
1171
+ netUsed: videoCredits.netUsed ?? videoCreditNetUsed,
1172
+ current_api_key: currentApiKey,
1173
+ currentApiKey: videoCredits.currentApiKey ?? currentApiKey
1174
+ };
1175
+ const videoGeneration = this.normalizeVideoGenerationReadiness(
1176
+ usage.video_generation ?? usage.videoGeneration,
1177
+ videoCredits.balance
1178
+ );
1179
+ return {
1180
+ ...usage,
1181
+ video_credits: normalizedVideoCredits,
1182
+ videoCredits: usage.videoCredits ?? normalizedVideoCredits,
1183
+ video_generation: videoGeneration,
1184
+ videoGeneration: usage.videoGeneration ?? videoGeneration,
1185
+ plan: usage.plan ?? "developer_api",
1186
+ creditsTotal: usage.creditsTotal ?? Math.max(
1187
+ videoCredits.balance + videoCreditNetUsed,
1188
+ videoCredits.balance
1189
+ ),
1190
+ creditsUsed: usage.creditsUsed ?? videoCreditNetUsed,
1191
+ creditsRemaining: usage.creditsRemaining ?? videoCredits.balance,
1192
+ periodStart: usage.periodStart ?? `${videoCredits.month}-01`,
1193
+ periodEnd: usage.periodEnd ?? resetAt
1194
+ };
1195
+ }
1196
+ normalizeVideoGenerationReadiness(readiness, balance) {
1197
+ const requiredCredits = readiness?.required_credits ?? readiness?.requiredCredits ?? 1;
1198
+ const creditsRemaining = readiness?.credits_remaining ?? readiness?.creditsRemaining ?? balance;
1199
+ const concurrentJobs = readiness?.concurrent_jobs ?? readiness?.concurrentJobs;
1200
+ const activeJobs = readiness?.active_jobs ?? readiness?.activeJobs;
1201
+ const availableSlots = readiness?.available_slots ?? readiness?.availableSlots;
1202
+ const activeJobExpiresInSeconds = readiness?.active_job_expires_in_seconds ?? readiness?.activeJobExpiresInSeconds;
1203
+ const canCreate = readiness?.can_create ?? readiness?.canCreate ?? (creditsRemaining >= requiredCredits && (availableSlots === void 0 || availableSlots > 0));
1204
+ const reason = readiness?.reason ?? (creditsRemaining < requiredCredits ? "insufficient_video_credits" : availableSlots !== void 0 && availableSlots <= 0 ? "concurrent_job_limit_reached" : "available");
1205
+ const retryAfterSeconds = readiness?.retry_after_seconds ?? readiness?.retryAfterSeconds ?? readiness?.retryAfter ?? (reason === "concurrent_job_limit_reached" ? activeJobExpiresInSeconds : void 0);
1206
+ const nextPollAt = readiness?.nextPollAt ?? readiness?.next_poll_at;
1207
+ const blockers = this.normalizeVideoGenerationBlockers(
1208
+ readiness?.blockers,
1209
+ {
1210
+ reason,
1211
+ requiredCredits,
1212
+ creditsRemaining,
1213
+ concurrentJobs,
1214
+ activeJobs,
1215
+ availableSlots,
1216
+ activeJobExpiresInSeconds,
1217
+ retryAfterSeconds
1218
+ }
1219
+ );
1220
+ return {
1221
+ can_create: canCreate,
1222
+ canCreate: readiness?.canCreate ?? canCreate,
1223
+ required_credits: requiredCredits,
1224
+ requiredCredits: readiness?.requiredCredits ?? requiredCredits,
1225
+ credits_remaining: creditsRemaining,
1226
+ creditsRemaining: readiness?.creditsRemaining ?? creditsRemaining,
1227
+ ...concurrentJobs === void 0 ? {} : {
1228
+ concurrent_jobs: concurrentJobs,
1229
+ concurrentJobs: readiness?.concurrentJobs ?? concurrentJobs
1230
+ },
1231
+ ...activeJobs === void 0 ? {} : {
1232
+ active_jobs: activeJobs,
1233
+ activeJobs: readiness?.activeJobs ?? activeJobs
1234
+ },
1235
+ ...availableSlots === void 0 ? {} : {
1236
+ available_slots: availableSlots,
1237
+ availableSlots: readiness?.availableSlots ?? availableSlots
1238
+ },
1239
+ ...activeJobExpiresInSeconds === void 0 ? {} : {
1240
+ active_job_expires_in_seconds: activeJobExpiresInSeconds,
1241
+ activeJobExpiresInSeconds: readiness?.activeJobExpiresInSeconds ?? activeJobExpiresInSeconds
1242
+ },
1243
+ ...retryAfterSeconds === void 0 ? {} : {
1244
+ retry_after_seconds: retryAfterSeconds,
1245
+ retryAfter: readiness?.retryAfter ?? retryAfterSeconds,
1246
+ retryAfterSeconds: readiness?.retryAfterSeconds ?? retryAfterSeconds
1247
+ },
1248
+ ...nextPollAt === void 0 ? {} : { next_poll_at: nextPollAt, nextPollAt },
1249
+ ...blockers.length === 0 ? {} : { blockers },
1250
+ reason
1251
+ };
1252
+ }
1253
+ normalizeVideoGenerationBlockers(blockers, fallback) {
1254
+ if (Array.isArray(blockers) && blockers.length > 0) {
1255
+ return blockers.map(
1256
+ (blocker) => this.normalizeVideoGenerationBlocker(blocker)
1257
+ );
1258
+ }
1259
+ return this.derivedVideoGenerationBlockers(fallback);
1260
+ }
1261
+ normalizeVideoGenerationBlocker(blocker) {
1262
+ const record = blocker;
1263
+ const code = this.videoGenerationReadinessReason(record.code) ?? this.videoGenerationReadinessReason(record.reason);
1264
+ if (code === "insufficient_video_credits") {
1265
+ const requiredCredits = this.readinessNumber(record.required_credits) ?? this.readinessNumber(record.requiredCredits) ?? 1;
1266
+ const creditsRemaining = this.readinessNumber(record.credits_remaining) ?? this.readinessNumber(record.creditsRemaining) ?? 0;
1267
+ const retryBlockCode = this.readinessString(record.retry_block_code) ?? this.readinessString(record.retryBlockCode);
1268
+ return {
1269
+ code,
1270
+ reason: code,
1271
+ retryable: false,
1272
+ required_credits: requiredCredits,
1273
+ requiredCredits,
1274
+ credits_remaining: creditsRemaining,
1275
+ creditsRemaining,
1276
+ ...retryBlockCode === void 0 ? {} : {
1277
+ retry_block_code: retryBlockCode,
1278
+ retryBlockCode
1279
+ }
1280
+ };
1281
+ }
1282
+ const retryAfterSeconds = this.readinessNumber(record.retry_after_seconds) ?? this.readinessNumber(record.retryAfterSeconds) ?? this.readinessNumber(record.retryAfter);
1283
+ const nextPollAt = this.readinessString(record.nextPollAt) ?? this.readinessString(record.next_poll_at);
1284
+ return {
1285
+ code: "concurrent_job_limit_reached",
1286
+ reason: "concurrent_job_limit_reached",
1287
+ retryable: true,
1288
+ ...this.activeJobReadinessAliases(record),
1289
+ ...retryAfterSeconds === void 0 ? {} : {
1290
+ retry_after_seconds: retryAfterSeconds,
1291
+ retryAfter: retryAfterSeconds,
1292
+ retryAfterSeconds
1293
+ },
1294
+ ...nextPollAt === void 0 ? {} : { next_poll_at: nextPollAt, nextPollAt }
1295
+ };
1296
+ }
1297
+ derivedVideoGenerationBlockers(params) {
1298
+ const blockers = [];
1299
+ if (params.reason === "insufficient_video_credits" || params.creditsRemaining < params.requiredCredits) {
1300
+ blockers.push({
1301
+ code: "insufficient_video_credits",
1302
+ reason: "insufficient_video_credits",
1303
+ retryable: false,
1304
+ required_credits: params.requiredCredits,
1305
+ requiredCredits: params.requiredCredits,
1306
+ credits_remaining: params.creditsRemaining,
1307
+ creditsRemaining: params.creditsRemaining
1308
+ });
1309
+ }
1310
+ if (params.reason === "concurrent_job_limit_reached" || params.availableSlots !== void 0 && params.availableSlots <= 0) {
1311
+ blockers.push({
1312
+ code: "concurrent_job_limit_reached",
1313
+ reason: "concurrent_job_limit_reached",
1314
+ retryable: true,
1315
+ ...params.concurrentJobs === void 0 ? {} : {
1316
+ concurrent_jobs: params.concurrentJobs,
1317
+ concurrentJobs: params.concurrentJobs
1318
+ },
1319
+ ...params.activeJobs === void 0 ? {} : {
1320
+ active_jobs: params.activeJobs,
1321
+ activeJobs: params.activeJobs
1322
+ },
1323
+ ...params.availableSlots === void 0 ? {} : {
1324
+ available_slots: params.availableSlots,
1325
+ availableSlots: params.availableSlots
1326
+ },
1327
+ ...params.activeJobExpiresInSeconds === void 0 ? {} : {
1328
+ active_job_expires_in_seconds: params.activeJobExpiresInSeconds,
1329
+ activeJobExpiresInSeconds: params.activeJobExpiresInSeconds
1330
+ },
1331
+ ...params.retryAfterSeconds === void 0 ? {} : {
1332
+ retry_after_seconds: params.retryAfterSeconds,
1333
+ retryAfter: params.retryAfterSeconds,
1334
+ retryAfterSeconds: params.retryAfterSeconds
1335
+ }
1336
+ });
1337
+ }
1338
+ return blockers;
1339
+ }
1340
+ activeJobReadinessAliases(record) {
1341
+ const concurrentJobs = this.readinessNumber(record.concurrent_jobs) ?? this.readinessNumber(record.concurrentJobs);
1342
+ const activeJobs = this.readinessNumber(record.active_jobs) ?? this.readinessNumber(record.activeJobs);
1343
+ const availableSlots = this.readinessNumber(record.available_slots) ?? this.readinessNumber(record.availableSlots);
1344
+ const activeJobExpiresInSeconds = this.readinessNumber(record.active_job_expires_in_seconds) ?? this.readinessNumber(record.activeJobExpiresInSeconds);
1345
+ return {
1346
+ ...concurrentJobs === void 0 ? {} : { concurrent_jobs: concurrentJobs, concurrentJobs },
1347
+ ...activeJobs === void 0 ? {} : { active_jobs: activeJobs, activeJobs },
1348
+ ...availableSlots === void 0 ? {} : { available_slots: availableSlots, availableSlots },
1349
+ ...activeJobExpiresInSeconds === void 0 ? {} : {
1350
+ active_job_expires_in_seconds: activeJobExpiresInSeconds,
1351
+ activeJobExpiresInSeconds
1352
+ }
1353
+ };
1354
+ }
1355
+ videoGenerationReadinessReason(value) {
1356
+ return value === "available" || value === "insufficient_video_credits" || value === "concurrent_job_limit_reached" ? value : void 0;
1357
+ }
1358
+ readinessNumber(value) {
1359
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
1360
+ }
1361
+ readinessString(value) {
1362
+ return typeof value === "string" && value ? value : void 0;
1363
+ }
1364
+ async getVideoGenerationReadiness() {
1365
+ const usage = await this.usage.get();
1366
+ const readiness = usage.video_generation ?? usage.videoGeneration;
1367
+ if (!readiness) {
1368
+ throw new PuppetryError(
1369
+ 502,
1370
+ "invalid_response",
1371
+ "Puppetry API usage response did not include video_generation readiness"
1372
+ );
1373
+ }
1374
+ return readiness;
1375
+ }
1376
+ async ensureVideoGenerationReady() {
1377
+ const readiness = await this.getVideoGenerationReadiness();
1378
+ if (readiness.can_create !== false) return readiness;
1379
+ const retryAfter = readiness.retry_after_seconds ?? readiness.retryAfterSeconds ?? readiness.retryAfter ?? readiness.active_job_expires_in_seconds ?? readiness.activeJobExpiresInSeconds;
1380
+ const nextPollAt = readiness.nextPollAt ?? readiness.next_poll_at;
1381
+ const details = { video_generation: readiness };
1382
+ const errorOptions = this.videoGenerationReadinessErrorOptions(readiness);
1383
+ if (readiness.reason === "concurrent_job_limit_reached") {
1384
+ throw new PuppetryRateLimitError(
1385
+ retryAfter,
1386
+ "Video generation is at the active job limit",
1387
+ details,
1388
+ "rate_limited",
1389
+ {
1390
+ ...errorOptions,
1391
+ ...nextPollAt === void 0 ? {} : { next_poll_at: nextPollAt, nextPollAt }
1392
+ }
1393
+ );
1394
+ }
1395
+ throw new PuppetryError(
1396
+ 402,
1397
+ readiness.reason,
1398
+ "Video generation is not ready for this API key",
1399
+ details,
1400
+ {
1401
+ ...errorOptions,
1402
+ retryAfter,
1403
+ ...nextPollAt === void 0 ? {} : { next_poll_at: nextPollAt, nextPollAt }
1404
+ }
1405
+ );
1406
+ }
1407
+ videoGenerationReadinessErrorOptions(readiness) {
1408
+ const requiredCredits = readiness.required_credits ?? readiness.requiredCredits;
1409
+ const creditsRemaining = readiness.credits_remaining ?? readiness.creditsRemaining;
1410
+ const blocker = readiness.blockers?.find(
1411
+ (item) => item.reason === readiness.reason || item.code === readiness.reason
1412
+ );
1413
+ const retryBlockCode = readiness.reason === "insufficient_video_credits" ? (blocker && "retry_block_code" in blocker ? blocker.retry_block_code ?? blocker.retryBlockCode : void 0) ?? "INSUFFICIENT_VIDEO_CREDITS" : void 0;
1414
+ const retryBlockedReason = readiness.reason === "insufficient_video_credits" ? `Developer API video generation needs ${requiredCredits ?? 1} credit, but only ${creditsRemaining ?? 0} remain.` : readiness.reason;
1415
+ return {
1416
+ retryable: readiness.reason === "concurrent_job_limit_reached",
1417
+ ...requiredCredits === void 0 ? {} : { required_credits: requiredCredits, requiredCredits },
1418
+ ...creditsRemaining === void 0 ? {} : {
1419
+ credits_remaining: creditsRemaining,
1420
+ creditsRemaining,
1421
+ remaining_credits: creditsRemaining,
1422
+ remainingCredits: creditsRemaining
1423
+ },
1424
+ retry_blocked_reason: retryBlockedReason,
1425
+ retryBlockedReason,
1426
+ ...retryBlockCode === void 0 ? {} : { retry_block_code: retryBlockCode, retryBlockCode }
1427
+ };
1428
+ }
1429
+ normalizeAudioUploadLimits(limits) {
1430
+ const maxFileBytes = limits.max_file_bytes ?? limits.maxFileBytes;
1431
+ const monthlyUploads = limits.monthly_uploads ?? limits.monthlyUploads;
1432
+ const storageBytes = limits.storage_bytes ?? limits.storageBytes;
1433
+ const remainingMonthlyUploads = limits.remaining_monthly_uploads ?? limits.remainingMonthlyUploads;
1434
+ const remainingStorageBytes = limits.remaining_storage_bytes ?? limits.remainingStorageBytes;
1435
+ return {
1436
+ ...limits,
1437
+ max_file_bytes: maxFileBytes,
1438
+ maxFileBytes,
1439
+ monthly_uploads: monthlyUploads,
1440
+ monthlyUploads,
1441
+ storage_bytes: storageBytes,
1442
+ storageBytes,
1443
+ remaining_monthly_uploads: remainingMonthlyUploads,
1444
+ remainingMonthlyUploads,
1445
+ remaining_storage_bytes: remainingStorageBytes,
1446
+ remainingStorageBytes
1447
+ };
1448
+ }
1449
+ normalizeAudioUpload(upload) {
1450
+ const uploadUrl = upload.upload_url ?? upload.uploadUrl;
1451
+ const readUrl = upload.read_url ?? upload.readUrl;
1452
+ if (!uploadUrl || !readUrl) {
1453
+ throw new PuppetryError(
1454
+ 502,
1455
+ "invalid_response",
1456
+ "Puppetry API returned an upload response without upload/read URLs"
1457
+ );
1458
+ }
1459
+ const readUrlExpiresIn = upload.read_url_expires_in ?? upload.readUrlExpiresIn;
1460
+ const readUrlExpiresAt = upload.read_url_expires_at ?? upload.readUrlExpiresAt;
1461
+ const uploadUrlExpiresIn = upload.upload_url_expires_in ?? upload.uploadUrlExpiresIn;
1462
+ const idempotentReplay = upload.idempotent_replay ?? upload.idempotentReplay;
1463
+ return {
1464
+ ...upload,
1465
+ upload_url: uploadUrl,
1466
+ uploadUrl,
1467
+ read_url: readUrl,
1468
+ readUrl,
1469
+ read_url_expires_in: readUrlExpiresIn,
1470
+ readUrlExpiresIn,
1471
+ read_url_expires_at: readUrlExpiresAt,
1472
+ readUrlExpiresAt,
1473
+ upload_url_expires_in: uploadUrlExpiresIn,
1474
+ uploadUrlExpiresIn,
1475
+ idempotent_replay: idempotentReplay,
1476
+ idempotentReplay,
1477
+ limits: this.normalizeAudioUploadLimits(upload.limits)
1478
+ };
1479
+ }
1480
+ // ── Polled event helper ────────────────────────────────────────────
1481
+ videoJobEvent(job, headers) {
1482
+ const normalizedJob = this.normalizeVideoJob(job, headers);
1483
+ const progress = typeof normalizedJob.progress === "number" ? normalizedJob.progress : normalizedJob.status === "completed" ? 100 : void 0;
1484
+ const videoUrl = normalizedJob.videoUrl ?? void 0;
1485
+ const type = normalizedJob.status === "processing" && progress !== void 0 ? "progress" : normalizedJob.status;
1486
+ const statusUrl = normalizedJob.statusUrl ?? normalizedJob.status_url;
1487
+ const contentLocation = normalizedJob.contentLocation ?? normalizedJob.content_location;
1488
+ const operationId = job.operationId ?? job.operation_id ?? this.videoOperationIdHeader(headers);
1489
+ const requestId = job.requestId ?? job.request_id ?? this.videoRequestIdHeader(headers);
1490
+ const source = normalizeVideoJobSource(job.source) ?? normalizeVideoJobSource(job.request_source) ?? normalizeVideoJobSource(job.requestSource);
1491
+ const retryAfter = normalizedJob.retryAfter ?? normalizedJob.retryAfterSeconds ?? normalizedJob.retry_after_seconds;
1492
+ const nextPollAt = normalizedJob.nextPollAt ?? normalizedJob.next_poll_at;
1493
+ const creationCredits = this.videoCreationCredits(normalizedJob);
1494
+ return {
1495
+ type,
1496
+ id: normalizedJob.id,
1497
+ job_id: normalizedJob.job_id,
1498
+ jobId: normalizedJob.id,
1499
+ task_id: normalizedJob.task_id,
1500
+ taskId: normalizedJob.taskId,
1501
+ ...operationId ? { operation_id: operationId, operationId } : {},
1502
+ ...requestId ? { request_id: requestId, requestId } : {},
1503
+ ...source ? { source, request_source: source, requestSource: source } : {},
1504
+ ...statusUrl ? { status_url: statusUrl, statusUrl } : {},
1505
+ ...contentLocation ? { content_location: contentLocation, contentLocation } : {},
1506
+ ...progress !== void 0 ? { progress } : {},
1507
+ ...videoUrl ? { video_url: videoUrl, videoUrl } : {},
1508
+ ...normalizedJob.error ? { error: normalizedJob.error } : {},
1509
+ ...normalizedJob.retryable !== void 0 ? { retryable: normalizedJob.retryable } : {},
1510
+ ...normalizedJob.credits ? { credits: normalizedJob.credits } : {},
1511
+ ...creationCredits ? {
1512
+ creation_credits: creationCredits,
1513
+ creationCredits
1514
+ } : {},
1515
+ ...normalizedJob.retryBlockedReason ? {
1516
+ retry_blocked_reason: normalizedJob.retryBlockedReason,
1517
+ retryBlockedReason: normalizedJob.retryBlockedReason
1518
+ } : {},
1519
+ ...normalizedJob.retryBlockCode ? {
1520
+ retry_block_code: normalizedJob.retryBlockCode,
1521
+ retryBlockCode: normalizedJob.retryBlockCode
1522
+ } : {},
1523
+ ...retryAfter !== void 0 ? {
1524
+ retry_after_seconds: retryAfter,
1525
+ retryAfter,
1526
+ retryAfterSeconds: retryAfter
1527
+ } : {},
1528
+ ...nextPollAt ? {
1529
+ next_poll_at: nextPollAt,
1530
+ nextPollAt
1531
+ } : {},
1532
+ ...normalizedJob.createdAt ? {
1533
+ created_at: normalizedJob.createdAt,
1534
+ createdAt: normalizedJob.createdAt
1535
+ } : {},
1536
+ ...normalizedJob.expiresAt ? {
1537
+ expires_at: normalizedJob.expiresAt,
1538
+ expiresAt: normalizedJob.expiresAt
1539
+ } : {},
1540
+ ...normalizedJob.completedAt !== void 0 ? {
1541
+ completed_at: normalizedJob.completedAt,
1542
+ completedAt: normalizedJob.completedAt
1543
+ } : {},
1544
+ timestamp: normalizedJob.completedAt ?? normalizedJob.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()
1545
+ };
1546
+ }
1547
+ videoRetryingEvent(err, fallbackJobId) {
1548
+ const jobId = this.retryableStatusErrorJobId(err) ?? fallbackJobId;
1549
+ const source = normalizeVideoJobSource(err.source);
1550
+ const requestSource = normalizeVideoJobSource(
1551
+ err.requestSource ?? err.request_source
1552
+ );
1553
+ return {
1554
+ type: "retrying",
1555
+ id: err.id ?? jobId,
1556
+ job_id: err.job_id ?? jobId,
1557
+ jobId,
1558
+ task_id: err.task_id ?? jobId,
1559
+ taskId: err.taskId ?? jobId,
1560
+ ...err.operationId ? { operation_id: err.operationId, operationId: err.operationId } : {},
1561
+ ...err.requestId ? { request_id: err.requestId, requestId: err.requestId } : {},
1562
+ ...source || requestSource ? {
1563
+ source: source ?? requestSource,
1564
+ request_source: requestSource ?? source,
1565
+ requestSource: requestSource ?? source
1566
+ } : {},
1567
+ ...err.statusUrl ? { status_url: err.statusUrl, statusUrl: err.statusUrl } : {},
1568
+ ...err.contentLocation ? {
1569
+ content_location: err.contentLocation,
1570
+ contentLocation: err.contentLocation
1571
+ } : {},
1572
+ error: err.message,
1573
+ code: err.code,
1574
+ status: err.status,
1575
+ statusCode: err.status,
1576
+ ...err.retryable !== void 0 ? { retryable: err.retryable } : {},
1577
+ ...err.credits ? { credits: err.credits } : {},
1578
+ ...err.creationCredits ? {
1579
+ creation_credits: err.creationCredits,
1580
+ creationCredits: err.creationCredits
1581
+ } : {},
1582
+ ...err.retryBlockedReason ? {
1583
+ retry_blocked_reason: err.retryBlockedReason,
1584
+ retryBlockedReason: err.retryBlockedReason
1585
+ } : {},
1586
+ ...err.retryBlockCode ? {
1587
+ retry_block_code: err.retryBlockCode,
1588
+ retryBlockCode: err.retryBlockCode
1589
+ } : {},
1590
+ ...err.retryAfter !== void 0 ? {
1591
+ retry_after_seconds: err.retryAfter,
1592
+ retryAfter: err.retryAfter,
1593
+ retryAfterSeconds: err.retryAfter
1594
+ } : {},
1595
+ ...err.nextPollAt ? { next_poll_at: err.nextPollAt, nextPollAt: err.nextPollAt } : {},
1596
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1597
+ };
1598
+ }
1599
+ async *streamPolledVideoEvents(jobId, options) {
1600
+ const interval = options?.intervalMs ?? 2e3;
1601
+ const timeout = options?.timeoutMs ?? 3e5;
1602
+ const deadline = Date.now() + timeout;
1603
+ const initialDelayMs = this.optionDelayMs(options?.initialDelayMs);
1604
+ let currentJobId = jobId;
1605
+ if (initialDelayMs !== void 0 && initialDelayMs > 0) {
1606
+ await this.sleepWithinDeadline(initialDelayMs, deadline);
1607
+ }
1608
+ while (Date.now() < deadline) {
1609
+ try {
1610
+ const { data: job, headers } = await this.getWithResponse(
1611
+ this.videoJobPath(currentJobId)
1612
+ );
1613
+ const normalizedJob = this.normalizeVideoJob(job, headers);
1614
+ currentJobId = this.videoJobIdFromStatusUrl(
1615
+ normalizedJob.statusUrl ?? normalizedJob.status_url
1616
+ ) ?? normalizedJob.id;
1617
+ yield this.videoJobEvent(job, headers);
1618
+ if (normalizedJob.status === "completed" || normalizedJob.status === "failed") {
1619
+ return;
1620
+ }
1621
+ const delayMs = this.pollingDelayMs(normalizedJob, headers, interval);
1622
+ await this.sleep(Math.min(delayMs, Math.max(deadline - Date.now(), 0)));
1623
+ } catch (err) {
1624
+ if (!(err instanceof PuppetryError)) throw err;
1625
+ const delayMs = this.retryableStatusErrorDelayMs(err, interval);
1626
+ if (delayMs === void 0) throw err;
1627
+ currentJobId = this.retryableStatusErrorJobId(err) ?? currentJobId;
1628
+ yield this.videoRetryingEvent(err, currentJobId);
1629
+ await this.sleepWithinDeadline(delayMs, deadline);
1630
+ }
1631
+ }
1632
+ throw new PuppetryTimeoutError(timeout);
1633
+ }
1634
+ };
1635
+
1636
+ export {
1637
+ Puppetry
1638
+ };