@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,3174 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
18
+ // If the importer is in node compatibility mode or this is not an ESM
19
+ // file that has been converted to a CommonJS file using a Babel-
20
+ // compatible transform (i.e. "__esModule" has not been set), then set
21
+ // "default" to the CommonJS "module.exports" for node compatibility.
22
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
+ mod
24
+ ));
25
+
26
+ // src/mcp-stdio.ts
27
+ var import_node_buffer = require("buffer");
28
+ var import_node_process = __toESM(require("process"));
29
+
30
+ // src/errors.ts
31
+ var PuppetryError = class _PuppetryError extends Error {
32
+ constructor(status, code, message, details, options) {
33
+ super(message);
34
+ this.name = "PuppetryError";
35
+ this.status = status;
36
+ this.code = code;
37
+ this.details = details;
38
+ this.object = options?.object;
39
+ this.retryAfter = options?.retryAfter;
40
+ this.retryable = options?.retryable;
41
+ this.credits = options?.credits;
42
+ this.creationCredits = options?.creationCredits ?? options?.creation_credits;
43
+ this.creation_credits = this.creationCredits;
44
+ this.retryBlockedReason = options?.retryBlockedReason ?? options?.retry_blocked_reason;
45
+ this.retry_blocked_reason = this.retryBlockedReason;
46
+ this.retryBlockCode = options?.retryBlockCode ?? options?.retry_block_code;
47
+ this.retry_block_code = this.retryBlockCode;
48
+ this.jobId = options?.jobId ?? options?.job_id ?? options?.id;
49
+ this.job_id = this.jobId;
50
+ this.id = this.jobId;
51
+ this.operationId = options?.operationId ?? options?.operation_id;
52
+ this.operation_id = this.operationId;
53
+ this.taskId = options?.taskId ?? options?.task_id ?? this.jobId;
54
+ this.task_id = this.taskId;
55
+ this.statusUrl = options?.statusUrl ?? options?.status_url ?? options?.contentLocation ?? options?.content_location;
56
+ this.status_url = this.statusUrl;
57
+ this.contentLocation = options?.contentLocation ?? options?.content_location ?? options?.statusUrl ?? options?.status_url;
58
+ this.content_location = this.contentLocation;
59
+ this.createdAt = options?.createdAt ?? options?.created_at;
60
+ this.created_at = this.createdAt;
61
+ this.expiresAt = options?.expiresAt ?? options?.expires_at;
62
+ this.expires_at = this.expiresAt;
63
+ this.nextPollAt = options?.nextPollAt ?? options?.next_poll_at;
64
+ this.next_poll_at = this.nextPollAt;
65
+ this.source = options?.source ?? options?.requestSource ?? options?.request_source;
66
+ this.requestSource = options?.requestSource ?? options?.request_source ?? this.source;
67
+ this.request_source = this.requestSource;
68
+ this.requestId = options?.requestId ?? options?.request_id;
69
+ this.request_id = this.requestId;
70
+ this.requiredCredits = options?.requiredCredits ?? options?.required_credits;
71
+ this.required_credits = this.requiredCredits;
72
+ this.creditsRemaining = options?.creditsRemaining ?? options?.credits_remaining ?? options?.remainingCredits ?? options?.remaining_credits;
73
+ this.credits_remaining = this.creditsRemaining;
74
+ this.remainingCredits = this.creditsRemaining;
75
+ this.remaining_credits = this.creditsRemaining;
76
+ }
77
+ static fromResponse(status, body, options) {
78
+ return new _PuppetryError(status, body.error, body.message, body.details, {
79
+ ...options,
80
+ ...retryMetadataFromApiError(body),
81
+ ...creditReadinessMetadataFromApiError(body),
82
+ ...jobCorrelationMetadataFromApiError(body)
83
+ });
84
+ }
85
+ };
86
+ var PuppetryTimeoutError = class extends PuppetryError {
87
+ constructor(timeoutMs) {
88
+ super(408, "timeout", `Request timed out after ${timeoutMs}ms`);
89
+ this.name = "PuppetryTimeoutError";
90
+ }
91
+ };
92
+ function retryMetadataFromApiError(body) {
93
+ const readinessDetails = objectFromKeys(
94
+ body.details,
95
+ "video_generation",
96
+ "videoGeneration"
97
+ );
98
+ const readinessBlocker = firstObjectFromArrayKeys(
99
+ readinessDetails,
100
+ "blockers"
101
+ );
102
+ const retryable = typeof body.retryable === "boolean" ? body.retryable : typeof body.details?.retryable === "boolean" ? body.details.retryable : typeof readinessDetails?.retryable === "boolean" ? readinessDetails.retryable : typeof readinessBlocker?.retryable === "boolean" ? readinessBlocker.retryable : void 0;
103
+ const retryBlockedReason = stringFromAliases(body, "retryBlockedReason", "retry_blocked_reason") ?? stringFromAliases(
104
+ body.details,
105
+ "retryBlockedReason",
106
+ "retry_blocked_reason"
107
+ ) ?? stringFromAliases(
108
+ readinessDetails,
109
+ "retryBlockedReason",
110
+ "retry_blocked_reason"
111
+ ) ?? stringFromAliases(
112
+ readinessBlocker,
113
+ "retryBlockedReason",
114
+ "retry_blocked_reason"
115
+ ) ?? stringFromKeys(readinessBlocker, "reason", "code") ?? stringFromKeys(readinessDetails, "reason");
116
+ const retryBlockCode = stringFromAliases(body, "retryBlockCode", "retry_block_code") ?? stringFromAliases(body.details, "retryBlockCode", "retry_block_code") ?? stringFromAliases(readinessDetails, "retryBlockCode", "retry_block_code") ?? stringFromAliases(readinessBlocker, "retryBlockCode", "retry_block_code") ?? stringFromKeys(readinessBlocker, "code");
117
+ return {
118
+ ...retryable !== void 0 ? { retryable } : {},
119
+ ...retryBlockedReason ? {
120
+ retry_blocked_reason: retryBlockedReason,
121
+ retryBlockedReason
122
+ } : {},
123
+ ...retryBlockCode ? {
124
+ retry_block_code: retryBlockCode,
125
+ retryBlockCode
126
+ } : {}
127
+ };
128
+ }
129
+ function jobCorrelationMetadataFromApiError(body) {
130
+ const readinessDetails = objectFromKeys(
131
+ body.details,
132
+ "video_generation",
133
+ "videoGeneration"
134
+ );
135
+ const object = stringFromKeys(body, "object") ?? stringFromKeys(body.details, "object");
136
+ const jobId = stringFromKeys(body, "jobId", "job_id", "id") ?? stringFromKeys(body.details, "jobId", "job_id", "id");
137
+ const operationId = stringFromKeys(body, "operationId", "operation_id") ?? stringFromKeys(body.details, "operationId", "operation_id");
138
+ const taskId = stringFromKeys(body, "taskId", "task_id") ?? stringFromKeys(body.details, "taskId", "task_id") ?? jobId;
139
+ const bodyStatusUrl = stringFromKeys(body, "statusUrl", "status_url") ?? stringFromKeys(body.details, "statusUrl", "status_url");
140
+ const bodyContentLocation = stringFromKeys(body, "contentLocation", "content_location") ?? stringFromKeys(body.details, "contentLocation", "content_location");
141
+ const statusUrl = bodyStatusUrl ?? bodyContentLocation;
142
+ const contentLocation = bodyContentLocation ?? bodyStatusUrl;
143
+ const createdAt = stringFromKeys(body, "createdAt", "created_at") ?? stringFromKeys(body.details, "createdAt", "created_at");
144
+ const expiresAt = stringFromKeys(body, "expiresAt", "expires_at") ?? stringFromKeys(body.details, "expiresAt", "expires_at");
145
+ const nextPollAt = stringFromKeys(body, "nextPollAt", "next_poll_at") ?? stringFromKeys(body.details, "nextPollAt", "next_poll_at") ?? stringFromKeys(readinessDetails, "nextPollAt", "next_poll_at");
146
+ const source = stringFromKeys(body, "source") ?? stringFromKeys(body.details, "source");
147
+ const requestSource = stringFromKeys(body, "requestSource", "request_source") ?? stringFromKeys(body.details, "requestSource", "request_source") ?? source;
148
+ const requestId = stringFromKeys(body, "requestId", "request_id") ?? stringFromKeys(body.details, "requestId", "request_id") ?? stringFromKeys(readinessDetails, "requestId", "request_id");
149
+ const credits = statusCreditsFromKeys(body, "credits") ?? statusCreditsFromKeys(body.details, "credits");
150
+ const creationCredits = creationCreditsFromKeys(body, "creationCredits", "creation_credits") ?? creationCreditsFromKeys(
151
+ body.details,
152
+ "creationCredits",
153
+ "creation_credits"
154
+ );
155
+ return {
156
+ ...object ? { object } : {},
157
+ ...jobId ? { id: jobId, job_id: jobId, jobId } : {},
158
+ ...operationId ? { operation_id: operationId, operationId } : {},
159
+ ...taskId ? { task_id: taskId, taskId } : {},
160
+ ...statusUrl ? { status_url: statusUrl, statusUrl } : {},
161
+ ...contentLocation ? { content_location: contentLocation, contentLocation } : {},
162
+ ...createdAt ? { created_at: createdAt, createdAt } : {},
163
+ ...expiresAt ? { expires_at: expiresAt, expiresAt } : {},
164
+ ...nextPollAt ? { next_poll_at: nextPollAt, nextPollAt } : {},
165
+ ...source ? { source } : {},
166
+ ...requestSource ? { request_source: requestSource, requestSource } : {},
167
+ ...requestId ? { request_id: requestId, requestId } : {},
168
+ ...credits ? { credits } : {},
169
+ ...creationCredits ? { creation_credits: creationCredits, creationCredits } : {}
170
+ };
171
+ }
172
+ function creditReadinessMetadataFromApiError(body) {
173
+ const readinessDetails = objectFromKeys(
174
+ body.details,
175
+ "video_generation",
176
+ "videoGeneration"
177
+ );
178
+ const readinessBlocker = firstObjectFromArrayKeys(
179
+ readinessDetails,
180
+ "blockers"
181
+ );
182
+ const requiredCredits = numberFromKeys(body, "requiredCredits", "required_credits") ?? numberFromKeys(body.details, "requiredCredits", "required_credits") ?? numberFromKeys(readinessDetails, "requiredCredits", "required_credits") ?? numberFromKeys(readinessBlocker, "requiredCredits", "required_credits");
183
+ const creditsRemaining = numberFromKeys(
184
+ body,
185
+ "creditsRemaining",
186
+ "credits_remaining",
187
+ "remainingCredits",
188
+ "remaining_credits"
189
+ ) ?? numberFromKeys(
190
+ body.details,
191
+ "creditsRemaining",
192
+ "credits_remaining",
193
+ "remainingCredits",
194
+ "remaining_credits"
195
+ ) ?? numberFromKeys(readinessDetails, "creditsRemaining", "credits_remaining") ?? numberFromKeys(
196
+ readinessBlocker,
197
+ "creditsRemaining",
198
+ "credits_remaining",
199
+ "remainingCredits",
200
+ "remaining_credits"
201
+ );
202
+ return {
203
+ ...requiredCredits !== void 0 ? { required_credits: requiredCredits, requiredCredits } : {},
204
+ ...creditsRemaining !== void 0 ? {
205
+ credits_remaining: creditsRemaining,
206
+ creditsRemaining,
207
+ remaining_credits: creditsRemaining,
208
+ remainingCredits: creditsRemaining
209
+ } : {}
210
+ };
211
+ }
212
+ function stringFromAliases(record, camelKey, snakeKey) {
213
+ return stringFromKeys(record, camelKey, snakeKey);
214
+ }
215
+ function stringFromKeys(record, ...keys) {
216
+ if (!record) return void 0;
217
+ const source = record;
218
+ const value = keys.map((key) => source[key]).find((item) => item !== void 0);
219
+ if (typeof value !== "string") return void 0;
220
+ const trimmed = value.trim();
221
+ return trimmed || void 0;
222
+ }
223
+ function objectFromKeys(record, ...keys) {
224
+ if (!record) return void 0;
225
+ const source = record;
226
+ const value = keys.map((key) => source[key]).find((item) => item !== void 0);
227
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
228
+ return void 0;
229
+ }
230
+ return value;
231
+ }
232
+ function statusCreditsFromKeys(record, ...keys) {
233
+ const value = objectFromKeys(record, ...keys);
234
+ if (!value) return void 0;
235
+ const charged = numberFromKeys(value, "charged");
236
+ const remaining = nullableNumberFromKeys(value, "remaining");
237
+ if (charged === void 0 || remaining === void 0) return void 0;
238
+ return { charged, remaining };
239
+ }
240
+ function creationCreditsFromKeys(record, ...keys) {
241
+ const value = objectFromKeys(record, ...keys);
242
+ if (!value) return void 0;
243
+ const charged = numberFromKeys(value, "charged");
244
+ const remainingAfterDebit = nullableNumberFromKeys(
245
+ value,
246
+ "remainingAfterDebit",
247
+ "remaining_after_debit"
248
+ );
249
+ if (charged === void 0 || remainingAfterDebit === void 0) {
250
+ return void 0;
251
+ }
252
+ return {
253
+ charged,
254
+ remaining_after_debit: remainingAfterDebit,
255
+ remainingAfterDebit
256
+ };
257
+ }
258
+ function numberFromKeys(record, ...keys) {
259
+ if (!record) return void 0;
260
+ const source = record;
261
+ const value = keys.map((key) => source[key]).find((item) => item !== void 0);
262
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
263
+ }
264
+ function nullableNumberFromKeys(record, ...keys) {
265
+ if (!record) return void 0;
266
+ const source = record;
267
+ const value = keys.map((key) => source[key]).find((item) => item !== void 0);
268
+ if (value === null) return null;
269
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
270
+ }
271
+ function firstObjectFromArrayKeys(record, ...keys) {
272
+ if (!record) return void 0;
273
+ const source = record;
274
+ const value = keys.map((key) => source[key]).find((item) => Array.isArray(item));
275
+ const first = Array.isArray(value) ? value[0] : void 0;
276
+ if (!first || typeof first !== "object" || Array.isArray(first)) {
277
+ return void 0;
278
+ }
279
+ return first;
280
+ }
281
+ var PuppetryAuthError = class extends PuppetryError {
282
+ constructor(message = "Invalid or missing API key") {
283
+ super(401, "unauthorized", message);
284
+ this.name = "PuppetryAuthError";
285
+ }
286
+ };
287
+ var PuppetryRateLimitError = class _PuppetryRateLimitError extends PuppetryError {
288
+ constructor(retryAfter, message = "Rate limit exceeded", details, code = "rate_limited", options) {
289
+ super(429, code, message, details, {
290
+ ...options,
291
+ retryAfter,
292
+ retryable: options?.retryable ?? (typeof details?.retryable === "boolean" ? details.retryable : void 0)
293
+ });
294
+ this.name = "PuppetryRateLimitError";
295
+ }
296
+ static fromRateLimitResponse(body, options) {
297
+ return new _PuppetryRateLimitError(
298
+ options?.retryAfter,
299
+ body.message,
300
+ body.details,
301
+ body.error || "rate_limited",
302
+ {
303
+ ...options,
304
+ ...retryMetadataFromApiError(body),
305
+ ...creditReadinessMetadataFromApiError(body),
306
+ ...jobCorrelationMetadataFromApiError(body)
307
+ }
308
+ );
309
+ }
310
+ };
311
+ var PuppetryNetworkError = class extends PuppetryError {
312
+ constructor(message = "Network request failed") {
313
+ super(0, "network_error", message);
314
+ this.name = "PuppetryNetworkError";
315
+ }
316
+ };
317
+ function puppetryErrorData(err) {
318
+ const videoGenerationReadiness = videoGenerationReadinessFromDetails(
319
+ err.details
320
+ );
321
+ return {
322
+ status: err.status,
323
+ statusCode: err.status,
324
+ code: err.code,
325
+ message: err.message,
326
+ ...err.object ? { object: err.object } : {},
327
+ ...err.retryAfter !== void 0 ? {
328
+ retry_after_seconds: err.retryAfter,
329
+ retryAfterSeconds: err.retryAfter,
330
+ retryAfter: err.retryAfter
331
+ } : {},
332
+ ...err.retryable !== void 0 ? { retryable: err.retryable } : {},
333
+ ...err.credits ? { credits: err.credits } : {},
334
+ ...err.creationCredits ? {
335
+ creation_credits: err.creationCredits,
336
+ creationCredits: err.creationCredits
337
+ } : {},
338
+ ...err.retryBlockedReason ? {
339
+ retry_blocked_reason: err.retryBlockedReason,
340
+ retryBlockedReason: err.retryBlockedReason
341
+ } : {},
342
+ ...err.retryBlockCode ? {
343
+ retry_block_code: err.retryBlockCode,
344
+ retryBlockCode: err.retryBlockCode
345
+ } : {},
346
+ ...err.jobId ? { id: err.jobId, job_id: err.jobId, jobId: err.jobId } : {},
347
+ ...err.operationId ? { operation_id: err.operationId, operationId: err.operationId } : {},
348
+ ...err.taskId ? { task_id: err.taskId, taskId: err.taskId } : {},
349
+ ...err.statusUrl ? { status_url: err.statusUrl, statusUrl: err.statusUrl } : {},
350
+ ...err.contentLocation ? {
351
+ content_location: err.contentLocation,
352
+ contentLocation: err.contentLocation
353
+ } : {},
354
+ ...err.createdAt ? { created_at: err.createdAt, createdAt: err.createdAt } : {},
355
+ ...err.expiresAt ? { expires_at: err.expiresAt, expiresAt: err.expiresAt } : {},
356
+ ...err.nextPollAt ? { next_poll_at: err.nextPollAt, nextPollAt: err.nextPollAt } : {},
357
+ ...err.source ? {
358
+ source: err.source,
359
+ request_source: err.requestSource ?? err.source,
360
+ requestSource: err.requestSource ?? err.source
361
+ } : {},
362
+ ...err.requestId ? { request_id: err.requestId, requestId: err.requestId } : {},
363
+ ...err.requiredCredits !== void 0 ? {
364
+ required_credits: err.requiredCredits,
365
+ requiredCredits: err.requiredCredits
366
+ } : {},
367
+ ...err.creditsRemaining !== void 0 ? {
368
+ credits_remaining: err.creditsRemaining,
369
+ creditsRemaining: err.creditsRemaining,
370
+ remaining_credits: err.creditsRemaining,
371
+ remainingCredits: err.creditsRemaining
372
+ } : {},
373
+ ...videoGenerationReadiness ? {
374
+ video_generation: videoGenerationReadiness,
375
+ videoGeneration: videoGenerationReadiness
376
+ } : {},
377
+ ...err.details !== void 0 ? { details: err.details } : {}
378
+ };
379
+ }
380
+ function videoGenerationReadinessFromDetails(details) {
381
+ const readiness = objectFromKeys(
382
+ details,
383
+ "video_generation",
384
+ "videoGeneration"
385
+ );
386
+ if (!readiness) return void 0;
387
+ const canCreate = booleanFromKeys(readiness, "canCreate", "can_create");
388
+ const requiredCredits = numberFromKeys(
389
+ readiness,
390
+ "requiredCredits",
391
+ "required_credits"
392
+ );
393
+ const creditsRemaining = numberFromKeys(
394
+ readiness,
395
+ "creditsRemaining",
396
+ "credits_remaining"
397
+ );
398
+ const retryAfterSeconds = numberFromKeys(
399
+ readiness,
400
+ "retryAfterSeconds",
401
+ "retryAfter",
402
+ "retry_after_seconds"
403
+ );
404
+ const nextPollAt = stringFromKeys(readiness, "nextPollAt", "next_poll_at");
405
+ return {
406
+ ...readiness,
407
+ ...canCreate === void 0 ? {} : { can_create: canCreate, canCreate },
408
+ ...requiredCredits === void 0 ? {} : { required_credits: requiredCredits, requiredCredits },
409
+ ...creditsRemaining === void 0 ? {} : { credits_remaining: creditsRemaining, creditsRemaining },
410
+ ...retryAfterSeconds === void 0 ? {} : {
411
+ retry_after_seconds: retryAfterSeconds,
412
+ retryAfter: retryAfterSeconds,
413
+ retryAfterSeconds
414
+ },
415
+ ...nextPollAt === void 0 ? {} : { next_poll_at: nextPollAt, nextPollAt }
416
+ };
417
+ }
418
+ function booleanFromKeys(record, ...keys) {
419
+ if (!record) return void 0;
420
+ const source = record;
421
+ const value = keys.map((key) => source[key]).find((item) => item !== void 0);
422
+ return typeof value === "boolean" ? value : void 0;
423
+ }
424
+
425
+ // src/client.ts
426
+ var DEFAULT_BASE_URL = "https://www.puppetry.com/api/v1";
427
+ var DEFAULT_TIMEOUT = 3e4;
428
+ var DEFAULT_MAX_RETRIES = 2;
429
+ var DEFAULT_RETRY_DELAY_MS = 500;
430
+ var SDK_VERSION = "0.1.0";
431
+ var IDEMPOTENCY_KEY_MAX_LENGTH = 200;
432
+ var EMPTY_HEADERS = { get: () => null };
433
+ var VIDEO_STATUS_ALIASES = {
434
+ active: "processing",
435
+ complete: "completed",
436
+ completed: "completed",
437
+ delayed: "queued",
438
+ done: "completed",
439
+ error: "failed",
440
+ errored: "failed",
441
+ failed: "failed",
442
+ failure: "failed",
443
+ finished: "completed",
444
+ "in-progress": "processing",
445
+ in_progress: "processing",
446
+ paused: "queued",
447
+ prioritized: "queued",
448
+ process: "processing",
449
+ processing: "processing",
450
+ queued: "queued",
451
+ rendering: "processing",
452
+ retrying: "processing",
453
+ running: "processing",
454
+ started: "processing",
455
+ success: "completed",
456
+ succeeded: "completed",
457
+ wait: "queued",
458
+ waiting: "queued",
459
+ "waiting-children": "queued"
460
+ };
461
+ function normalizeVideoStatusAlias(status) {
462
+ if (typeof status !== "string") return void 0;
463
+ return VIDEO_STATUS_ALIASES[status.trim().toLowerCase()];
464
+ }
465
+ function normalizeVideoJobSource(source) {
466
+ return source === "text" || source === "audio" ? source : void 0;
467
+ }
468
+ var Puppetry = class {
469
+ constructor(config) {
470
+ // ── Videos ─────────────────────────────────────────────────────────
471
+ /**
472
+ * Create a video from text (text-to-speech + lip sync).
473
+ *
474
+ * @example
475
+ * ```ts
476
+ * const job = await puppetry.videos.createFromText({
477
+ * text: "Hello, world!",
478
+ * image_url: "https://example.com/photo.jpg",
479
+ * voice_id: "puppetry-af_heart",
480
+ * });
481
+ * ```
482
+ */
483
+ this.videos = {
484
+ createFromText: async (params) => this.requestWithResponse(
485
+ "POST",
486
+ "/videos/text",
487
+ this.textVideoBody(params),
488
+ {
489
+ idempotencyKey: this.idempotencyKeyFromParams(params)
490
+ }
491
+ ).then(({ data, headers }) => this.normalizeVideoJob(data, headers)),
492
+ createFromAudio: async (params) => this.requestWithResponse(
493
+ "POST",
494
+ "/videos/audio",
495
+ this.audioVideoBody(params),
496
+ {
497
+ idempotencyKey: this.idempotencyKeyFromParams(params)
498
+ }
499
+ ).then(({ data, headers }) => this.normalizeVideoJob(data, headers)),
500
+ get: async (jobId) => this.getWithResponse(this.videoJobPath(jobId)).then(
501
+ ({ data, headers }) => this.normalizeVideoJob(data, headers)
502
+ ),
503
+ /**
504
+ * Return the current Developer API video readiness contract.
505
+ *
506
+ * Use this before reserving uploads or queueing a video when an agent needs
507
+ * to fail fast on credits or active slot limits.
508
+ */
509
+ getReadiness: () => this.getVideoGenerationReadiness(),
510
+ /**
511
+ * Assert that this API key can create a video right now.
512
+ *
513
+ * Throws PuppetryRateLimitError with retryAfter for active-slot blockers,
514
+ * and PuppetryError(402) for credit blockers.
515
+ */
516
+ ensureReady: () => this.ensureVideoGenerationReady(),
517
+ /**
518
+ * Wait for a video job to complete, polling at the given interval.
519
+ *
520
+ * @param jobId - The job ID to watch
521
+ * @param options.intervalMs - Polling interval (default: 2000)
522
+ * @param options.timeoutMs - Max wait time (default: 300000 = 5 min)
523
+ * @returns The completed VideoJob
524
+ * @throws PuppetryTimeoutError if the job doesn't complete in time
525
+ */
526
+ waitForCompletion: async (jobId, options) => {
527
+ const interval = options?.intervalMs ?? 2e3;
528
+ const timeout = options?.timeoutMs ?? 3e5;
529
+ const deadline = Date.now() + timeout;
530
+ const initialDelayMs = this.optionDelayMs(options?.initialDelayMs);
531
+ let currentJobId = jobId;
532
+ if (initialDelayMs !== void 0 && initialDelayMs > 0) {
533
+ await this.sleepWithinDeadline(initialDelayMs, deadline);
534
+ }
535
+ while (Date.now() < deadline) {
536
+ try {
537
+ const { data: job, headers } = await this.getWithResponse(
538
+ this.videoJobPath(currentJobId)
539
+ );
540
+ const normalizedJob = this.normalizeVideoJob(job, headers);
541
+ currentJobId = this.videoJobIdFromStatusUrl(
542
+ normalizedJob.statusUrl ?? normalizedJob.status_url
543
+ ) ?? normalizedJob.id;
544
+ await options?.onPoll?.(normalizedJob);
545
+ if (normalizedJob.status === "completed" || normalizedJob.status === "failed") {
546
+ return normalizedJob;
547
+ }
548
+ const delayMs = this.pollingDelayMs(normalizedJob, headers, interval);
549
+ await this.sleep(
550
+ Math.min(delayMs, Math.max(deadline - Date.now(), 0))
551
+ );
552
+ } catch (err) {
553
+ if (!(err instanceof PuppetryError)) throw err;
554
+ const delayMs = this.retryableStatusErrorDelayMs(err, interval);
555
+ if (delayMs === void 0) throw err;
556
+ const retryingEvent = this.videoRetryingEvent(err, currentJobId);
557
+ currentJobId = retryingEvent.jobId;
558
+ await options?.onRetrying?.(retryingEvent);
559
+ await this.sleepWithinDeadline(delayMs, deadline);
560
+ }
561
+ }
562
+ throw new PuppetryTimeoutError(timeout);
563
+ },
564
+ /**
565
+ * Stream progress-like events by polling the live v1 job status endpoint.
566
+ *
567
+ * @param jobId - The job ID to stream
568
+ * @param options.intervalMs - Polling interval (default: 2000)
569
+ * @param options.timeoutMs - Max stream time (default: 300000 = 5 min)
570
+ * @returns AsyncGenerator yielding VideoEvent objects
571
+ *
572
+ * @example
573
+ * ```ts
574
+ * for await (const event of puppetry.videos.stream("job-123")) {
575
+ * console.log(`${event.type}: ${event.progress}%`);
576
+ * if (event.type === "completed") break;
577
+ * }
578
+ * ```
579
+ */
580
+ stream: (jobId, options) => this.streamPolledVideoEvents(jobId, options)
581
+ };
582
+ // ── Jobs ───────────────────────────────────────────────────────────
583
+ this.jobs = {
584
+ /**
585
+ * Fetch a generated video job by ID.
586
+ *
587
+ * This is an alias for `videos.get()` because Puppetry's public v1 API
588
+ * exposes generated video job state under `/videos/{jobId}`.
589
+ */
590
+ get: (jobId) => this.videos.get(jobId),
591
+ /**
592
+ * Wait for a generated video job to complete, polling at the given interval.
593
+ */
594
+ waitForCompletion: (jobId, options) => this.videos.waitForCompletion(jobId, options)
595
+ };
596
+ // ── Voices ─────────────────────────────────────────────────────────
597
+ this.voices = {
598
+ list: (params) => {
599
+ const query = new URLSearchParams();
600
+ if (params?.language) query.set("language", params.language);
601
+ if (params?.gender) query.set("gender", params.gender);
602
+ if (params?.cursor) query.set("cursor", params.cursor);
603
+ if (params?.limit) query.set("limit", String(params.limit));
604
+ const qs = query.toString();
605
+ return this.get(`/voices${qs ? "?" + qs : ""}`);
606
+ },
607
+ listPuppetry: () => this.get("/voices/puppetry")
608
+ };
609
+ // ── Text to Speech ───────────────────────────────────────────────────
610
+ this.tts = {
611
+ createPuppetry: (params) => this.post(
612
+ "/tts/puppetry",
613
+ params
614
+ )
615
+ };
616
+ // ── Uploads ───────────────────────────────────────────────────────────
617
+ this.uploads = {
618
+ /**
619
+ * Create a signed PUT URL for hosting audio that can be used by Puppetry API
620
+ * workflows.
621
+ */
622
+ createAudioUrl: async (params) => this.post(
623
+ "/uploads/audio-url",
624
+ this.compactBody({
625
+ mime_type: this.resolveUploadStringAliases([
626
+ { name: "mime_type", value: params.mime_type },
627
+ { name: "mimeType", value: params.mimeType },
628
+ { name: "contentType", value: params.contentType }
629
+ ]),
630
+ content_length: this.resolveNumberAliases([
631
+ { name: "content_length", value: params.content_length },
632
+ { name: "contentLength", value: params.contentLength },
633
+ { name: "sizeBytes", value: params.sizeBytes },
634
+ { name: "file_size", value: params.file_size },
635
+ { name: "fileSize", value: params.fileSize }
636
+ ]),
637
+ require_video_readiness: params.require_video_readiness ?? params.requireVideoReadiness
638
+ }),
639
+ { idempotencyKey: this.idempotencyKeyFromParams(params) }
640
+ ).then((upload) => this.normalizeAudioUpload(upload)),
641
+ /**
642
+ * Create a signed audio upload URL and PUT the provided bytes to it.
643
+ *
644
+ * The returned `read_url` can be passed to `videos.createFromAudio()`.
645
+ */
646
+ uploadAudio: async (params) => {
647
+ const upload = await this.uploads.createAudioUrl(params);
648
+ const controller = new AbortController();
649
+ const timer = setTimeout(() => controller.abort(), this.timeout);
650
+ try {
651
+ const res = await this.fetchFn(upload.upload_url, {
652
+ method: upload.method,
653
+ headers: upload.headers,
654
+ body: params.body,
655
+ signal: controller.signal
656
+ });
657
+ if (!res.ok) {
658
+ throw new PuppetryError(
659
+ res.status,
660
+ "upload_failed",
661
+ res.statusText || "Failed to upload audio bytes"
662
+ );
663
+ }
664
+ } catch (err) {
665
+ if (err instanceof PuppetryError) throw err;
666
+ if (this.isAbortError(err)) {
667
+ throw new PuppetryTimeoutError(this.timeout);
668
+ }
669
+ if (this.isNetworkError(err)) {
670
+ throw new PuppetryNetworkError(this.transportErrorMessage(err));
671
+ }
672
+ throw err;
673
+ } finally {
674
+ clearTimeout(timer);
675
+ }
676
+ return this.normalizeAudioUpload(upload);
677
+ }
678
+ };
679
+ // ── Puppets ────────────────────────────────────────────────────────
680
+ this.puppets = {
681
+ list: (params) => {
682
+ const query = new URLSearchParams();
683
+ if (params?.cursor) query.set("cursor", params.cursor);
684
+ if (params?.limit) query.set("limit", String(params.limit));
685
+ const qs = query.toString();
686
+ return this.get(
687
+ `/puppets${qs ? "?" + qs : ""}`
688
+ );
689
+ },
690
+ create: (params) => this.post(
691
+ "/puppets",
692
+ params
693
+ ),
694
+ delete: (puppetId) => this.delete(`/puppets/${encodeURIComponent(puppetId)}`)
695
+ };
696
+ // ── Usage ──────────────────────────────────────────────────────────
697
+ this.usage = {
698
+ get: async () => this.normalizeUsage(await this.get("/usage"))
699
+ };
700
+ // ── Quota ──────────────────────────────────────────────────────────
701
+ this.quota = {
702
+ get: () => this.usage.get()
703
+ };
704
+ const apiKey2 = config.apiKey.trim();
705
+ if (!apiKey2) {
706
+ throw new PuppetryAuthError("apiKey is required");
707
+ }
708
+ this.apiKey = apiKey2;
709
+ this.baseUrl = (config.baseUrl || DEFAULT_BASE_URL).replace(/\/+$/, "");
710
+ this.timeout = config.timeout || DEFAULT_TIMEOUT;
711
+ this.maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;
712
+ this.retryDelayMs = config.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS;
713
+ this.onRetry = config.onRetry;
714
+ this.fetchFn = config.fetch || globalThis.fetch;
715
+ }
716
+ // ── HTTP Layer ─────────────────────────────────────────────────────
717
+ async requestWithResponse(method, path, body, options) {
718
+ const url = `${this.baseUrl}${path}`;
719
+ const requestBody = body ? JSON.stringify(body) : void 0;
720
+ const headers = {
721
+ Authorization: `Bearer ${this.apiKey}`,
722
+ "Content-Type": "application/json",
723
+ "User-Agent": `@puppetry.com/sdk/${SDK_VERSION}`
724
+ };
725
+ const idempotencyKey = this.normalizeIdempotencyKey(
726
+ options?.idempotencyKey
727
+ );
728
+ if (idempotencyKey) {
729
+ headers["Idempotency-Key"] = idempotencyKey;
730
+ }
731
+ for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
732
+ const controller = new AbortController();
733
+ const timer = setTimeout(() => controller.abort(), this.timeout);
734
+ try {
735
+ const res = await this.fetchFn(url, {
736
+ method,
737
+ headers,
738
+ body: requestBody,
739
+ signal: controller.signal
740
+ });
741
+ if (!res.ok) {
742
+ const errorBody = await res.json().catch(() => ({
743
+ error: "unknown",
744
+ message: res.statusText
745
+ }));
746
+ const responseHeaders = this.responseHeaders(res);
747
+ const retryAfter = this.responseRetryAfter(res.headers, errorBody);
748
+ const errorOptions = {
749
+ ...this.videoCreditErrorOptions(responseHeaders),
750
+ ...this.videoStatusErrorOptions(
751
+ method,
752
+ path,
753
+ responseHeaders,
754
+ retryAfter
755
+ )
756
+ };
757
+ const isVideoStatusRetry = this.isRetryableVideoStatusError(
758
+ method,
759
+ path,
760
+ errorBody
761
+ );
762
+ if (!isVideoStatusRetry && this.shouldRetry(method, res.status, Boolean(idempotencyKey)) && attempt < this.maxRetries) {
763
+ const delayMs = this.retryDelay(attempt, retryAfter);
764
+ const retryError = res.status === 429 ? PuppetryRateLimitError.fromRateLimitResponse(errorBody, {
765
+ ...errorOptions
766
+ }) : PuppetryError.fromResponse(
767
+ res.status,
768
+ errorBody,
769
+ errorOptions
770
+ );
771
+ await this.notifyRetry(
772
+ this.requestRetryEvent(
773
+ method,
774
+ path,
775
+ url,
776
+ idempotencyKey,
777
+ attempt,
778
+ delayMs,
779
+ retryError
780
+ )
781
+ );
782
+ await this.sleep(delayMs);
783
+ continue;
784
+ }
785
+ if (res.status === 401)
786
+ throw new PuppetryAuthError(errorBody.message);
787
+ if (res.status === 429) {
788
+ throw PuppetryRateLimitError.fromRateLimitResponse(errorBody, {
789
+ ...errorOptions
790
+ });
791
+ }
792
+ throw PuppetryError.fromResponse(res.status, errorBody, errorOptions);
793
+ }
794
+ if (res.status === 204) {
795
+ return { data: void 0, headers: this.responseHeaders(res) };
796
+ }
797
+ return {
798
+ data: await res.json(),
799
+ headers: this.responseHeaders(res)
800
+ };
801
+ } catch (err) {
802
+ if (err instanceof PuppetryError) throw err;
803
+ if (this.isAbortError(err)) {
804
+ if (this.shouldRetryTransport(method, Boolean(idempotencyKey), attempt)) {
805
+ const delayMs = this.retryDelay(attempt);
806
+ await this.notifyRetry(
807
+ this.requestRetryEvent(
808
+ method,
809
+ path,
810
+ url,
811
+ idempotencyKey,
812
+ attempt,
813
+ delayMs,
814
+ new PuppetryTimeoutError(this.timeout)
815
+ )
816
+ );
817
+ await this.sleep(delayMs);
818
+ continue;
819
+ }
820
+ throw new PuppetryTimeoutError(this.timeout);
821
+ }
822
+ if (this.isNetworkError(err)) {
823
+ if (this.shouldRetryTransport(method, Boolean(idempotencyKey), attempt)) {
824
+ const delayMs = this.retryDelay(attempt);
825
+ await this.notifyRetry(
826
+ this.requestRetryEvent(
827
+ method,
828
+ path,
829
+ url,
830
+ idempotencyKey,
831
+ attempt,
832
+ delayMs,
833
+ new PuppetryNetworkError(this.transportErrorMessage(err))
834
+ )
835
+ );
836
+ await this.sleep(delayMs);
837
+ continue;
838
+ }
839
+ throw new PuppetryNetworkError(this.transportErrorMessage(err));
840
+ }
841
+ throw err;
842
+ } finally {
843
+ clearTimeout(timer);
844
+ }
845
+ }
846
+ throw new PuppetryError(
847
+ 500,
848
+ "retry_exhausted",
849
+ "Request retries exhausted"
850
+ );
851
+ }
852
+ requestRetryEvent(method, path, url, idempotencyKey, attempt, delayMs, err) {
853
+ return {
854
+ method,
855
+ path,
856
+ url,
857
+ ...idempotencyKey ? {
858
+ idempotency_key: idempotencyKey,
859
+ idempotencyKey
860
+ } : {},
861
+ attempt: attempt + 1,
862
+ maxRetries: this.maxRetries,
863
+ delayMs,
864
+ ...puppetryErrorData(err)
865
+ };
866
+ }
867
+ async notifyRetry(event) {
868
+ if (!this.onRetry) return;
869
+ try {
870
+ await this.onRetry(event);
871
+ } catch {
872
+ }
873
+ }
874
+ async request(method, path, body, options) {
875
+ return (await this.requestWithResponse(method, path, body, options)).data;
876
+ }
877
+ get(path) {
878
+ return this.request("GET", path);
879
+ }
880
+ getWithResponse(path) {
881
+ return this.requestWithResponse("GET", path);
882
+ }
883
+ post(path, body, options) {
884
+ return this.request("POST", path, body, options);
885
+ }
886
+ delete(path) {
887
+ return this.request("DELETE", path);
888
+ }
889
+ shouldRetry(method, status, hasIdempotencyKey) {
890
+ if (status !== 429 && status < 500) return false;
891
+ return this.isRetrySafeRequest(method, hasIdempotencyKey);
892
+ }
893
+ isRetryableVideoStatusError(method, path, body) {
894
+ if (!this.isVideoStatusRequest(method, path)) return false;
895
+ return body.retryable === true || body.details?.retryable === true;
896
+ }
897
+ isVideoStatusRequest(method, path) {
898
+ if (method.toUpperCase() !== "GET") return false;
899
+ const pathname = path.split("?")[0];
900
+ return /^\/videos\/[^/]+\/?$/.test(pathname);
901
+ }
902
+ shouldRetryTransport(method, hasIdempotencyKey, attempt) {
903
+ return attempt < this.maxRetries && this.isRetrySafeRequest(method, hasIdempotencyKey);
904
+ }
905
+ isRetrySafeRequest(method, hasIdempotencyKey) {
906
+ const normalizedMethod = method.toUpperCase();
907
+ if (normalizedMethod === "GET" || normalizedMethod === "HEAD" || normalizedMethod === "OPTIONS") {
908
+ return true;
909
+ }
910
+ return hasIdempotencyKey;
911
+ }
912
+ isAbortError(err) {
913
+ return typeof err === "object" && err !== null && "name" in err && err.name === "AbortError";
914
+ }
915
+ isNetworkError(err) {
916
+ return err instanceof TypeError;
917
+ }
918
+ transportErrorMessage(err) {
919
+ if (err instanceof Error && err.message) return err.message;
920
+ return "Network request failed";
921
+ }
922
+ parseRetryAfter(value) {
923
+ if (!value) return void 0;
924
+ const trimmedValue = value.trim();
925
+ if (/^\d+$/.test(trimmedValue)) {
926
+ const seconds = Number(trimmedValue);
927
+ return Number.isSafeInteger(seconds) ? seconds : void 0;
928
+ }
929
+ const retryAtMs = Date.parse(trimmedValue);
930
+ if (!Number.isFinite(retryAtMs)) return void 0;
931
+ return Math.max(0, Math.ceil((retryAtMs - Date.now()) / 1e3));
932
+ }
933
+ responseRetryAfter(headers, body) {
934
+ return this.parseRetryAfter(this.readHeader(headers, "Retry-After")) ?? this.retryAfterFromErrorBody(body) ?? this.retryAfterFromDetails(body.details);
935
+ }
936
+ retryAfterFromErrorBody(body) {
937
+ return this.normalizeRetryAfterSeconds(
938
+ body.retry_after_seconds ?? body.retryAfterSeconds ?? body.retryAfter
939
+ );
940
+ }
941
+ retryAfterFromDetails(details) {
942
+ const directRetryAfter = this.normalizeRetryAfterSeconds(
943
+ details?.retry_after_seconds ?? details?.retryAfterSeconds ?? details?.retryAfter
944
+ );
945
+ if (directRetryAfter !== void 0) return directRetryAfter;
946
+ const nestedReadiness = details && typeof details === "object" ? details.video_generation ?? details.videoGeneration : void 0;
947
+ if (!nestedReadiness || typeof nestedReadiness !== "object") {
948
+ return void 0;
949
+ }
950
+ const readiness = nestedReadiness;
951
+ return this.normalizeRetryAfterSeconds(
952
+ readiness.retry_after_seconds ?? readiness.retryAfterSeconds ?? readiness.retryAfter
953
+ );
954
+ }
955
+ normalizeRetryAfterSeconds(value) {
956
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
957
+ return void 0;
958
+ }
959
+ return value;
960
+ }
961
+ retryDelay(attempt, retryAfterSeconds) {
962
+ if (retryAfterSeconds !== void 0) {
963
+ return retryAfterSeconds * 1e3;
964
+ }
965
+ return this.retryDelayMs * 2 ** attempt;
966
+ }
967
+ sleep(ms) {
968
+ if (ms <= 0) return Promise.resolve();
969
+ return new Promise((resolve) => setTimeout(resolve, ms));
970
+ }
971
+ responseHeaders(response) {
972
+ return response.headers ?? EMPTY_HEADERS;
973
+ }
974
+ readHeader(headers, name) {
975
+ const reader = headers;
976
+ return reader?.get?.(name) ?? reader?.get?.(name.toLowerCase()) ?? null;
977
+ }
978
+ readResponseHeader(headers, name) {
979
+ return headers?.get?.(name) ?? headers?.get?.(name.toLowerCase()) ?? null;
980
+ }
981
+ compactBody(body) {
982
+ return Object.fromEntries(
983
+ Object.entries(body).filter(([, value]) => value !== void 0)
984
+ );
985
+ }
986
+ normalizeIdempotencyKey(value) {
987
+ if (value === void 0) return void 0;
988
+ const key = value.trim();
989
+ if (!key) {
990
+ throw new PuppetryError(
991
+ 400,
992
+ "invalid_request",
993
+ "Idempotency-Key must not be blank"
994
+ );
995
+ }
996
+ if (key.length > IDEMPOTENCY_KEY_MAX_LENGTH) {
997
+ throw new PuppetryError(
998
+ 400,
999
+ "invalid_request",
1000
+ `Idempotency-Key must be ${IDEMPOTENCY_KEY_MAX_LENGTH} characters or fewer`
1001
+ );
1002
+ }
1003
+ return key;
1004
+ }
1005
+ idempotencyKeyFromParams(params) {
1006
+ const aliases = [
1007
+ { name: "idempotencyKey", value: params.idempotencyKey },
1008
+ { name: "idempotency_key", value: params.idempotency_key }
1009
+ ];
1010
+ const provided = aliases.filter((alias) => alias.value !== void 0 && alias.value !== null).map((alias) => {
1011
+ if (typeof alias.value !== "string") {
1012
+ throw new PuppetryError(
1013
+ 400,
1014
+ "invalid_request",
1015
+ `${alias.name} must be a string`
1016
+ );
1017
+ }
1018
+ return { ...alias, value: alias.value };
1019
+ });
1020
+ const first = provided[0];
1021
+ if (!first) return void 0;
1022
+ const conflictingAlias = provided.find(
1023
+ (alias) => alias.value !== first.value
1024
+ );
1025
+ if (conflictingAlias) {
1026
+ throw new PuppetryError(
1027
+ 400,
1028
+ "invalid_request",
1029
+ `Pass one of ${aliases.map((alias) => alias.name).join(", ")}, not conflicting values`
1030
+ );
1031
+ }
1032
+ return first.value;
1033
+ }
1034
+ resolveAlias(preferred, alias, preferredName, aliasName) {
1035
+ if (preferred && alias && preferred !== alias) {
1036
+ throw new PuppetryError(
1037
+ 400,
1038
+ "invalid_request",
1039
+ `Pass either ${preferredName} or ${aliasName}, not conflicting values`
1040
+ );
1041
+ }
1042
+ return preferred ?? alias;
1043
+ }
1044
+ resolveRequiredStringAliases(aliases) {
1045
+ const provided = aliases.flatMap((alias) => {
1046
+ if (alias.value === void 0 || alias.value === null) return [];
1047
+ if (typeof alias.value !== "string") {
1048
+ throw new PuppetryError(
1049
+ 400,
1050
+ "invalid_request",
1051
+ `${alias.name} must be a string`
1052
+ );
1053
+ }
1054
+ const value = alias.value.trim();
1055
+ if (!value) {
1056
+ throw new PuppetryError(
1057
+ 400,
1058
+ "invalid_request",
1059
+ `${alias.name} must not be blank`
1060
+ );
1061
+ }
1062
+ return [{ name: alias.name, value }];
1063
+ });
1064
+ const first = provided[0];
1065
+ if (!first) return void 0;
1066
+ const conflictingAlias = provided.find(
1067
+ (alias) => alias.value !== first.value
1068
+ );
1069
+ if (conflictingAlias) {
1070
+ throw new PuppetryError(
1071
+ 400,
1072
+ "invalid_request",
1073
+ `Pass one of ${aliases.map((alias) => alias.name).join(", ")}, not conflicting values`
1074
+ );
1075
+ }
1076
+ return first.value;
1077
+ }
1078
+ resolveOptionalStringAliases(aliases) {
1079
+ const provided = aliases.flatMap((alias) => {
1080
+ if (alias.value === void 0 || alias.value === null) return [];
1081
+ if (typeof alias.value !== "string") {
1082
+ throw new PuppetryError(
1083
+ 400,
1084
+ "invalid_request",
1085
+ `${alias.name} must be a string`
1086
+ );
1087
+ }
1088
+ const value = alias.value.trim();
1089
+ return value ? [{ name: alias.name, value }] : [];
1090
+ });
1091
+ const first = provided[0];
1092
+ if (!first) return void 0;
1093
+ const conflictingAlias = provided.find(
1094
+ (alias) => alias.value !== first.value
1095
+ );
1096
+ if (conflictingAlias) {
1097
+ throw new PuppetryError(
1098
+ 400,
1099
+ "invalid_request",
1100
+ `Pass one of ${aliases.map((alias) => alias.name).join(", ")}, not conflicting values`
1101
+ );
1102
+ }
1103
+ return first.value;
1104
+ }
1105
+ resolveUploadStringAliases(aliases) {
1106
+ const provided = aliases.flatMap((alias) => {
1107
+ if (alias.value === void 0 || alias.value === null) return [];
1108
+ if (typeof alias.value !== "string") {
1109
+ throw new PuppetryError(
1110
+ 400,
1111
+ "invalid_request",
1112
+ `${alias.name} must be a string`
1113
+ );
1114
+ }
1115
+ const value = alias.value.trim();
1116
+ return value ? [{ name: alias.name, value }] : [];
1117
+ });
1118
+ const first = provided[0];
1119
+ if (!first) return void 0;
1120
+ const conflictingAlias = provided.find(
1121
+ (alias) => alias.value !== first.value
1122
+ );
1123
+ if (conflictingAlias) {
1124
+ throw new PuppetryError(
1125
+ 400,
1126
+ "invalid_request",
1127
+ `Pass one of ${aliases.map((alias) => alias.name).join(", ")}, not conflicting values`
1128
+ );
1129
+ }
1130
+ return first.value;
1131
+ }
1132
+ resolveNumberAliases(aliases) {
1133
+ const provided = aliases.flatMap((alias) => {
1134
+ if (alias.value === void 0 || alias.value === null) return [];
1135
+ if (typeof alias.value === "string" && alias.value.trim() === "") {
1136
+ return [];
1137
+ }
1138
+ if (typeof alias.value !== "number") {
1139
+ throw new PuppetryError(
1140
+ 400,
1141
+ "invalid_request",
1142
+ `${alias.name} must be a number`
1143
+ );
1144
+ }
1145
+ return [{ name: alias.name, value: alias.value }];
1146
+ });
1147
+ const first = provided[0];
1148
+ if (!first) return void 0;
1149
+ const conflictingAlias = provided.find(
1150
+ (alias) => alias.value !== first.value
1151
+ );
1152
+ if (conflictingAlias) {
1153
+ throw new PuppetryError(
1154
+ 400,
1155
+ "invalid_request",
1156
+ `Pass one of ${aliases.map((alias) => alias.name).join(", ")}, not conflicting values`
1157
+ );
1158
+ }
1159
+ return first.value;
1160
+ }
1161
+ firstDefinedNullable(...values) {
1162
+ for (const value of values) {
1163
+ if (value !== void 0 && value !== null) return value;
1164
+ }
1165
+ return values.some((value) => value === null) ? null : void 0;
1166
+ }
1167
+ textVideoBody(params) {
1168
+ return this.compactBody({
1169
+ image_url: this.resolveRequiredStringAliases([
1170
+ { name: "image_url", value: params.image_url },
1171
+ { name: "imageUrl", value: params.imageUrl },
1172
+ { name: "image", value: params.image },
1173
+ { name: "portrait", value: params.portrait },
1174
+ { name: "portrait_url", value: params.portrait_url },
1175
+ { name: "portraitUrl", value: params.portraitUrl },
1176
+ { name: "avatar", value: params.avatar },
1177
+ { name: "avatarUrl", value: params.avatarUrl },
1178
+ { name: "avatar_url", value: params.avatar_url },
1179
+ { name: "photo", value: params.photo },
1180
+ { name: "photoUrl", value: params.photoUrl },
1181
+ { name: "photo_url", value: params.photo_url }
1182
+ ]),
1183
+ text: this.resolveRequiredStringAliases([
1184
+ { name: "text", value: params.text },
1185
+ { name: "script", value: params.script },
1186
+ { name: "prompt", value: params.prompt }
1187
+ ]),
1188
+ voice_id: this.resolveOptionalStringAliases([
1189
+ { name: "voice_id", value: params.voice_id },
1190
+ { name: "voiceId", value: params.voiceId },
1191
+ { name: "voice", value: params.voice }
1192
+ ]),
1193
+ language: params.language,
1194
+ expressiveness: params.expressiveness
1195
+ });
1196
+ }
1197
+ audioVideoBody(params) {
1198
+ return this.compactBody({
1199
+ image_url: this.resolveRequiredStringAliases([
1200
+ { name: "image_url", value: params.image_url },
1201
+ { name: "imageUrl", value: params.imageUrl },
1202
+ { name: "image", value: params.image },
1203
+ { name: "portrait", value: params.portrait },
1204
+ { name: "portrait_url", value: params.portrait_url },
1205
+ { name: "portraitUrl", value: params.portraitUrl },
1206
+ { name: "avatar", value: params.avatar },
1207
+ { name: "avatarUrl", value: params.avatarUrl },
1208
+ { name: "avatar_url", value: params.avatar_url },
1209
+ { name: "photo", value: params.photo },
1210
+ { name: "photoUrl", value: params.photoUrl },
1211
+ { name: "photo_url", value: params.photo_url }
1212
+ ]),
1213
+ audio_url: this.resolveRequiredStringAliases([
1214
+ { name: "audio_url", value: params.audio_url },
1215
+ { name: "audioUrl", value: params.audioUrl },
1216
+ { name: "audio", value: params.audio }
1217
+ ]),
1218
+ expressiveness: params.expressiveness
1219
+ });
1220
+ }
1221
+ videoJobPath(jobId) {
1222
+ return `/videos/${encodeURIComponent(jobId)}`;
1223
+ }
1224
+ normalizeVideoJob(job, headers) {
1225
+ const jobId = job.id ?? job.jobId ?? job.job_id ?? job.task_id ?? job.taskId;
1226
+ if (!jobId) {
1227
+ throw new PuppetryError(
1228
+ 502,
1229
+ "invalid_response",
1230
+ "Puppetry API returned a video job without an id"
1231
+ );
1232
+ }
1233
+ const retryAfter = job.retryAfter ?? job.retryAfterSeconds ?? job.retry_after_seconds ?? this.parseRetryAfter(this.readResponseHeader(headers, "Retry-After"));
1234
+ const nextPollAt = job.nextPollAt ?? job.next_poll_at ?? this.videoNextPollAtHeader(headers);
1235
+ const videoUrl = this.firstDefinedNullable(
1236
+ job.videoUrl,
1237
+ job.video_url,
1238
+ job.resultUrl,
1239
+ job.result_url,
1240
+ job.downloadUrl,
1241
+ job.download_url,
1242
+ job.outputUrl,
1243
+ job.output_url,
1244
+ job.url
1245
+ );
1246
+ const durationSeconds = this.firstDefinedNullable(
1247
+ job.durationSeconds,
1248
+ job.duration_seconds,
1249
+ job.duration
1250
+ );
1251
+ const retryable = typeof job.retryable === "boolean" ? job.retryable : void 0;
1252
+ const retryBlockedReason = job.retryBlockedReason ?? job.retry_blocked_reason;
1253
+ const retryBlockCode = job.retryBlockCode ?? job.retry_block_code;
1254
+ const diagnostics = job.diagnostics ?? this.videoJobResultDiagnostics(job.result);
1255
+ const createdAt = job.createdAt ?? job.created_at;
1256
+ const expiresAt = job.expiresAt ?? job.expires_at;
1257
+ const completedAt = this.firstDefinedNullable(
1258
+ job.completedAt,
1259
+ job.completed_at
1260
+ );
1261
+ const thumbnailUrl = this.firstDefinedNullable(
1262
+ job.thumbnailUrl,
1263
+ job.thumbnail_url
1264
+ );
1265
+ const { statusUrl, contentLocation } = this.videoJobPollLocationAliases(job, headers);
1266
+ const statusCredits = this.videoStatusCredits(job.credits);
1267
+ const creationCredits = this.videoCreationCredits(job, headers);
1268
+ const operationId = job.operationId ?? job.operation_id ?? this.videoOperationIdHeader(headers);
1269
+ const requestId = job.requestId ?? job.request_id ?? this.videoRequestIdHeader(headers);
1270
+ const idempotentReplay = job.idempotentReplay ?? job.idempotent_replay;
1271
+ const source = normalizeVideoJobSource(job.source) ?? normalizeVideoJobSource(job.request_source) ?? normalizeVideoJobSource(job.requestSource);
1272
+ const status = normalizeVideoStatusAlias(job.status) ?? normalizeVideoStatusAlias(job.state) ?? normalizeVideoStatusAlias(job.task_status) ?? normalizeVideoStatusAlias(job.taskStatus) ?? "processing";
1273
+ const progress = this.videoProgress(job);
1274
+ const {
1275
+ credits: _credits,
1276
+ creation_credits: _creationCredits,
1277
+ creationCredits: _creationCreditsCamel,
1278
+ ...jobWithoutCreditAliases
1279
+ } = job;
1280
+ return this.attachVideoJobHelpers({
1281
+ ...jobWithoutCreditAliases,
1282
+ id: jobId,
1283
+ job_id: job.job_id ?? jobId,
1284
+ jobId: job.jobId ?? jobId,
1285
+ operation_id: job.operation_id ?? operationId ?? jobId,
1286
+ operationId: job.operationId ?? operationId ?? jobId,
1287
+ ...requestId !== void 0 ? { request_id: requestId, requestId } : {},
1288
+ task_id: job.task_id ?? jobId,
1289
+ taskId: job.taskId ?? jobId,
1290
+ status,
1291
+ state: normalizeVideoStatusAlias(job.state) ?? status,
1292
+ task_status: normalizeVideoStatusAlias(job.task_status) ?? status,
1293
+ taskStatus: normalizeVideoStatusAlias(job.taskStatus) ?? status,
1294
+ ...source !== void 0 ? { source, request_source: source, requestSource: source } : {},
1295
+ ...progress !== void 0 ? {
1296
+ progress,
1297
+ percent: job.percent ?? progress,
1298
+ percentage: job.percentage ?? progress,
1299
+ progress_percent: job.progress_percent ?? progress,
1300
+ progressPercent: job.progressPercent ?? progress
1301
+ } : {},
1302
+ ...createdAt !== void 0 ? { created_at: createdAt, createdAt } : {},
1303
+ ...expiresAt !== void 0 ? { expires_at: expiresAt, expiresAt } : {},
1304
+ ...completedAt !== void 0 ? { completed_at: completedAt, completedAt } : {},
1305
+ ...videoUrl !== void 0 ? {
1306
+ url: videoUrl,
1307
+ video_url: videoUrl,
1308
+ videoUrl,
1309
+ result_url: videoUrl,
1310
+ resultUrl: videoUrl,
1311
+ download_url: videoUrl,
1312
+ downloadUrl: videoUrl,
1313
+ output_url: videoUrl,
1314
+ outputUrl: videoUrl
1315
+ } : {},
1316
+ ...thumbnailUrl !== void 0 ? { thumbnail_url: thumbnailUrl, thumbnailUrl } : {},
1317
+ duration: job.duration ?? durationSeconds,
1318
+ duration_seconds: job.duration_seconds ?? durationSeconds,
1319
+ durationSeconds,
1320
+ ...statusUrl !== void 0 ? { status_url: statusUrl, statusUrl } : {},
1321
+ ...contentLocation !== void 0 ? { content_location: contentLocation, contentLocation } : {},
1322
+ ...statusCredits !== void 0 ? { credits: statusCredits } : {},
1323
+ ...creationCredits !== void 0 ? { creation_credits: creationCredits, creationCredits } : {},
1324
+ ...idempotentReplay !== void 0 ? { idempotent_replay: idempotentReplay, idempotentReplay } : {},
1325
+ ...retryable !== void 0 ? { retryable } : {},
1326
+ ...retryBlockedReason ? {
1327
+ retry_blocked_reason: retryBlockedReason,
1328
+ retryBlockedReason
1329
+ } : {},
1330
+ ...retryBlockCode ? {
1331
+ retry_block_code: retryBlockCode,
1332
+ retryBlockCode
1333
+ } : {},
1334
+ ...diagnostics ? { diagnostics } : {},
1335
+ ...retryAfter !== void 0 ? { retryAfter } : {},
1336
+ ...retryAfter !== void 0 ? { retry_after_seconds: retryAfter } : {},
1337
+ ...retryAfter !== void 0 ? { retryAfterSeconds: retryAfter } : {},
1338
+ ...nextPollAt !== void 0 ? { next_poll_at: nextPollAt, nextPollAt } : {}
1339
+ });
1340
+ }
1341
+ videoJobStatusUrlHeader(headers) {
1342
+ return this.videoLocationHeader(headers) ?? this.videoContentLocationHeader(headers);
1343
+ }
1344
+ videoJobPollLocationAliases(job, headers) {
1345
+ const bodyStatusUrl = job.statusUrl ?? job.status_url;
1346
+ const bodyContentLocation = job.contentLocation ?? job.content_location;
1347
+ const headerLocation = this.videoLocationHeader(headers);
1348
+ const headerContentLocation = this.videoContentLocationHeader(headers);
1349
+ return {
1350
+ statusUrl: bodyStatusUrl ?? bodyContentLocation ?? headerLocation ?? headerContentLocation,
1351
+ contentLocation: bodyContentLocation ?? bodyStatusUrl ?? headerContentLocation ?? headerLocation
1352
+ };
1353
+ }
1354
+ videoLocationHeader(headers) {
1355
+ const value = headers?.get("Location") ?? headers?.get("location");
1356
+ if (!value) return void 0;
1357
+ const trimmed = value.trim();
1358
+ return trimmed ? trimmed : void 0;
1359
+ }
1360
+ videoContentLocationHeader(headers) {
1361
+ const value = headers?.get("Content-Location") ?? headers?.get("content-location");
1362
+ if (!value) return void 0;
1363
+ const trimmed = value.trim();
1364
+ return trimmed ? trimmed : void 0;
1365
+ }
1366
+ videoOperationIdHeader(headers) {
1367
+ const value = headers?.get("Puppetry-Operation-Id") ?? headers?.get("puppetry-operation-id") ?? headers?.get("X-Puppetry-Operation-Id") ?? headers?.get("x-puppetry-operation-id");
1368
+ if (!value) return void 0;
1369
+ const trimmed = value.trim();
1370
+ return trimmed ? trimmed : void 0;
1371
+ }
1372
+ videoRequestIdHeader(headers) {
1373
+ const value = headers?.get("Puppetry-Request-Id") ?? headers?.get("puppetry-request-id") ?? headers?.get("X-Puppetry-Request-Id") ?? headers?.get("x-puppetry-request-id");
1374
+ if (!value) return void 0;
1375
+ const trimmed = value.trim();
1376
+ return trimmed ? trimmed : void 0;
1377
+ }
1378
+ videoNextPollAtHeader(headers) {
1379
+ const value = headers?.get("X-Puppetry-Next-Poll-At") ?? headers?.get("x-puppetry-next-poll-at");
1380
+ if (!value) return void 0;
1381
+ const trimmed = value.trim();
1382
+ return trimmed ? trimmed : void 0;
1383
+ }
1384
+ videoStatusErrorOptions(method, path, headers, retryAfter) {
1385
+ const statusUrl = this.videoJobStatusUrlHeader(headers);
1386
+ const contentLocation = this.videoContentLocationHeader(headers);
1387
+ const jobId = this.videoJobIdFromStatusUrl(statusUrl ?? contentLocation);
1388
+ const operationId = this.videoOperationIdHeader(headers);
1389
+ const requestId = this.videoRequestIdHeader(headers);
1390
+ const nextPollAt = this.videoNextPollAtHeader(headers);
1391
+ const creationCredits = this.isVideoStatusRequest(method, path) ? this.videoCreationCreditsFromHeaders(headers) : void 0;
1392
+ return {
1393
+ retryAfter,
1394
+ ...jobId ? { id: jobId, job_id: jobId, jobId } : {},
1395
+ ...statusUrl ? { status_url: statusUrl, statusUrl } : {},
1396
+ ...contentLocation ? { content_location: contentLocation, contentLocation } : {},
1397
+ ...operationId ? { operation_id: operationId, operationId } : {},
1398
+ ...requestId ? { request_id: requestId, requestId } : {},
1399
+ ...nextPollAt ? { next_poll_at: nextPollAt, nextPollAt } : {},
1400
+ ...creationCredits ? { creation_credits: creationCredits, creationCredits } : {}
1401
+ };
1402
+ }
1403
+ videoCreditErrorOptions(headers) {
1404
+ const requiredCredits = this.videoCreditHeaderNumber(
1405
+ headers,
1406
+ "X-Puppetry-Credits-Required"
1407
+ );
1408
+ const creditsRemaining = this.videoCreditHeaderNumber(
1409
+ headers,
1410
+ "X-Puppetry-Credits-Remaining"
1411
+ );
1412
+ return {
1413
+ ...requiredCredits !== void 0 ? { required_credits: requiredCredits, requiredCredits } : {},
1414
+ ...creditsRemaining !== void 0 ? {
1415
+ credits_remaining: creditsRemaining,
1416
+ creditsRemaining,
1417
+ remaining_credits: creditsRemaining,
1418
+ remainingCredits: creditsRemaining
1419
+ } : {}
1420
+ };
1421
+ }
1422
+ videoCreditHeaderNumber(headers, name) {
1423
+ const raw = this.readResponseHeader(headers, name);
1424
+ if (raw === null) return void 0;
1425
+ const trimmed = raw.trim();
1426
+ if (!/^\d+$/.test(trimmed)) return void 0;
1427
+ const value = Number(trimmed);
1428
+ return Number.isSafeInteger(value) ? value : void 0;
1429
+ }
1430
+ videoJobIdFromStatusUrl(statusUrl) {
1431
+ if (!statusUrl) return void 0;
1432
+ try {
1433
+ const url = new URL(statusUrl, this.baseUrl);
1434
+ const match = url.pathname.match(/\/videos\/([^/]+)\/?$/);
1435
+ if (!match) return void 0;
1436
+ return decodeURIComponent(match[1]);
1437
+ } catch {
1438
+ return void 0;
1439
+ }
1440
+ }
1441
+ retryableStatusErrorJobId(err) {
1442
+ return err.jobId ?? this.videoJobIdFromStatusUrl(err.statusUrl ?? err.status_url);
1443
+ }
1444
+ videoJobResultDiagnostics(result) {
1445
+ if (!result || typeof result !== "object" || Array.isArray(result)) {
1446
+ return void 0;
1447
+ }
1448
+ return result.diagnostics;
1449
+ }
1450
+ videoStatusCredits(credits) {
1451
+ if (!credits || typeof credits !== "object") return void 0;
1452
+ if (typeof credits.charged !== "number" || !Number.isFinite(credits.charged)) {
1453
+ return void 0;
1454
+ }
1455
+ return {
1456
+ charged: credits.charged,
1457
+ remaining: typeof credits.remaining === "number" && Number.isFinite(credits.remaining) ? credits.remaining : null
1458
+ };
1459
+ }
1460
+ videoCreationCredits(job, headers) {
1461
+ const raw = job.creationCredits ?? job.creation_credits;
1462
+ if (raw && typeof raw === "object") {
1463
+ if (typeof raw.charged !== "number" || !Number.isFinite(raw.charged)) {
1464
+ return this.videoCreationCreditsFromHeaders(headers);
1465
+ }
1466
+ const remainingAfterDebit = raw.remainingAfterDebit ?? raw.remaining_after_debit ?? null;
1467
+ const normalizedRemainingAfterDebit = typeof remainingAfterDebit === "number" && Number.isFinite(remainingAfterDebit) ? remainingAfterDebit : null;
1468
+ return {
1469
+ charged: raw.charged,
1470
+ remaining_after_debit: normalizedRemainingAfterDebit,
1471
+ remainingAfterDebit: normalizedRemainingAfterDebit
1472
+ };
1473
+ }
1474
+ return this.videoCreationCreditsFromHeaders(headers);
1475
+ }
1476
+ videoCreationCreditsFromHeaders(headers) {
1477
+ const charged = this.videoCreditHeaderNumber(
1478
+ headers,
1479
+ "X-Puppetry-Credits-Required"
1480
+ );
1481
+ if (charged === void 0) return void 0;
1482
+ const remainingAfterDebit = this.videoCreditHeaderNumber(
1483
+ headers,
1484
+ "X-Puppetry-Credits-Remaining"
1485
+ ) ?? null;
1486
+ return {
1487
+ charged,
1488
+ remaining_after_debit: remainingAfterDebit,
1489
+ remainingAfterDebit
1490
+ };
1491
+ }
1492
+ videoProgress(job) {
1493
+ const progress = job.progress ?? job.percent ?? job.percentage ?? job.progressPercent ?? job.progress_percent;
1494
+ return typeof progress === "number" || progress === null ? progress : void 0;
1495
+ }
1496
+ attachVideoJobHelpers(job) {
1497
+ const sdkJob = job;
1498
+ Object.defineProperty(sdkJob, "waitForCompletion", {
1499
+ value: (options) => {
1500
+ if (sdkJob.status === "completed" || sdkJob.status === "failed") {
1501
+ return Promise.resolve(sdkJob);
1502
+ }
1503
+ return this.videos.waitForCompletion(
1504
+ this.videoJobIdFromStatusUrl(sdkJob.statusUrl ?? sdkJob.status_url) ?? sdkJob.id,
1505
+ this.waitOptionsWithInitialDelay(
1506
+ options,
1507
+ sdkJob.retryAfter,
1508
+ sdkJob.nextPollAt ?? sdkJob.next_poll_at
1509
+ )
1510
+ );
1511
+ },
1512
+ enumerable: false,
1513
+ configurable: true
1514
+ });
1515
+ return sdkJob;
1516
+ }
1517
+ waitOptionsWithInitialDelay(options, retryAfterSeconds, nextPollAt) {
1518
+ if (options?.initialDelayMs !== void 0) return options;
1519
+ const initialDelayMs = this.retryAfterDelayMs(retryAfterSeconds) ?? this.nextPollDelayMs(nextPollAt);
1520
+ return initialDelayMs === void 0 ? options : { ...options, initialDelayMs };
1521
+ }
1522
+ retryAfterDelayMs(value) {
1523
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
1524
+ return void 0;
1525
+ }
1526
+ return value * 1e3;
1527
+ }
1528
+ optionDelayMs(value) {
1529
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
1530
+ return void 0;
1531
+ }
1532
+ return value;
1533
+ }
1534
+ nextPollDelayMs(value) {
1535
+ if (typeof value !== "string" || value.trim() === "") {
1536
+ return void 0;
1537
+ }
1538
+ const timestamp = Date.parse(value);
1539
+ if (!Number.isFinite(timestamp)) {
1540
+ return void 0;
1541
+ }
1542
+ return Math.max(timestamp - Date.now(), 0);
1543
+ }
1544
+ async sleepWithinDeadline(ms, deadline) {
1545
+ const remainingMs = Math.max(deadline - Date.now(), 0);
1546
+ await this.sleep(Math.min(ms, remainingMs));
1547
+ }
1548
+ pollingDelayMs(job, headers, intervalMs) {
1549
+ const retryAfterSeconds = this.parseRetryAfter(this.readResponseHeader(headers, "Retry-After")) ?? job.retryAfter;
1550
+ const retryAfterDelayMs = this.retryAfterDelayMs(retryAfterSeconds);
1551
+ if (retryAfterDelayMs !== void 0) {
1552
+ return retryAfterDelayMs;
1553
+ }
1554
+ return this.nextPollDelayMs(job.nextPollAt ?? job.next_poll_at) ?? intervalMs;
1555
+ }
1556
+ retryableStatusErrorDelayMs(err, intervalMs) {
1557
+ if (err.retryable !== true) return void 0;
1558
+ return this.retryAfterDelayMs(err.retryAfter) ?? this.nextPollDelayMs(err.nextPollAt ?? err.next_poll_at) ?? intervalMs;
1559
+ }
1560
+ normalizeUsage(usage) {
1561
+ const videoCredits = usage.video_credits ?? usage.videoCredits;
1562
+ if (!videoCredits) {
1563
+ return usage;
1564
+ }
1565
+ const rawCurrentApiKey = videoCredits.current_api_key ?? videoCredits.currentApiKey ?? {
1566
+ used: 0,
1567
+ refunded: 0,
1568
+ net_used: 0,
1569
+ transactions: 0
1570
+ };
1571
+ const currentApiKeyNetUsed = rawCurrentApiKey.net_used ?? rawCurrentApiKey.netUsed ?? 0;
1572
+ const currentApiKey = {
1573
+ ...rawCurrentApiKey,
1574
+ keyId: rawCurrentApiKey.keyId ?? rawCurrentApiKey.key_id,
1575
+ keyPrefix: rawCurrentApiKey.keyPrefix ?? rawCurrentApiKey.key_prefix,
1576
+ net_used: currentApiKeyNetUsed,
1577
+ netUsed: rawCurrentApiKey.netUsed ?? currentApiKeyNetUsed
1578
+ };
1579
+ const videoCreditNetUsed = videoCredits.net_used ?? videoCredits.netUsed ?? 0;
1580
+ const resetAt = videoCredits.reset_at ?? videoCredits.resetAt;
1581
+ const normalizedVideoCredits = {
1582
+ ...videoCredits,
1583
+ reset_at: resetAt,
1584
+ resetAt: videoCredits.resetAt ?? resetAt,
1585
+ net_used: videoCreditNetUsed,
1586
+ netUsed: videoCredits.netUsed ?? videoCreditNetUsed,
1587
+ current_api_key: currentApiKey,
1588
+ currentApiKey: videoCredits.currentApiKey ?? currentApiKey
1589
+ };
1590
+ const videoGeneration = this.normalizeVideoGenerationReadiness(
1591
+ usage.video_generation ?? usage.videoGeneration,
1592
+ videoCredits.balance
1593
+ );
1594
+ return {
1595
+ ...usage,
1596
+ video_credits: normalizedVideoCredits,
1597
+ videoCredits: usage.videoCredits ?? normalizedVideoCredits,
1598
+ video_generation: videoGeneration,
1599
+ videoGeneration: usage.videoGeneration ?? videoGeneration,
1600
+ plan: usage.plan ?? "developer_api",
1601
+ creditsTotal: usage.creditsTotal ?? Math.max(
1602
+ videoCredits.balance + videoCreditNetUsed,
1603
+ videoCredits.balance
1604
+ ),
1605
+ creditsUsed: usage.creditsUsed ?? videoCreditNetUsed,
1606
+ creditsRemaining: usage.creditsRemaining ?? videoCredits.balance,
1607
+ periodStart: usage.periodStart ?? `${videoCredits.month}-01`,
1608
+ periodEnd: usage.periodEnd ?? resetAt
1609
+ };
1610
+ }
1611
+ normalizeVideoGenerationReadiness(readiness, balance) {
1612
+ const requiredCredits = readiness?.required_credits ?? readiness?.requiredCredits ?? 1;
1613
+ const creditsRemaining = readiness?.credits_remaining ?? readiness?.creditsRemaining ?? balance;
1614
+ const concurrentJobs = readiness?.concurrent_jobs ?? readiness?.concurrentJobs;
1615
+ const activeJobs = readiness?.active_jobs ?? readiness?.activeJobs;
1616
+ const availableSlots = readiness?.available_slots ?? readiness?.availableSlots;
1617
+ const activeJobExpiresInSeconds = readiness?.active_job_expires_in_seconds ?? readiness?.activeJobExpiresInSeconds;
1618
+ const canCreate = readiness?.can_create ?? readiness?.canCreate ?? (creditsRemaining >= requiredCredits && (availableSlots === void 0 || availableSlots > 0));
1619
+ const reason = readiness?.reason ?? (creditsRemaining < requiredCredits ? "insufficient_video_credits" : availableSlots !== void 0 && availableSlots <= 0 ? "concurrent_job_limit_reached" : "available");
1620
+ const retryAfterSeconds = readiness?.retry_after_seconds ?? readiness?.retryAfterSeconds ?? readiness?.retryAfter ?? (reason === "concurrent_job_limit_reached" ? activeJobExpiresInSeconds : void 0);
1621
+ const nextPollAt = readiness?.nextPollAt ?? readiness?.next_poll_at;
1622
+ const blockers = this.normalizeVideoGenerationBlockers(
1623
+ readiness?.blockers,
1624
+ {
1625
+ reason,
1626
+ requiredCredits,
1627
+ creditsRemaining,
1628
+ concurrentJobs,
1629
+ activeJobs,
1630
+ availableSlots,
1631
+ activeJobExpiresInSeconds,
1632
+ retryAfterSeconds
1633
+ }
1634
+ );
1635
+ return {
1636
+ can_create: canCreate,
1637
+ canCreate: readiness?.canCreate ?? canCreate,
1638
+ required_credits: requiredCredits,
1639
+ requiredCredits: readiness?.requiredCredits ?? requiredCredits,
1640
+ credits_remaining: creditsRemaining,
1641
+ creditsRemaining: readiness?.creditsRemaining ?? creditsRemaining,
1642
+ ...concurrentJobs === void 0 ? {} : {
1643
+ concurrent_jobs: concurrentJobs,
1644
+ concurrentJobs: readiness?.concurrentJobs ?? concurrentJobs
1645
+ },
1646
+ ...activeJobs === void 0 ? {} : {
1647
+ active_jobs: activeJobs,
1648
+ activeJobs: readiness?.activeJobs ?? activeJobs
1649
+ },
1650
+ ...availableSlots === void 0 ? {} : {
1651
+ available_slots: availableSlots,
1652
+ availableSlots: readiness?.availableSlots ?? availableSlots
1653
+ },
1654
+ ...activeJobExpiresInSeconds === void 0 ? {} : {
1655
+ active_job_expires_in_seconds: activeJobExpiresInSeconds,
1656
+ activeJobExpiresInSeconds: readiness?.activeJobExpiresInSeconds ?? activeJobExpiresInSeconds
1657
+ },
1658
+ ...retryAfterSeconds === void 0 ? {} : {
1659
+ retry_after_seconds: retryAfterSeconds,
1660
+ retryAfter: readiness?.retryAfter ?? retryAfterSeconds,
1661
+ retryAfterSeconds: readiness?.retryAfterSeconds ?? retryAfterSeconds
1662
+ },
1663
+ ...nextPollAt === void 0 ? {} : { next_poll_at: nextPollAt, nextPollAt },
1664
+ ...blockers.length === 0 ? {} : { blockers },
1665
+ reason
1666
+ };
1667
+ }
1668
+ normalizeVideoGenerationBlockers(blockers, fallback) {
1669
+ if (Array.isArray(blockers) && blockers.length > 0) {
1670
+ return blockers.map(
1671
+ (blocker) => this.normalizeVideoGenerationBlocker(blocker)
1672
+ );
1673
+ }
1674
+ return this.derivedVideoGenerationBlockers(fallback);
1675
+ }
1676
+ normalizeVideoGenerationBlocker(blocker) {
1677
+ const record = blocker;
1678
+ const code = this.videoGenerationReadinessReason(record.code) ?? this.videoGenerationReadinessReason(record.reason);
1679
+ if (code === "insufficient_video_credits") {
1680
+ const requiredCredits = this.readinessNumber(record.required_credits) ?? this.readinessNumber(record.requiredCredits) ?? 1;
1681
+ const creditsRemaining = this.readinessNumber(record.credits_remaining) ?? this.readinessNumber(record.creditsRemaining) ?? 0;
1682
+ const retryBlockCode = this.readinessString(record.retry_block_code) ?? this.readinessString(record.retryBlockCode);
1683
+ return {
1684
+ code,
1685
+ reason: code,
1686
+ retryable: false,
1687
+ required_credits: requiredCredits,
1688
+ requiredCredits,
1689
+ credits_remaining: creditsRemaining,
1690
+ creditsRemaining,
1691
+ ...retryBlockCode === void 0 ? {} : {
1692
+ retry_block_code: retryBlockCode,
1693
+ retryBlockCode
1694
+ }
1695
+ };
1696
+ }
1697
+ const retryAfterSeconds = this.readinessNumber(record.retry_after_seconds) ?? this.readinessNumber(record.retryAfterSeconds) ?? this.readinessNumber(record.retryAfter);
1698
+ const nextPollAt = this.readinessString(record.nextPollAt) ?? this.readinessString(record.next_poll_at);
1699
+ return {
1700
+ code: "concurrent_job_limit_reached",
1701
+ reason: "concurrent_job_limit_reached",
1702
+ retryable: true,
1703
+ ...this.activeJobReadinessAliases(record),
1704
+ ...retryAfterSeconds === void 0 ? {} : {
1705
+ retry_after_seconds: retryAfterSeconds,
1706
+ retryAfter: retryAfterSeconds,
1707
+ retryAfterSeconds
1708
+ },
1709
+ ...nextPollAt === void 0 ? {} : { next_poll_at: nextPollAt, nextPollAt }
1710
+ };
1711
+ }
1712
+ derivedVideoGenerationBlockers(params) {
1713
+ const blockers = [];
1714
+ if (params.reason === "insufficient_video_credits" || params.creditsRemaining < params.requiredCredits) {
1715
+ blockers.push({
1716
+ code: "insufficient_video_credits",
1717
+ reason: "insufficient_video_credits",
1718
+ retryable: false,
1719
+ required_credits: params.requiredCredits,
1720
+ requiredCredits: params.requiredCredits,
1721
+ credits_remaining: params.creditsRemaining,
1722
+ creditsRemaining: params.creditsRemaining
1723
+ });
1724
+ }
1725
+ if (params.reason === "concurrent_job_limit_reached" || params.availableSlots !== void 0 && params.availableSlots <= 0) {
1726
+ blockers.push({
1727
+ code: "concurrent_job_limit_reached",
1728
+ reason: "concurrent_job_limit_reached",
1729
+ retryable: true,
1730
+ ...params.concurrentJobs === void 0 ? {} : {
1731
+ concurrent_jobs: params.concurrentJobs,
1732
+ concurrentJobs: params.concurrentJobs
1733
+ },
1734
+ ...params.activeJobs === void 0 ? {} : {
1735
+ active_jobs: params.activeJobs,
1736
+ activeJobs: params.activeJobs
1737
+ },
1738
+ ...params.availableSlots === void 0 ? {} : {
1739
+ available_slots: params.availableSlots,
1740
+ availableSlots: params.availableSlots
1741
+ },
1742
+ ...params.activeJobExpiresInSeconds === void 0 ? {} : {
1743
+ active_job_expires_in_seconds: params.activeJobExpiresInSeconds,
1744
+ activeJobExpiresInSeconds: params.activeJobExpiresInSeconds
1745
+ },
1746
+ ...params.retryAfterSeconds === void 0 ? {} : {
1747
+ retry_after_seconds: params.retryAfterSeconds,
1748
+ retryAfter: params.retryAfterSeconds,
1749
+ retryAfterSeconds: params.retryAfterSeconds
1750
+ }
1751
+ });
1752
+ }
1753
+ return blockers;
1754
+ }
1755
+ activeJobReadinessAliases(record) {
1756
+ const concurrentJobs = this.readinessNumber(record.concurrent_jobs) ?? this.readinessNumber(record.concurrentJobs);
1757
+ const activeJobs = this.readinessNumber(record.active_jobs) ?? this.readinessNumber(record.activeJobs);
1758
+ const availableSlots = this.readinessNumber(record.available_slots) ?? this.readinessNumber(record.availableSlots);
1759
+ const activeJobExpiresInSeconds = this.readinessNumber(record.active_job_expires_in_seconds) ?? this.readinessNumber(record.activeJobExpiresInSeconds);
1760
+ return {
1761
+ ...concurrentJobs === void 0 ? {} : { concurrent_jobs: concurrentJobs, concurrentJobs },
1762
+ ...activeJobs === void 0 ? {} : { active_jobs: activeJobs, activeJobs },
1763
+ ...availableSlots === void 0 ? {} : { available_slots: availableSlots, availableSlots },
1764
+ ...activeJobExpiresInSeconds === void 0 ? {} : {
1765
+ active_job_expires_in_seconds: activeJobExpiresInSeconds,
1766
+ activeJobExpiresInSeconds
1767
+ }
1768
+ };
1769
+ }
1770
+ videoGenerationReadinessReason(value) {
1771
+ return value === "available" || value === "insufficient_video_credits" || value === "concurrent_job_limit_reached" ? value : void 0;
1772
+ }
1773
+ readinessNumber(value) {
1774
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
1775
+ }
1776
+ readinessString(value) {
1777
+ return typeof value === "string" && value ? value : void 0;
1778
+ }
1779
+ async getVideoGenerationReadiness() {
1780
+ const usage = await this.usage.get();
1781
+ const readiness = usage.video_generation ?? usage.videoGeneration;
1782
+ if (!readiness) {
1783
+ throw new PuppetryError(
1784
+ 502,
1785
+ "invalid_response",
1786
+ "Puppetry API usage response did not include video_generation readiness"
1787
+ );
1788
+ }
1789
+ return readiness;
1790
+ }
1791
+ async ensureVideoGenerationReady() {
1792
+ const readiness = await this.getVideoGenerationReadiness();
1793
+ if (readiness.can_create !== false) return readiness;
1794
+ const retryAfter = readiness.retry_after_seconds ?? readiness.retryAfterSeconds ?? readiness.retryAfter ?? readiness.active_job_expires_in_seconds ?? readiness.activeJobExpiresInSeconds;
1795
+ const nextPollAt = readiness.nextPollAt ?? readiness.next_poll_at;
1796
+ const details = { video_generation: readiness };
1797
+ const errorOptions = this.videoGenerationReadinessErrorOptions(readiness);
1798
+ if (readiness.reason === "concurrent_job_limit_reached") {
1799
+ throw new PuppetryRateLimitError(
1800
+ retryAfter,
1801
+ "Video generation is at the active job limit",
1802
+ details,
1803
+ "rate_limited",
1804
+ {
1805
+ ...errorOptions,
1806
+ ...nextPollAt === void 0 ? {} : { next_poll_at: nextPollAt, nextPollAt }
1807
+ }
1808
+ );
1809
+ }
1810
+ throw new PuppetryError(
1811
+ 402,
1812
+ readiness.reason,
1813
+ "Video generation is not ready for this API key",
1814
+ details,
1815
+ {
1816
+ ...errorOptions,
1817
+ retryAfter,
1818
+ ...nextPollAt === void 0 ? {} : { next_poll_at: nextPollAt, nextPollAt }
1819
+ }
1820
+ );
1821
+ }
1822
+ videoGenerationReadinessErrorOptions(readiness) {
1823
+ const requiredCredits = readiness.required_credits ?? readiness.requiredCredits;
1824
+ const creditsRemaining = readiness.credits_remaining ?? readiness.creditsRemaining;
1825
+ const blocker = readiness.blockers?.find(
1826
+ (item) => item.reason === readiness.reason || item.code === readiness.reason
1827
+ );
1828
+ 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;
1829
+ const retryBlockedReason = readiness.reason === "insufficient_video_credits" ? `Developer API video generation needs ${requiredCredits ?? 1} credit, but only ${creditsRemaining ?? 0} remain.` : readiness.reason;
1830
+ return {
1831
+ retryable: readiness.reason === "concurrent_job_limit_reached",
1832
+ ...requiredCredits === void 0 ? {} : { required_credits: requiredCredits, requiredCredits },
1833
+ ...creditsRemaining === void 0 ? {} : {
1834
+ credits_remaining: creditsRemaining,
1835
+ creditsRemaining,
1836
+ remaining_credits: creditsRemaining,
1837
+ remainingCredits: creditsRemaining
1838
+ },
1839
+ retry_blocked_reason: retryBlockedReason,
1840
+ retryBlockedReason,
1841
+ ...retryBlockCode === void 0 ? {} : { retry_block_code: retryBlockCode, retryBlockCode }
1842
+ };
1843
+ }
1844
+ normalizeAudioUploadLimits(limits) {
1845
+ const maxFileBytes = limits.max_file_bytes ?? limits.maxFileBytes;
1846
+ const monthlyUploads = limits.monthly_uploads ?? limits.monthlyUploads;
1847
+ const storageBytes = limits.storage_bytes ?? limits.storageBytes;
1848
+ const remainingMonthlyUploads = limits.remaining_monthly_uploads ?? limits.remainingMonthlyUploads;
1849
+ const remainingStorageBytes = limits.remaining_storage_bytes ?? limits.remainingStorageBytes;
1850
+ return {
1851
+ ...limits,
1852
+ max_file_bytes: maxFileBytes,
1853
+ maxFileBytes,
1854
+ monthly_uploads: monthlyUploads,
1855
+ monthlyUploads,
1856
+ storage_bytes: storageBytes,
1857
+ storageBytes,
1858
+ remaining_monthly_uploads: remainingMonthlyUploads,
1859
+ remainingMonthlyUploads,
1860
+ remaining_storage_bytes: remainingStorageBytes,
1861
+ remainingStorageBytes
1862
+ };
1863
+ }
1864
+ normalizeAudioUpload(upload) {
1865
+ const uploadUrl = upload.upload_url ?? upload.uploadUrl;
1866
+ const readUrl = upload.read_url ?? upload.readUrl;
1867
+ if (!uploadUrl || !readUrl) {
1868
+ throw new PuppetryError(
1869
+ 502,
1870
+ "invalid_response",
1871
+ "Puppetry API returned an upload response without upload/read URLs"
1872
+ );
1873
+ }
1874
+ const readUrlExpiresIn = upload.read_url_expires_in ?? upload.readUrlExpiresIn;
1875
+ const readUrlExpiresAt = upload.read_url_expires_at ?? upload.readUrlExpiresAt;
1876
+ const uploadUrlExpiresIn = upload.upload_url_expires_in ?? upload.uploadUrlExpiresIn;
1877
+ const idempotentReplay = upload.idempotent_replay ?? upload.idempotentReplay;
1878
+ return {
1879
+ ...upload,
1880
+ upload_url: uploadUrl,
1881
+ uploadUrl,
1882
+ read_url: readUrl,
1883
+ readUrl,
1884
+ read_url_expires_in: readUrlExpiresIn,
1885
+ readUrlExpiresIn,
1886
+ read_url_expires_at: readUrlExpiresAt,
1887
+ readUrlExpiresAt,
1888
+ upload_url_expires_in: uploadUrlExpiresIn,
1889
+ uploadUrlExpiresIn,
1890
+ idempotent_replay: idempotentReplay,
1891
+ idempotentReplay,
1892
+ limits: this.normalizeAudioUploadLimits(upload.limits)
1893
+ };
1894
+ }
1895
+ // ── Polled event helper ────────────────────────────────────────────
1896
+ videoJobEvent(job, headers) {
1897
+ const normalizedJob = this.normalizeVideoJob(job, headers);
1898
+ const progress = typeof normalizedJob.progress === "number" ? normalizedJob.progress : normalizedJob.status === "completed" ? 100 : void 0;
1899
+ const videoUrl = normalizedJob.videoUrl ?? void 0;
1900
+ const type = normalizedJob.status === "processing" && progress !== void 0 ? "progress" : normalizedJob.status;
1901
+ const statusUrl = normalizedJob.statusUrl ?? normalizedJob.status_url;
1902
+ const contentLocation = normalizedJob.contentLocation ?? normalizedJob.content_location;
1903
+ const operationId = job.operationId ?? job.operation_id ?? this.videoOperationIdHeader(headers);
1904
+ const requestId = job.requestId ?? job.request_id ?? this.videoRequestIdHeader(headers);
1905
+ const source = normalizeVideoJobSource(job.source) ?? normalizeVideoJobSource(job.request_source) ?? normalizeVideoJobSource(job.requestSource);
1906
+ const retryAfter = normalizedJob.retryAfter ?? normalizedJob.retryAfterSeconds ?? normalizedJob.retry_after_seconds;
1907
+ const nextPollAt = normalizedJob.nextPollAt ?? normalizedJob.next_poll_at;
1908
+ const creationCredits = this.videoCreationCredits(normalizedJob);
1909
+ return {
1910
+ type,
1911
+ id: normalizedJob.id,
1912
+ job_id: normalizedJob.job_id,
1913
+ jobId: normalizedJob.id,
1914
+ task_id: normalizedJob.task_id,
1915
+ taskId: normalizedJob.taskId,
1916
+ ...operationId ? { operation_id: operationId, operationId } : {},
1917
+ ...requestId ? { request_id: requestId, requestId } : {},
1918
+ ...source ? { source, request_source: source, requestSource: source } : {},
1919
+ ...statusUrl ? { status_url: statusUrl, statusUrl } : {},
1920
+ ...contentLocation ? { content_location: contentLocation, contentLocation } : {},
1921
+ ...progress !== void 0 ? { progress } : {},
1922
+ ...videoUrl ? { video_url: videoUrl, videoUrl } : {},
1923
+ ...normalizedJob.error ? { error: normalizedJob.error } : {},
1924
+ ...normalizedJob.retryable !== void 0 ? { retryable: normalizedJob.retryable } : {},
1925
+ ...normalizedJob.credits ? { credits: normalizedJob.credits } : {},
1926
+ ...creationCredits ? {
1927
+ creation_credits: creationCredits,
1928
+ creationCredits
1929
+ } : {},
1930
+ ...normalizedJob.retryBlockedReason ? {
1931
+ retry_blocked_reason: normalizedJob.retryBlockedReason,
1932
+ retryBlockedReason: normalizedJob.retryBlockedReason
1933
+ } : {},
1934
+ ...normalizedJob.retryBlockCode ? {
1935
+ retry_block_code: normalizedJob.retryBlockCode,
1936
+ retryBlockCode: normalizedJob.retryBlockCode
1937
+ } : {},
1938
+ ...retryAfter !== void 0 ? {
1939
+ retry_after_seconds: retryAfter,
1940
+ retryAfter,
1941
+ retryAfterSeconds: retryAfter
1942
+ } : {},
1943
+ ...nextPollAt ? {
1944
+ next_poll_at: nextPollAt,
1945
+ nextPollAt
1946
+ } : {},
1947
+ ...normalizedJob.createdAt ? {
1948
+ created_at: normalizedJob.createdAt,
1949
+ createdAt: normalizedJob.createdAt
1950
+ } : {},
1951
+ ...normalizedJob.expiresAt ? {
1952
+ expires_at: normalizedJob.expiresAt,
1953
+ expiresAt: normalizedJob.expiresAt
1954
+ } : {},
1955
+ ...normalizedJob.completedAt !== void 0 ? {
1956
+ completed_at: normalizedJob.completedAt,
1957
+ completedAt: normalizedJob.completedAt
1958
+ } : {},
1959
+ timestamp: normalizedJob.completedAt ?? normalizedJob.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()
1960
+ };
1961
+ }
1962
+ videoRetryingEvent(err, fallbackJobId) {
1963
+ const jobId = this.retryableStatusErrorJobId(err) ?? fallbackJobId;
1964
+ const source = normalizeVideoJobSource(err.source);
1965
+ const requestSource = normalizeVideoJobSource(
1966
+ err.requestSource ?? err.request_source
1967
+ );
1968
+ return {
1969
+ type: "retrying",
1970
+ id: err.id ?? jobId,
1971
+ job_id: err.job_id ?? jobId,
1972
+ jobId,
1973
+ task_id: err.task_id ?? jobId,
1974
+ taskId: err.taskId ?? jobId,
1975
+ ...err.operationId ? { operation_id: err.operationId, operationId: err.operationId } : {},
1976
+ ...err.requestId ? { request_id: err.requestId, requestId: err.requestId } : {},
1977
+ ...source || requestSource ? {
1978
+ source: source ?? requestSource,
1979
+ request_source: requestSource ?? source,
1980
+ requestSource: requestSource ?? source
1981
+ } : {},
1982
+ ...err.statusUrl ? { status_url: err.statusUrl, statusUrl: err.statusUrl } : {},
1983
+ ...err.contentLocation ? {
1984
+ content_location: err.contentLocation,
1985
+ contentLocation: err.contentLocation
1986
+ } : {},
1987
+ error: err.message,
1988
+ code: err.code,
1989
+ status: err.status,
1990
+ statusCode: err.status,
1991
+ ...err.retryable !== void 0 ? { retryable: err.retryable } : {},
1992
+ ...err.credits ? { credits: err.credits } : {},
1993
+ ...err.creationCredits ? {
1994
+ creation_credits: err.creationCredits,
1995
+ creationCredits: err.creationCredits
1996
+ } : {},
1997
+ ...err.retryBlockedReason ? {
1998
+ retry_blocked_reason: err.retryBlockedReason,
1999
+ retryBlockedReason: err.retryBlockedReason
2000
+ } : {},
2001
+ ...err.retryBlockCode ? {
2002
+ retry_block_code: err.retryBlockCode,
2003
+ retryBlockCode: err.retryBlockCode
2004
+ } : {},
2005
+ ...err.retryAfter !== void 0 ? {
2006
+ retry_after_seconds: err.retryAfter,
2007
+ retryAfter: err.retryAfter,
2008
+ retryAfterSeconds: err.retryAfter
2009
+ } : {},
2010
+ ...err.nextPollAt ? { next_poll_at: err.nextPollAt, nextPollAt: err.nextPollAt } : {},
2011
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2012
+ };
2013
+ }
2014
+ async *streamPolledVideoEvents(jobId, options) {
2015
+ const interval = options?.intervalMs ?? 2e3;
2016
+ const timeout = options?.timeoutMs ?? 3e5;
2017
+ const deadline = Date.now() + timeout;
2018
+ const initialDelayMs = this.optionDelayMs(options?.initialDelayMs);
2019
+ let currentJobId = jobId;
2020
+ if (initialDelayMs !== void 0 && initialDelayMs > 0) {
2021
+ await this.sleepWithinDeadline(initialDelayMs, deadline);
2022
+ }
2023
+ while (Date.now() < deadline) {
2024
+ try {
2025
+ const { data: job, headers } = await this.getWithResponse(
2026
+ this.videoJobPath(currentJobId)
2027
+ );
2028
+ const normalizedJob = this.normalizeVideoJob(job, headers);
2029
+ currentJobId = this.videoJobIdFromStatusUrl(
2030
+ normalizedJob.statusUrl ?? normalizedJob.status_url
2031
+ ) ?? normalizedJob.id;
2032
+ yield this.videoJobEvent(job, headers);
2033
+ if (normalizedJob.status === "completed" || normalizedJob.status === "failed") {
2034
+ return;
2035
+ }
2036
+ const delayMs = this.pollingDelayMs(normalizedJob, headers, interval);
2037
+ await this.sleep(Math.min(delayMs, Math.max(deadline - Date.now(), 0)));
2038
+ } catch (err) {
2039
+ if (!(err instanceof PuppetryError)) throw err;
2040
+ const delayMs = this.retryableStatusErrorDelayMs(err, interval);
2041
+ if (delayMs === void 0) throw err;
2042
+ currentJobId = this.retryableStatusErrorJobId(err) ?? currentJobId;
2043
+ yield this.videoRetryingEvent(err, currentJobId);
2044
+ await this.sleepWithinDeadline(delayMs, deadline);
2045
+ }
2046
+ }
2047
+ throw new PuppetryTimeoutError(timeout);
2048
+ }
2049
+ };
2050
+
2051
+ // src/agentTools.ts
2052
+ var imageUrlAliasRequirement = {
2053
+ anyOf: [
2054
+ { required: ["image_url"] },
2055
+ { required: ["imageUrl"] },
2056
+ { required: ["image"] },
2057
+ { required: ["portrait"] },
2058
+ { required: ["portrait_url"] },
2059
+ { required: ["portraitUrl"] },
2060
+ { required: ["avatar"] },
2061
+ { required: ["avatarUrl"] },
2062
+ { required: ["avatar_url"] },
2063
+ { required: ["photo"] },
2064
+ { required: ["photoUrl"] },
2065
+ { required: ["photo_url"] }
2066
+ ]
2067
+ };
2068
+ var textScriptAliasRequirement = {
2069
+ anyOf: [
2070
+ { required: ["text"] },
2071
+ { required: ["script"] },
2072
+ { required: ["prompt"] }
2073
+ ]
2074
+ };
2075
+ var audioUrlAliasRequirement = {
2076
+ anyOf: [
2077
+ { required: ["audio_url"] },
2078
+ { required: ["audioUrl"] },
2079
+ { required: ["audio"] }
2080
+ ]
2081
+ };
2082
+ var audioUploadReservationRequirement = {
2083
+ allOf: [
2084
+ {
2085
+ anyOf: [
2086
+ { required: ["mime_type"] },
2087
+ { required: ["mimeType"] },
2088
+ { required: ["contentType"] }
2089
+ ]
2090
+ },
2091
+ {
2092
+ anyOf: [
2093
+ { required: ["content_length"] },
2094
+ { required: ["contentLength"] },
2095
+ { required: ["sizeBytes"] },
2096
+ { required: ["file_size"] },
2097
+ { required: ["fileSize"] }
2098
+ ]
2099
+ }
2100
+ ]
2101
+ };
2102
+ var jobIdAliasRequirement = {
2103
+ anyOf: [
2104
+ { required: ["jobId"] },
2105
+ { required: ["job_id"] },
2106
+ { required: ["task_id"] },
2107
+ { required: ["taskId"] },
2108
+ { required: ["id"] }
2109
+ ]
2110
+ };
2111
+ var waitOptionsProperties = {
2112
+ intervalMs: {
2113
+ type: "number",
2114
+ description: "Polling interval in milliseconds. Defaults to 2000."
2115
+ },
2116
+ timeoutMs: {
2117
+ type: "number",
2118
+ description: "Maximum wait time in milliseconds. Defaults to 300000."
2119
+ },
2120
+ initialDelayMs: {
2121
+ type: "number",
2122
+ description: "Optional delay before the first status request, useful with Retry-After."
2123
+ }
2124
+ };
2125
+ var readinessPreflightProperties = {
2126
+ preflightReadiness: {
2127
+ type: "boolean",
2128
+ description: "Check video credits and active slots before queueing. Defaults to false on create-only tools and true on create-and-wait tools."
2129
+ },
2130
+ preflight_readiness: {
2131
+ type: "boolean",
2132
+ description: "Snake_case alias for preflightReadiness."
2133
+ }
2134
+ };
2135
+ function resolveJobIdAlias(input2) {
2136
+ const aliases = [
2137
+ { name: "jobId", value: input2.jobId },
2138
+ { name: "job_id", value: input2.job_id },
2139
+ { name: "task_id", value: input2.task_id },
2140
+ { name: "taskId", value: input2.taskId },
2141
+ { name: "id", value: input2.id }
2142
+ ];
2143
+ const provided = aliases.filter((alias) => alias.value);
2144
+ const first = provided[0];
2145
+ if (!first?.value) {
2146
+ throw new PuppetryError(
2147
+ 400,
2148
+ "invalid_request",
2149
+ "Pass one of jobId, job_id, task_id, taskId, id"
2150
+ );
2151
+ }
2152
+ const conflictingAlias = provided.find(
2153
+ (alias) => alias.value !== first.value
2154
+ );
2155
+ if (conflictingAlias) {
2156
+ throw new PuppetryError(
2157
+ 400,
2158
+ "invalid_request",
2159
+ "Pass one of jobId, job_id, task_id, taskId, id, not conflicting values"
2160
+ );
2161
+ }
2162
+ return first.value;
2163
+ }
2164
+ var PUPPETRY_AGENT_TOOLS = [
2165
+ {
2166
+ name: "puppetry_create_video_from_text",
2167
+ resource: "videos.createFromText",
2168
+ description: "Create a talking-head video from a portrait image and text.",
2169
+ inputSchema: {
2170
+ type: "object",
2171
+ required: [],
2172
+ allOf: [textScriptAliasRequirement, imageUrlAliasRequirement],
2173
+ properties: {
2174
+ text: {
2175
+ type: "string",
2176
+ description: "Script to synthesize and animate."
2177
+ },
2178
+ script: {
2179
+ type: "string",
2180
+ description: "Alias for text script to synthesize and animate."
2181
+ },
2182
+ prompt: {
2183
+ type: "string",
2184
+ description: "Alias for text script, useful for prompt-oriented agents."
2185
+ },
2186
+ image_url: {
2187
+ type: "string",
2188
+ description: "Hosted portrait image URL."
2189
+ },
2190
+ imageUrl: {
2191
+ type: "string",
2192
+ description: "Hosted portrait image URL alias."
2193
+ },
2194
+ image: {
2195
+ type: "string",
2196
+ description: "Hosted portrait image URL alias for agents."
2197
+ },
2198
+ portrait: {
2199
+ type: "string",
2200
+ description: "Deprecated hosted portrait image URL alias."
2201
+ },
2202
+ portrait_url: {
2203
+ type: "string",
2204
+ description: "Hosted portrait image URL snake_case alias."
2205
+ },
2206
+ portraitUrl: {
2207
+ type: "string",
2208
+ description: "Hosted portrait image URL alias."
2209
+ },
2210
+ avatar: {
2211
+ type: "string",
2212
+ description: "Hosted avatar image URL alias."
2213
+ },
2214
+ avatarUrl: {
2215
+ type: "string",
2216
+ description: "Hosted avatar image URL alias."
2217
+ },
2218
+ avatar_url: {
2219
+ type: "string",
2220
+ description: "Hosted avatar image URL snake_case alias."
2221
+ },
2222
+ photo: {
2223
+ type: "string",
2224
+ description: "Hosted photo image URL alias."
2225
+ },
2226
+ photoUrl: {
2227
+ type: "string",
2228
+ description: "Hosted photo image URL alias."
2229
+ },
2230
+ photo_url: {
2231
+ type: "string",
2232
+ description: "Hosted photo image URL snake_case alias."
2233
+ },
2234
+ voice_id: {
2235
+ type: "string",
2236
+ description: "Optional Puppetry voice ID from voices.listPuppetry()."
2237
+ },
2238
+ voiceId: {
2239
+ type: "string",
2240
+ description: "Optional Puppetry voice ID alias."
2241
+ },
2242
+ voice: {
2243
+ type: "string",
2244
+ description: "Optional Puppetry voice ID alias for SDK examples."
2245
+ },
2246
+ language: {
2247
+ type: "string",
2248
+ description: "Optional language code, such as en."
2249
+ },
2250
+ expressiveness: {
2251
+ type: "number",
2252
+ description: "Optional animation expressiveness from 0 to 2."
2253
+ },
2254
+ idempotencyKey: {
2255
+ type: "string",
2256
+ description: "Optional retry key sent as Idempotency-Key."
2257
+ },
2258
+ idempotency_key: {
2259
+ type: "string",
2260
+ description: "Optional retry key alias sent as Idempotency-Key."
2261
+ },
2262
+ ...readinessPreflightProperties
2263
+ }
2264
+ }
2265
+ },
2266
+ {
2267
+ name: "puppetry_create_video_from_audio",
2268
+ resource: "videos.createFromAudio",
2269
+ description: "Create a talking-head video from a portrait image and audio.",
2270
+ inputSchema: {
2271
+ type: "object",
2272
+ required: [],
2273
+ allOf: [imageUrlAliasRequirement, audioUrlAliasRequirement],
2274
+ properties: {
2275
+ audio_url: {
2276
+ type: "string",
2277
+ description: "Hosted audio URL."
2278
+ },
2279
+ audioUrl: {
2280
+ type: "string",
2281
+ description: "Hosted audio URL alias."
2282
+ },
2283
+ audio: {
2284
+ type: "string",
2285
+ description: "Hosted audio URL alias for agents."
2286
+ },
2287
+ image_url: {
2288
+ type: "string",
2289
+ description: "Hosted portrait image URL."
2290
+ },
2291
+ imageUrl: {
2292
+ type: "string",
2293
+ description: "Hosted portrait image URL alias."
2294
+ },
2295
+ image: {
2296
+ type: "string",
2297
+ description: "Hosted portrait image URL alias for agents."
2298
+ },
2299
+ portrait: {
2300
+ type: "string",
2301
+ description: "Deprecated hosted portrait image URL alias."
2302
+ },
2303
+ portrait_url: {
2304
+ type: "string",
2305
+ description: "Hosted portrait image URL snake_case alias."
2306
+ },
2307
+ portraitUrl: {
2308
+ type: "string",
2309
+ description: "Hosted portrait image URL alias."
2310
+ },
2311
+ avatar: {
2312
+ type: "string",
2313
+ description: "Hosted avatar image URL alias."
2314
+ },
2315
+ avatarUrl: {
2316
+ type: "string",
2317
+ description: "Hosted avatar image URL alias."
2318
+ },
2319
+ avatar_url: {
2320
+ type: "string",
2321
+ description: "Hosted avatar image URL snake_case alias."
2322
+ },
2323
+ photo: {
2324
+ type: "string",
2325
+ description: "Hosted photo image URL alias."
2326
+ },
2327
+ photoUrl: {
2328
+ type: "string",
2329
+ description: "Hosted photo image URL alias."
2330
+ },
2331
+ photo_url: {
2332
+ type: "string",
2333
+ description: "Hosted photo image URL snake_case alias."
2334
+ },
2335
+ expressiveness: {
2336
+ type: "number",
2337
+ description: "Optional animation expressiveness from 0 to 2."
2338
+ },
2339
+ idempotencyKey: {
2340
+ type: "string",
2341
+ description: "Optional retry key sent as Idempotency-Key."
2342
+ },
2343
+ idempotency_key: {
2344
+ type: "string",
2345
+ description: "Optional retry key alias sent as Idempotency-Key."
2346
+ },
2347
+ ...readinessPreflightProperties
2348
+ }
2349
+ }
2350
+ },
2351
+ {
2352
+ name: "puppetry_create_video_from_text_and_wait",
2353
+ resource: "videos.createFromText.waitForCompletion",
2354
+ description: "Create a talking-head video from text and wait for completed or failed status.",
2355
+ inputSchema: {
2356
+ type: "object",
2357
+ required: [],
2358
+ allOf: [textScriptAliasRequirement, imageUrlAliasRequirement],
2359
+ properties: {
2360
+ text: {
2361
+ type: "string",
2362
+ description: "Script to synthesize and animate."
2363
+ },
2364
+ script: {
2365
+ type: "string",
2366
+ description: "Alias for text script to synthesize and animate."
2367
+ },
2368
+ prompt: {
2369
+ type: "string",
2370
+ description: "Alias for text script, useful for prompt-oriented agents."
2371
+ },
2372
+ image_url: {
2373
+ type: "string",
2374
+ description: "Hosted portrait image URL."
2375
+ },
2376
+ imageUrl: {
2377
+ type: "string",
2378
+ description: "Hosted portrait image URL alias."
2379
+ },
2380
+ image: {
2381
+ type: "string",
2382
+ description: "Hosted portrait image URL alias for agents."
2383
+ },
2384
+ portrait: {
2385
+ type: "string",
2386
+ description: "Deprecated hosted portrait image URL alias."
2387
+ },
2388
+ portrait_url: {
2389
+ type: "string",
2390
+ description: "Hosted portrait image URL snake_case alias."
2391
+ },
2392
+ portraitUrl: {
2393
+ type: "string",
2394
+ description: "Hosted portrait image URL alias."
2395
+ },
2396
+ avatar: {
2397
+ type: "string",
2398
+ description: "Hosted avatar image URL alias."
2399
+ },
2400
+ avatarUrl: {
2401
+ type: "string",
2402
+ description: "Hosted avatar image URL alias."
2403
+ },
2404
+ avatar_url: {
2405
+ type: "string",
2406
+ description: "Hosted avatar image URL snake_case alias."
2407
+ },
2408
+ photo: {
2409
+ type: "string",
2410
+ description: "Hosted photo image URL alias."
2411
+ },
2412
+ photoUrl: {
2413
+ type: "string",
2414
+ description: "Hosted photo image URL alias."
2415
+ },
2416
+ photo_url: {
2417
+ type: "string",
2418
+ description: "Hosted photo image URL snake_case alias."
2419
+ },
2420
+ voice_id: {
2421
+ type: "string",
2422
+ description: "Optional Puppetry voice ID from voices.listPuppetry()."
2423
+ },
2424
+ voiceId: {
2425
+ type: "string",
2426
+ description: "Optional Puppetry voice ID alias."
2427
+ },
2428
+ voice: {
2429
+ type: "string",
2430
+ description: "Optional Puppetry voice ID alias for SDK examples."
2431
+ },
2432
+ language: {
2433
+ type: "string",
2434
+ description: "Optional language code, such as en."
2435
+ },
2436
+ expressiveness: {
2437
+ type: "number",
2438
+ description: "Optional animation expressiveness from 0 to 2."
2439
+ },
2440
+ idempotencyKey: {
2441
+ type: "string",
2442
+ description: "Optional retry key sent as Idempotency-Key."
2443
+ },
2444
+ idempotency_key: {
2445
+ type: "string",
2446
+ description: "Optional retry key alias sent as Idempotency-Key."
2447
+ },
2448
+ ...readinessPreflightProperties,
2449
+ ...waitOptionsProperties
2450
+ }
2451
+ }
2452
+ },
2453
+ {
2454
+ name: "puppetry_create_video_from_audio_and_wait",
2455
+ resource: "videos.createFromAudio.waitForCompletion",
2456
+ description: "Create a talking-head video from audio and wait for completed or failed status.",
2457
+ inputSchema: {
2458
+ type: "object",
2459
+ required: [],
2460
+ allOf: [imageUrlAliasRequirement, audioUrlAliasRequirement],
2461
+ properties: {
2462
+ audio_url: {
2463
+ type: "string",
2464
+ description: "Hosted audio URL."
2465
+ },
2466
+ audioUrl: {
2467
+ type: "string",
2468
+ description: "Hosted audio URL alias."
2469
+ },
2470
+ audio: {
2471
+ type: "string",
2472
+ description: "Hosted audio URL alias for agents."
2473
+ },
2474
+ image_url: {
2475
+ type: "string",
2476
+ description: "Hosted portrait image URL."
2477
+ },
2478
+ imageUrl: {
2479
+ type: "string",
2480
+ description: "Hosted portrait image URL alias."
2481
+ },
2482
+ image: {
2483
+ type: "string",
2484
+ description: "Hosted portrait image URL alias for agents."
2485
+ },
2486
+ portrait: {
2487
+ type: "string",
2488
+ description: "Deprecated hosted portrait image URL alias."
2489
+ },
2490
+ portrait_url: {
2491
+ type: "string",
2492
+ description: "Hosted portrait image URL snake_case alias."
2493
+ },
2494
+ portraitUrl: {
2495
+ type: "string",
2496
+ description: "Hosted portrait image URL alias."
2497
+ },
2498
+ avatar: {
2499
+ type: "string",
2500
+ description: "Hosted avatar image URL alias."
2501
+ },
2502
+ avatarUrl: {
2503
+ type: "string",
2504
+ description: "Hosted avatar image URL alias."
2505
+ },
2506
+ avatar_url: {
2507
+ type: "string",
2508
+ description: "Hosted avatar image URL snake_case alias."
2509
+ },
2510
+ photo: {
2511
+ type: "string",
2512
+ description: "Hosted photo image URL alias."
2513
+ },
2514
+ photoUrl: {
2515
+ type: "string",
2516
+ description: "Hosted photo image URL alias."
2517
+ },
2518
+ photo_url: {
2519
+ type: "string",
2520
+ description: "Hosted photo image URL snake_case alias."
2521
+ },
2522
+ expressiveness: {
2523
+ type: "number",
2524
+ description: "Optional animation expressiveness from 0 to 2."
2525
+ },
2526
+ idempotencyKey: {
2527
+ type: "string",
2528
+ description: "Optional retry key sent as Idempotency-Key."
2529
+ },
2530
+ idempotency_key: {
2531
+ type: "string",
2532
+ description: "Optional retry key alias sent as Idempotency-Key."
2533
+ },
2534
+ ...readinessPreflightProperties,
2535
+ ...waitOptionsProperties
2536
+ }
2537
+ }
2538
+ },
2539
+ {
2540
+ name: "puppetry_create_audio_upload_url",
2541
+ resource: "uploads.createAudioUrl",
2542
+ description: "Reserve a Puppetry-hosted signed audio upload URL for audio-to-video workflows.",
2543
+ inputSchema: {
2544
+ type: "object",
2545
+ required: [],
2546
+ ...audioUploadReservationRequirement,
2547
+ properties: {
2548
+ mime_type: {
2549
+ type: "string",
2550
+ description: "Audio MIME type, such as audio/wav, audio/mpeg, or audio/mp4."
2551
+ },
2552
+ mimeType: {
2553
+ type: "string",
2554
+ description: "Audio MIME type alias."
2555
+ },
2556
+ contentType: {
2557
+ type: "string",
2558
+ description: "Audio MIME type alias for signed-upload callers."
2559
+ },
2560
+ content_length: {
2561
+ type: "number",
2562
+ description: "Exact byte length of the audio file to upload."
2563
+ },
2564
+ contentLength: {
2565
+ type: "number",
2566
+ description: "Exact byte length alias."
2567
+ },
2568
+ sizeBytes: {
2569
+ type: "number",
2570
+ description: "Exact byte length alias for agent/runtime callers."
2571
+ },
2572
+ file_size: {
2573
+ type: "number",
2574
+ description: "Exact byte length alias for upload metadata callers."
2575
+ },
2576
+ fileSize: {
2577
+ type: "number",
2578
+ description: "Exact byte length alias for upload metadata callers."
2579
+ },
2580
+ idempotencyKey: {
2581
+ type: "string",
2582
+ description: "Optional retry key sent as Idempotency-Key."
2583
+ },
2584
+ idempotency_key: {
2585
+ type: "string",
2586
+ description: "Optional retry key alias sent as Idempotency-Key."
2587
+ }
2588
+ }
2589
+ }
2590
+ },
2591
+ {
2592
+ name: "puppetry_lipsync",
2593
+ resource: "videos.createFromAudio",
2594
+ description: "Lip sync a portrait or puppet with an existing audio URL.",
2595
+ inputSchema: {
2596
+ type: "object",
2597
+ required: [],
2598
+ allOf: [imageUrlAliasRequirement, audioUrlAliasRequirement],
2599
+ properties: {
2600
+ audio_url: {
2601
+ type: "string",
2602
+ description: "Hosted audio URL."
2603
+ },
2604
+ audioUrl: {
2605
+ type: "string",
2606
+ description: "Hosted audio URL alias."
2607
+ },
2608
+ audio: {
2609
+ type: "string",
2610
+ description: "Hosted audio URL alias for agents."
2611
+ },
2612
+ image_url: {
2613
+ type: "string",
2614
+ description: "Hosted portrait image URL."
2615
+ },
2616
+ imageUrl: {
2617
+ type: "string",
2618
+ description: "Hosted portrait image URL alias."
2619
+ },
2620
+ image: {
2621
+ type: "string",
2622
+ description: "Hosted portrait image URL alias for agents."
2623
+ },
2624
+ portrait: {
2625
+ type: "string",
2626
+ description: "Deprecated hosted portrait image URL alias."
2627
+ },
2628
+ portrait_url: {
2629
+ type: "string",
2630
+ description: "Hosted portrait image URL snake_case alias."
2631
+ },
2632
+ portraitUrl: {
2633
+ type: "string",
2634
+ description: "Hosted portrait image URL alias."
2635
+ },
2636
+ avatar: {
2637
+ type: "string",
2638
+ description: "Hosted avatar image URL alias."
2639
+ },
2640
+ avatarUrl: {
2641
+ type: "string",
2642
+ description: "Hosted avatar image URL alias."
2643
+ },
2644
+ avatar_url: {
2645
+ type: "string",
2646
+ description: "Hosted avatar image URL snake_case alias."
2647
+ },
2648
+ photo: {
2649
+ type: "string",
2650
+ description: "Hosted photo image URL alias."
2651
+ },
2652
+ photoUrl: {
2653
+ type: "string",
2654
+ description: "Hosted photo image URL alias."
2655
+ },
2656
+ photo_url: {
2657
+ type: "string",
2658
+ description: "Hosted photo image URL snake_case alias."
2659
+ },
2660
+ idempotencyKey: {
2661
+ type: "string",
2662
+ description: "Optional retry key sent as Idempotency-Key."
2663
+ },
2664
+ idempotency_key: {
2665
+ type: "string",
2666
+ description: "Optional retry key alias sent as Idempotency-Key."
2667
+ }
2668
+ }
2669
+ }
2670
+ },
2671
+ {
2672
+ name: "puppetry_list_voices",
2673
+ resource: "voices.listPuppetry",
2674
+ description: "List Puppetry-hosted voices with preview text and audio.",
2675
+ inputSchema: {
2676
+ type: "object",
2677
+ properties: {}
2678
+ }
2679
+ },
2680
+ {
2681
+ name: "puppetry_get_job_status",
2682
+ resource: "jobs.get",
2683
+ description: "Fetch a generated video job by ID.",
2684
+ inputSchema: {
2685
+ type: "object",
2686
+ required: [],
2687
+ ...jobIdAliasRequirement,
2688
+ properties: {
2689
+ jobId: {
2690
+ type: "string",
2691
+ description: "Generated video job ID."
2692
+ },
2693
+ job_id: {
2694
+ type: "string",
2695
+ description: "Generated video job ID alias."
2696
+ },
2697
+ task_id: {
2698
+ type: "string",
2699
+ description: "Generated video task ID alias."
2700
+ },
2701
+ taskId: {
2702
+ type: "string",
2703
+ description: "Generated video task ID camelCase alias."
2704
+ },
2705
+ id: {
2706
+ type: "string",
2707
+ description: "Generated video ID alias."
2708
+ }
2709
+ }
2710
+ }
2711
+ },
2712
+ {
2713
+ name: "puppetry_wait_for_video",
2714
+ resource: "videos.waitForCompletion",
2715
+ description: "Poll a generated video job until it reaches completed or failed.",
2716
+ inputSchema: {
2717
+ type: "object",
2718
+ required: [],
2719
+ ...jobIdAliasRequirement,
2720
+ properties: {
2721
+ jobId: {
2722
+ type: "string",
2723
+ description: "Generated video job ID."
2724
+ },
2725
+ job_id: {
2726
+ type: "string",
2727
+ description: "Generated video job ID alias."
2728
+ },
2729
+ task_id: {
2730
+ type: "string",
2731
+ description: "Generated video task ID alias."
2732
+ },
2733
+ taskId: {
2734
+ type: "string",
2735
+ description: "Generated video task ID camelCase alias."
2736
+ },
2737
+ id: {
2738
+ type: "string",
2739
+ description: "Generated video ID alias."
2740
+ },
2741
+ intervalMs: {
2742
+ type: "number",
2743
+ description: "Polling interval in milliseconds. Defaults to 2000."
2744
+ },
2745
+ timeoutMs: {
2746
+ type: "number",
2747
+ description: "Maximum wait time in milliseconds. Defaults to 300000."
2748
+ },
2749
+ initialDelayMs: {
2750
+ type: "number",
2751
+ description: "Optional delay before the first status request, useful with Retry-After."
2752
+ }
2753
+ }
2754
+ }
2755
+ },
2756
+ {
2757
+ name: "puppetry_get_video_readiness",
2758
+ resource: "videos.getReadiness",
2759
+ description: "Check whether credit-backed video creation can run now, including credit and active-slot blockers.",
2760
+ inputSchema: {
2761
+ type: "object",
2762
+ properties: {}
2763
+ }
2764
+ },
2765
+ {
2766
+ name: "puppetry_get_quota",
2767
+ resource: "quota.get",
2768
+ description: "Check current plan, credit usage, and remaining credits.",
2769
+ inputSchema: {
2770
+ type: "object",
2771
+ properties: {}
2772
+ }
2773
+ }
2774
+ ];
2775
+ async function callPuppetryAgentTool(client2, name, input2) {
2776
+ switch (name) {
2777
+ case "puppetry_create_video_from_text":
2778
+ return createTextVideo(
2779
+ client2,
2780
+ input2
2781
+ );
2782
+ case "puppetry_create_video_from_audio":
2783
+ return createAudioVideo(
2784
+ client2,
2785
+ input2
2786
+ );
2787
+ case "puppetry_create_video_from_text_and_wait":
2788
+ return createTextVideoAndWait(
2789
+ client2,
2790
+ input2
2791
+ );
2792
+ case "puppetry_create_video_from_audio_and_wait":
2793
+ return createAudioVideoAndWait(
2794
+ client2,
2795
+ input2
2796
+ );
2797
+ case "puppetry_create_audio_upload_url":
2798
+ return client2.uploads.createAudioUrl(
2799
+ input2
2800
+ );
2801
+ case "puppetry_lipsync":
2802
+ return client2.videos.createFromAudio(
2803
+ input2
2804
+ );
2805
+ case "puppetry_list_voices":
2806
+ return client2.voices.listPuppetry();
2807
+ case "puppetry_get_job_status":
2808
+ return client2.jobs.get(
2809
+ resolveJobIdAlias(
2810
+ input2
2811
+ )
2812
+ );
2813
+ case "puppetry_wait_for_video": {
2814
+ const waitInput = input2;
2815
+ return client2.videos.waitForCompletion(resolveJobIdAlias(waitInput), {
2816
+ intervalMs: waitInput.intervalMs,
2817
+ timeoutMs: waitInput.timeoutMs,
2818
+ initialDelayMs: waitInput.initialDelayMs
2819
+ });
2820
+ }
2821
+ case "puppetry_get_video_readiness":
2822
+ return client2.videos.getReadiness();
2823
+ case "puppetry_get_quota":
2824
+ return client2.quota.get();
2825
+ default:
2826
+ throw new Error(`Unsupported Puppetry agent tool: ${String(name)}`);
2827
+ }
2828
+ }
2829
+ async function createTextVideo(client2, input2) {
2830
+ const readiness = await preflightVideoReadiness(client2, input2, false);
2831
+ const job = await client2.videos.createFromText(input2);
2832
+ return videoCreateResult(job, readiness);
2833
+ }
2834
+ async function createAudioVideo(client2, input2) {
2835
+ const readiness = await preflightVideoReadiness(client2, input2, false);
2836
+ const job = await client2.videos.createFromAudio(input2);
2837
+ return videoCreateResult(job, readiness);
2838
+ }
2839
+ async function createTextVideoAndWait(client2, input2) {
2840
+ const readiness = await preflightVideoReadiness(client2, input2, true);
2841
+ const acceptedJob = await client2.videos.createFromText(input2);
2842
+ const wait = measuredWaitOptions(input2);
2843
+ const finalJob = await acceptedJob.waitForCompletion(wait.options);
2844
+ return videoFlowResult(acceptedJob, finalJob, wait.summary(), readiness);
2845
+ }
2846
+ async function createAudioVideoAndWait(client2, input2) {
2847
+ const readiness = await preflightVideoReadiness(client2, input2, true);
2848
+ const acceptedJob = await client2.videos.createFromAudio(input2);
2849
+ const wait = measuredWaitOptions(input2);
2850
+ const finalJob = await acceptedJob.waitForCompletion(wait.options);
2851
+ return videoFlowResult(acceptedJob, finalJob, wait.summary(), readiness);
2852
+ }
2853
+ async function preflightVideoReadiness(client2, input2, defaultValue) {
2854
+ const shouldPreflight = resolveReadinessPreflight(input2, defaultValue);
2855
+ if (!shouldPreflight) return void 0;
2856
+ return client2.videos.ensureReady();
2857
+ }
2858
+ function resolveReadinessPreflight(input2, defaultValue) {
2859
+ const aliases = [
2860
+ { name: "preflightReadiness", value: input2.preflightReadiness },
2861
+ { name: "preflight_readiness", value: input2.preflight_readiness }
2862
+ ].flatMap((alias) => {
2863
+ if (alias.value === void 0 || alias.value === null) return [];
2864
+ if (typeof alias.value !== "boolean") {
2865
+ throw new PuppetryError(
2866
+ 400,
2867
+ "invalid_request",
2868
+ `${alias.name} must be a boolean`
2869
+ );
2870
+ }
2871
+ return [{ name: alias.name, value: alias.value }];
2872
+ });
2873
+ const first = aliases[0];
2874
+ if (!first) return defaultValue;
2875
+ for (const alias of aliases) {
2876
+ if (alias.value !== first.value) {
2877
+ throw new PuppetryError(
2878
+ 400,
2879
+ "invalid_request",
2880
+ "preflightReadiness and preflight_readiness must describe the same boolean value"
2881
+ );
2882
+ }
2883
+ }
2884
+ return first.value;
2885
+ }
2886
+ function measuredWaitOptions(input2) {
2887
+ const startedAt = Date.now();
2888
+ let pollCount = 0;
2889
+ let retryCount = 0;
2890
+ let latestRetryingEvent;
2891
+ const options = {
2892
+ ...input2.intervalMs === void 0 ? {} : { intervalMs: input2.intervalMs },
2893
+ ...input2.timeoutMs === void 0 ? {} : { timeoutMs: input2.timeoutMs },
2894
+ ...input2.initialDelayMs === void 0 ? {} : { initialDelayMs: input2.initialDelayMs },
2895
+ onPoll: () => {
2896
+ pollCount += 1;
2897
+ },
2898
+ onRetrying: (event) => {
2899
+ retryCount += 1;
2900
+ latestRetryingEvent = event;
2901
+ }
2902
+ };
2903
+ return {
2904
+ options,
2905
+ summary: () => ({
2906
+ pollCount,
2907
+ retryCount,
2908
+ ...latestRetryingEvent === void 0 ? {} : { latestRetryingEvent },
2909
+ durationMs: Date.now() - startedAt
2910
+ })
2911
+ };
2912
+ }
2913
+ function videoFlowResult(acceptedJob, finalJob, wait, readiness) {
2914
+ const operationId = nonDefaultOperationId(finalJob) ?? nonDefaultOperationId(acceptedJob) ?? finalJob.operationId ?? finalJob.operation_id ?? acceptedJob.operationId ?? acceptedJob.operation_id ?? finalJob.id;
2915
+ const requestId = finalJob.requestId ?? finalJob.request_id ?? acceptedJob.requestId ?? acceptedJob.request_id;
2916
+ const videoUrl = finalJob.videoUrl ?? finalJob.video_url ?? finalJob.url;
2917
+ const acceptedCredits = videoCreditEvidence(acceptedJob.credits);
2918
+ const finalCredits = videoCreditEvidence(finalJob.credits);
2919
+ const creationCredits = videoCreationCreditEvidence(finalJob) ?? videoCreationCreditEvidence(acceptedJob);
2920
+ return {
2921
+ object: "video_flow",
2922
+ accepted_job: acceptedJob,
2923
+ acceptedJob,
2924
+ final_job: finalJob,
2925
+ finalJob,
2926
+ ...acceptedCredits === void 0 ? {} : { accepted_credits: acceptedCredits, acceptedCredits },
2927
+ ...finalCredits === void 0 ? {} : {
2928
+ final_credits: finalCredits,
2929
+ finalCredits,
2930
+ credits: finalCredits
2931
+ },
2932
+ ...creationCredits === void 0 ? {} : { creation_credits: creationCredits, creationCredits },
2933
+ job_id: finalJob.id,
2934
+ jobId: finalJob.id,
2935
+ operation_id: operationId,
2936
+ operationId,
2937
+ ...requestId === void 0 ? {} : { request_id: requestId, requestId },
2938
+ status: finalJob.status,
2939
+ completed: finalJob.status === "completed",
2940
+ ...readiness === void 0 ? {} : { readiness: videoReadinessEvidence(readiness) },
2941
+ readiness_checked: readiness !== void 0,
2942
+ readinessChecked: readiness !== void 0,
2943
+ poll_count: wait.pollCount,
2944
+ pollCount: wait.pollCount,
2945
+ retry_count: wait.retryCount,
2946
+ retryCount: wait.retryCount,
2947
+ ...wait.latestRetryingEvent === void 0 ? {} : {
2948
+ latest_retrying_event: wait.latestRetryingEvent,
2949
+ latestRetryingEvent: wait.latestRetryingEvent
2950
+ },
2951
+ duration_ms: wait.durationMs,
2952
+ durationMs: wait.durationMs,
2953
+ ...videoUrl === void 0 ? {} : { video_url: videoUrl, videoUrl }
2954
+ };
2955
+ }
2956
+ function videoCreateResult(job, readiness) {
2957
+ const result = job;
2958
+ result.readiness_checked = readiness !== void 0;
2959
+ result.readinessChecked = readiness !== void 0;
2960
+ if (readiness !== void 0) {
2961
+ result.readiness = videoReadinessEvidence(readiness);
2962
+ }
2963
+ return result;
2964
+ }
2965
+ function videoCreditEvidence(credits) {
2966
+ if (!credits || typeof credits !== "object") return void 0;
2967
+ if (typeof credits.charged !== "number" || !Number.isFinite(credits.charged)) {
2968
+ return void 0;
2969
+ }
2970
+ return {
2971
+ charged: credits.charged,
2972
+ remaining: typeof credits.remaining === "number" && Number.isFinite(credits.remaining) ? credits.remaining : null
2973
+ };
2974
+ }
2975
+ function videoCreationCreditEvidence(job) {
2976
+ const raw = job.creationCredits ?? job.creation_credits;
2977
+ if (!raw || typeof raw !== "object") return void 0;
2978
+ if (typeof raw.charged !== "number" || !Number.isFinite(raw.charged)) {
2979
+ return void 0;
2980
+ }
2981
+ const remainingAfterDebit = raw.remainingAfterDebit ?? raw.remaining_after_debit ?? null;
2982
+ const normalizedRemainingAfterDebit = typeof remainingAfterDebit === "number" && Number.isFinite(remainingAfterDebit) ? remainingAfterDebit : null;
2983
+ return {
2984
+ charged: raw.charged,
2985
+ remaining_after_debit: normalizedRemainingAfterDebit,
2986
+ remainingAfterDebit: normalizedRemainingAfterDebit
2987
+ };
2988
+ }
2989
+ function videoReadinessEvidence(readiness) {
2990
+ const canCreate = readiness.canCreate ?? readiness.can_create;
2991
+ const requiredCredits = readiness.requiredCredits ?? readiness.required_credits;
2992
+ const creditsRemaining = readiness.creditsRemaining ?? readiness.credits_remaining;
2993
+ const concurrentJobs = readiness.concurrentJobs ?? readiness.concurrent_jobs;
2994
+ const activeJobs = readiness.activeJobs ?? readiness.active_jobs;
2995
+ const availableSlots = readiness.availableSlots ?? readiness.available_slots;
2996
+ const activeJobExpiresInSeconds = readiness.activeJobExpiresInSeconds ?? readiness.active_job_expires_in_seconds;
2997
+ const retryAfterSeconds = readiness.retryAfterSeconds ?? readiness.retryAfter ?? readiness.retry_after_seconds;
2998
+ const nextPollAt = readiness.nextPollAt ?? readiness.next_poll_at;
2999
+ return {
3000
+ ...readiness,
3001
+ can_create: canCreate,
3002
+ canCreate,
3003
+ required_credits: requiredCredits,
3004
+ requiredCredits,
3005
+ credits_remaining: creditsRemaining,
3006
+ creditsRemaining,
3007
+ ...concurrentJobs === void 0 ? {} : { concurrent_jobs: concurrentJobs, concurrentJobs },
3008
+ ...activeJobs === void 0 ? {} : { active_jobs: activeJobs, activeJobs },
3009
+ ...availableSlots === void 0 ? {} : { available_slots: availableSlots, availableSlots },
3010
+ ...activeJobExpiresInSeconds === void 0 ? {} : {
3011
+ active_job_expires_in_seconds: activeJobExpiresInSeconds,
3012
+ activeJobExpiresInSeconds
3013
+ },
3014
+ ...retryAfterSeconds === void 0 ? {} : {
3015
+ retry_after_seconds: retryAfterSeconds,
3016
+ retryAfter: readiness.retryAfter ?? retryAfterSeconds,
3017
+ retryAfterSeconds
3018
+ },
3019
+ ...nextPollAt === void 0 ? {} : { next_poll_at: nextPollAt, nextPollAt }
3020
+ };
3021
+ }
3022
+ function nonDefaultOperationId(job) {
3023
+ const operationId = job.operationId ?? job.operation_id;
3024
+ return operationId && operationId !== job.id ? operationId : void 0;
3025
+ }
3026
+
3027
+ // src/mcp.ts
3028
+ var SERVER_INFO = {
3029
+ name: "puppetry",
3030
+ version: "0.1.0"
3031
+ };
3032
+ var TOOL_NAMES = new Set(
3033
+ PUPPETRY_AGENT_TOOLS.map((tool) => tool.name)
3034
+ );
3035
+ async function handlePuppetryMcpRequest(client2, request) {
3036
+ if (request.id === void 0 && request.method === "notifications/initialized") {
3037
+ return null;
3038
+ }
3039
+ try {
3040
+ switch (request.method) {
3041
+ case "initialize":
3042
+ return success(request.id, {
3043
+ protocolVersion: requestedProtocolVersion(request.params),
3044
+ capabilities: { tools: {} },
3045
+ serverInfo: SERVER_INFO
3046
+ });
3047
+ case "ping":
3048
+ return success(request.id, {});
3049
+ case "tools/list":
3050
+ return success(request.id, {
3051
+ tools: PUPPETRY_AGENT_TOOLS.map(
3052
+ ({ name, description, inputSchema }) => ({
3053
+ name,
3054
+ description,
3055
+ inputSchema
3056
+ })
3057
+ )
3058
+ });
3059
+ case "tools/call":
3060
+ return success(request.id, await callTool(client2, request.params));
3061
+ default:
3062
+ return error(request.id, -32601, `Method not found: ${request.method}`);
3063
+ }
3064
+ } catch (err) {
3065
+ if (err instanceof PuppetryError) {
3066
+ return error(request.id, -32603, err.message, puppetryErrorData(err));
3067
+ }
3068
+ return error(
3069
+ request.id,
3070
+ -32603,
3071
+ err instanceof Error ? err.message : "Internal error"
3072
+ );
3073
+ }
3074
+ }
3075
+ async function callTool(client2, params) {
3076
+ if (!isObject(params) || typeof params.name !== "string") {
3077
+ throw new Error("tools/call requires a tool name");
3078
+ }
3079
+ if (!TOOL_NAMES.has(params.name)) {
3080
+ throw new Error(`Unsupported Puppetry MCP tool: ${params.name}`);
3081
+ }
3082
+ const result = await callPuppetryAgentTool(
3083
+ client2,
3084
+ params.name,
3085
+ isObject(params.arguments) ? params.arguments : {}
3086
+ );
3087
+ return {
3088
+ content: [
3089
+ {
3090
+ type: "text",
3091
+ text: JSON.stringify(result, null, 2)
3092
+ }
3093
+ ]
3094
+ };
3095
+ }
3096
+ function requestedProtocolVersion(params) {
3097
+ if (isObject(params) && typeof params.protocolVersion === "string") {
3098
+ return params.protocolVersion;
3099
+ }
3100
+ return "2024-11-05";
3101
+ }
3102
+ function success(id, result) {
3103
+ return {
3104
+ jsonrpc: "2.0",
3105
+ id: id ?? null,
3106
+ result
3107
+ };
3108
+ }
3109
+ function error(id, code, message, data) {
3110
+ return {
3111
+ jsonrpc: "2.0",
3112
+ id: id ?? null,
3113
+ error: data === void 0 ? { code, message } : { code, message, data }
3114
+ };
3115
+ }
3116
+ function isObject(value) {
3117
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3118
+ }
3119
+
3120
+ // src/mcp-stdio.ts
3121
+ var apiKey = import_node_process.default.env.PUPPETRY_API_KEY;
3122
+ if (!apiKey) {
3123
+ import_node_process.default.stderr.write("PUPPETRY_API_KEY is required to run puppetry-mcp.\n");
3124
+ import_node_process.default.exit(1);
3125
+ }
3126
+ var client = new Puppetry({
3127
+ apiKey,
3128
+ baseUrl: import_node_process.default.env.PUPPETRY_BASE_URL
3129
+ });
3130
+ var input = import_node_buffer.Buffer.alloc(0);
3131
+ var queue = Promise.resolve();
3132
+ import_node_process.default.stdin.on("data", (chunk) => {
3133
+ input = import_node_buffer.Buffer.concat([input, chunk]);
3134
+ queue = queue.then(processMessages).catch((err) => {
3135
+ import_node_process.default.stderr.write(
3136
+ `Failed to process Puppetry MCP request: ${err instanceof Error ? err.message : String(err)}
3137
+ `
3138
+ );
3139
+ });
3140
+ });
3141
+ import_node_process.default.stdin.resume();
3142
+ async function processMessages() {
3143
+ while (true) {
3144
+ const frame = readFrame();
3145
+ if (!frame) return;
3146
+ const request = JSON.parse(frame);
3147
+ const response = await handlePuppetryMcpRequest(client, request);
3148
+ if (response) writeFrame(response);
3149
+ }
3150
+ }
3151
+ function readFrame() {
3152
+ const headerEnd = input.indexOf("\r\n\r\n");
3153
+ if (headerEnd === -1) return null;
3154
+ const header = input.subarray(0, headerEnd).toString("utf8");
3155
+ const match = /^Content-Length:\s*(\d+)$/im.exec(header);
3156
+ if (!match) {
3157
+ throw new Error("Missing Content-Length header");
3158
+ }
3159
+ const bodyStart = headerEnd + 4;
3160
+ const bodyLength = Number(match[1]);
3161
+ const totalLength = bodyStart + bodyLength;
3162
+ if (input.length < totalLength) return null;
3163
+ const body = input.subarray(bodyStart, totalLength).toString("utf8");
3164
+ input = input.subarray(totalLength);
3165
+ return body;
3166
+ }
3167
+ function writeFrame(message) {
3168
+ const body = JSON.stringify(message);
3169
+ import_node_process.default.stdout.write(
3170
+ `Content-Length: ${import_node_buffer.Buffer.byteLength(body, "utf8")}\r
3171
+ \r
3172
+ ${body}`
3173
+ );
3174
+ }