@flatkey-ai/cli 0.1.17 → 0.1.18
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/package.json +1 -1
- package/src/api.js +8 -0
- package/src/artifacts.js +6 -1
- package/src/cli.js +65 -2
package/package.json
CHANGED
package/src/api.js
CHANGED
|
@@ -43,6 +43,10 @@ export function generateVideo(options) {
|
|
|
43
43
|
return requestJsonFromPlan(options, planVideoRequest(options));
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
export function getVideo(options, taskId) {
|
|
47
|
+
return requestJsonFromPlan(options, planVideoStatusRequest(options, taskId));
|
|
48
|
+
}
|
|
49
|
+
|
|
46
50
|
export function planVideoRequest(options) {
|
|
47
51
|
const model = options.model ?? "seedance-2.0-pro";
|
|
48
52
|
const imageUrls = [
|
|
@@ -84,6 +88,10 @@ export function planVideoRequest(options) {
|
|
|
84
88
|
return planJsonPost(options, "/v1/video/generations", basePayload);
|
|
85
89
|
}
|
|
86
90
|
|
|
91
|
+
export function planVideoStatusRequest(options, taskId) {
|
|
92
|
+
return planRequest(options, `/v1/videos/${encodeURIComponent(taskId)}`);
|
|
93
|
+
}
|
|
94
|
+
|
|
87
95
|
export function generateAudio(options) {
|
|
88
96
|
return requestBinaryArtifactFromPlan(options, planAudioRequest(options));
|
|
89
97
|
}
|
package/src/artifacts.js
CHANGED
|
@@ -47,7 +47,7 @@ function expandHomePath(path) {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
async function persistItem({ kind, item, outDir, output, index, fetchImpl }) {
|
|
50
|
-
const dataUrl = getString(item, ["url", "data_url", "dataUrl"]);
|
|
50
|
+
const dataUrl = getString(item, ["url", "video_url", "data_url", "dataUrl"]);
|
|
51
51
|
if (dataUrl?.startsWith("data:")) {
|
|
52
52
|
const parsed = parseDataUrl(dataUrl);
|
|
53
53
|
const path = artifactPath({ kind, outDir, output, index, extension: parsed.extension });
|
|
@@ -90,15 +90,20 @@ async function downloadArtifact({ url, path, fetchImpl }) {
|
|
|
90
90
|
function extractItems(response) {
|
|
91
91
|
if (Array.isArray(response?.data)) return response.data;
|
|
92
92
|
if (Array.isArray(response?.artifacts)) return response.artifacts;
|
|
93
|
+
if (Array.isArray(response?.content)) return response.content;
|
|
93
94
|
if (Array.isArray(response?.candidates)) {
|
|
94
95
|
return response.candidates.flatMap((candidate) => candidate.content?.parts ?? []);
|
|
95
96
|
}
|
|
97
|
+
if (typeof response?.metadata?.url === "string") return [{ url: response.metadata.url }];
|
|
98
|
+
if (typeof response?.url === "string") return [response];
|
|
99
|
+
if (typeof response?.video_url?.url === "string") return [{ url: response.video_url.url }];
|
|
96
100
|
return [];
|
|
97
101
|
}
|
|
98
102
|
|
|
99
103
|
function getString(item, keys) {
|
|
100
104
|
for (const key of keys) {
|
|
101
105
|
if (typeof item?.[key] === "string") return item[key];
|
|
106
|
+
if (typeof item?.[key]?.url === "string") return item[key].url;
|
|
102
107
|
if (typeof item?.inlineData?.[key] === "string") return item.inlineData[key];
|
|
103
108
|
if (typeof item?.inline_data?.[key] === "string") return item.inline_data[key];
|
|
104
109
|
}
|
package/src/cli.js
CHANGED
|
@@ -403,6 +403,7 @@ async function handleGenerate(command, deps) {
|
|
|
403
403
|
generateImage,
|
|
404
404
|
generateText,
|
|
405
405
|
generateVideo,
|
|
406
|
+
getVideo,
|
|
406
407
|
planAudioMusicRequest,
|
|
407
408
|
planAudioRequest,
|
|
408
409
|
planAudioSfxRequest,
|
|
@@ -428,6 +429,7 @@ async function handleGenerate(command, deps) {
|
|
|
428
429
|
...command.options,
|
|
429
430
|
apiKey,
|
|
430
431
|
baseUrl: routerOrigin,
|
|
432
|
+
env: deps.env ?? process.env,
|
|
431
433
|
fetch: deps.fetch,
|
|
432
434
|
};
|
|
433
435
|
if (command.options.dry_run) {
|
|
@@ -461,6 +463,9 @@ async function handleGenerate(command, deps) {
|
|
|
461
463
|
? await generateAudioMusic(options)
|
|
462
464
|
: await generateAudio(options)
|
|
463
465
|
: await generateText(options);
|
|
466
|
+
const artifactResponse = command.group === "video"
|
|
467
|
+
? await waitForVideoResult(response, { ...options, getVideo, sleep: deps.sleep })
|
|
468
|
+
: response;
|
|
464
469
|
if (command.group === "text") {
|
|
465
470
|
const text = extractText(response);
|
|
466
471
|
const output = await writeTextOutput(text, command.options.output);
|
|
@@ -468,17 +473,75 @@ async function handleGenerate(command, deps) {
|
|
|
468
473
|
}
|
|
469
474
|
const artifacts = await persistArtifacts({
|
|
470
475
|
kind: command.group,
|
|
471
|
-
response,
|
|
476
|
+
response: artifactResponse,
|
|
472
477
|
outDir: command.options.out ?? "flatkey-output",
|
|
473
478
|
output: command.options.output,
|
|
474
479
|
fetch: deps.fetch,
|
|
475
480
|
});
|
|
476
|
-
return { kind: command.group, artifacts, response: scrubArtifactResponse(
|
|
481
|
+
return { kind: command.group, artifacts, response: scrubArtifactResponse(artifactResponse) };
|
|
477
482
|
} finally {
|
|
478
483
|
animation.stop();
|
|
479
484
|
}
|
|
480
485
|
}
|
|
481
486
|
|
|
487
|
+
async function waitForVideoResult(response, options) {
|
|
488
|
+
if (hasArtifact(response)) return response;
|
|
489
|
+
const taskId = videoTaskId(response);
|
|
490
|
+
if (!taskId) return response;
|
|
491
|
+
|
|
492
|
+
const timeoutMs = parsePositiveInteger(
|
|
493
|
+
options.video_wait_timeout_ms ?? options.env?.FLATKEY_VIDEO_WAIT_TIMEOUT_MS,
|
|
494
|
+
600_000,
|
|
495
|
+
);
|
|
496
|
+
const intervalMs = parsePositiveInteger(
|
|
497
|
+
options.video_poll_interval_ms ?? options.env?.FLATKEY_VIDEO_POLL_INTERVAL_MS,
|
|
498
|
+
5_000,
|
|
499
|
+
);
|
|
500
|
+
const deadline = Date.now() + timeoutMs;
|
|
501
|
+
let last = response;
|
|
502
|
+
|
|
503
|
+
while (Date.now() <= deadline) {
|
|
504
|
+
last = await options.getVideo(options, taskId);
|
|
505
|
+
if (hasArtifact(last) || isVideoDone(last)) return last;
|
|
506
|
+
if (isVideoFailed(last)) {
|
|
507
|
+
const message = last?.error?.message ?? last?.message ?? `Video generation failed: ${taskId}`;
|
|
508
|
+
throw new Error(message);
|
|
509
|
+
}
|
|
510
|
+
await delay(intervalMs, { sleep: options.sleep });
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
throw new Error(`Video task still processing after ${Math.round(timeoutMs / 1000)}s: ${taskId}`);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
function hasArtifact(response) {
|
|
517
|
+
return Boolean(
|
|
518
|
+
response?.metadata?.url
|
|
519
|
+
|| response?.url
|
|
520
|
+
|| response?.video_url?.url
|
|
521
|
+
|| response?.data?.some?.((item) => item?.url || item?.b64_json || item?.base64 || item?.data)
|
|
522
|
+
|| response?.artifacts?.some?.((item) => item?.url || item?.path)
|
|
523
|
+
|| response?.content?.some?.((item) => item?.url || item?.video_url?.url || item?.data_url || item?.b64_json || item?.base64 || item?.data),
|
|
524
|
+
);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function videoTaskId(response) {
|
|
528
|
+
return firstNonEmpty(response?.id, response?.task_id, response?.taskId, response?.data?.id, response?.data?.task_id);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
function isVideoDone(response) {
|
|
532
|
+
return ["completed", "succeeded", "success"].includes(String(response?.status ?? "").toLowerCase());
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function isVideoFailed(response) {
|
|
536
|
+
return ["failed", "failure", "cancelled", "canceled"].includes(String(response?.status ?? "").toLowerCase())
|
|
537
|
+
|| Boolean(response?.error);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
function parsePositiveInteger(value, fallback) {
|
|
541
|
+
const parsed = Number.parseInt(value, 10);
|
|
542
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
543
|
+
}
|
|
544
|
+
|
|
482
545
|
function scrubArtifactResponse(value) {
|
|
483
546
|
if (Array.isArray(value)) return value.map(scrubArtifactResponse);
|
|
484
547
|
if (!value || typeof value !== "object") {
|