@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/README.md +372 -0
- package/dist/chunk-IP47SEN7.mjs +1638 -0
- package/dist/chunk-QK5VPBL7.mjs +1500 -0
- package/dist/index.d.mts +1105 -0
- package/dist/index.d.ts +1105 -0
- package/dist/index.js +3163 -0
- package/dist/index.mjs +28 -0
- package/dist/mcp-ChYQVbVe.d.mts +947 -0
- package/dist/mcp-ChYQVbVe.d.ts +947 -0
- package/dist/mcp-stdio.d.mts +1 -0
- package/dist/mcp-stdio.d.ts +1 -0
- package/dist/mcp-stdio.js +3174 -0
- package/dist/mcp-stdio.mjs +65 -0
- package/dist/mcp.d.mts +1 -0
- package/dist/mcp.d.ts +1 -0
- package/dist/mcp.js +1451 -0
- package/dist/mcp.mjs +6 -0
- package/package.json +61 -0
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
|
+
});
|