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