videobook 0.3.0 → 0.3.2
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/CHANGELOG.md +135 -0
- package/dist/asset-media-projection.js +161 -0
- package/dist/command-reference.js +3 -1
- package/dist/diagnostics.js +10 -4
- package/dist/generation-reference-mentions.js +19 -0
- package/dist/goal-plan-v3.js +21 -6
- package/dist/goal-plan.js +1 -1
- package/dist/goal-store.js +2 -2
- package/dist/har-capture.js +36 -3
- package/dist/jobs/_image-edit-adapter.js +2 -0
- package/dist/jobs/_kie-upload.js +3 -1
- package/dist/jobs/assess-media-probes.js +94 -0
- package/dist/jobs/complete-provider-task.js +2 -5
- package/dist/jobs/generate-media.js +22 -12
- package/dist/jobs/image-subjects.js +2 -0
- package/dist/jobs/kie-paygate.js +3 -3
- package/dist/jobs/register-all.js +2 -0
- package/dist/jobs/run-notebook.js +16 -1
- package/dist/library-asset-projection.js +141 -0
- package/dist/lightning-defaults.js +4 -4
- package/dist/media/catalog.js +12 -6
- package/dist/media/descriptor.js +16 -7
- package/dist/media/frontier-gaps.js +0 -1
- package/dist/media/har-endpoint.js +25 -0
- package/dist/media/json-schema.js +3 -0
- package/dist/media/listing.js +26 -4
- package/dist/media/preferred-defaults.js +161 -0
- package/dist/media/prompt-dialects.js +9 -1
- package/dist/media-library.js +158 -153
- package/dist/notebook-sticky-settings.js +60 -0
- package/dist/provider-usage.js +24 -9
- package/dist/tools/estimate-cost.js +9 -4
- package/dist/tools/reconcile-project-work.js +37 -184
- package/dist/tools/videobook-domain.js +16 -1
- package/dist/usage-providers.js +3 -3
- package/package.json +15 -15
- package/ui/assets/{alive-captions-Cj8C1tCN.js → alive-captions-BWk_4aX0.js} +1 -1
- package/ui/assets/confirm-bodies-CNW9vLsi.js +1 -0
- package/ui/assets/{fullscreen-DyEncSdm.js → fullscreen-ND_H86Ih.js} +1 -1
- package/ui/assets/{goal-ui-BdFeoSWh.js → goal-ui-CCTD4lxi.js} +1 -1
- package/ui/assets/index-CTPstLYq.css +1 -0
- package/ui/assets/index-D0ftggO4.js +37 -0
- package/ui/assets/inspector-BZnbKsEZ.js +1 -0
- package/ui/assets/{jobs-ui-BudI4w0r.js → jobs-ui-BNK0IPSl.js} +1 -1
- package/ui/assets/{music-video-CT4C55ok.js → music-video-CoOI0cgW.js} +1 -1
- package/ui/assets/{ui-mq0uIS5P.css → ui-BBM3YCY6.css} +1 -1
- package/ui/assets/ui-koLWlu0t.js +2 -0
- package/ui/assets/vendor-Ca2-b3hB.js +19 -0
- package/ui/assets/{vendor-flow-nqTFQmTh.js → vendor-flow-C2NYBYCL.js} +1 -1
- package/ui/assets/vendor-icons-D3KrGMJY.js +1 -0
- package/ui/assets/{vendor-mcp-D_C3EZPv.js → vendor-mcp-9gUvOX4H.js} +2 -2
- package/ui/assets/videobook-B8dYNQrG.js +21 -0
- package/ui/index.html +14 -14
- package/ui/assets/confirm-bodies-BxWXwDf0.js +0 -1
- package/ui/assets/index-DWfR79lA.css +0 -1
- package/ui/assets/index-QZ8lbemN.js +0 -37
- package/ui/assets/inspector-BC6xWkJU.js +0 -1
- package/ui/assets/ui-z5r-p0K9.js +0 -2
- package/ui/assets/vendor-D8XcMav4.js +0 -19
- package/ui/assets/vendor-icons-CGqhCIXV.js +0 -1
- package/ui/assets/videobook-CsW453sb.js +0 -21
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { probeVideo } from "../ffmpeg.js";
|
|
3
|
+
import { logger } from "../logger.js";
|
|
4
|
+
import { queue, registerJobHandler } from "../queue.js";
|
|
5
|
+
import { invalidVideoProxyMetadata, probeStoredVideoFile, videoProxyAssessmentMatches, videoProxyMetadata, videoProxyValidationError, } from "../video-proxy.js";
|
|
6
|
+
const PROXY_FILENAME = "proxy.mp4";
|
|
7
|
+
export function scheduleVideoProxyAssessment(input) {
|
|
8
|
+
const jobId = `assess-video-proxy-${input.bookId}-${input.artifactId}-${input.proxyFile.objectHash}`;
|
|
9
|
+
void queue.enqueue(jobId, {
|
|
10
|
+
type: "assess_video_proxy",
|
|
11
|
+
bookId: input.bookId,
|
|
12
|
+
proxyAssetId: input.artifactId,
|
|
13
|
+
sourceFilename: input.sourceFile.name,
|
|
14
|
+
sourceHash: input.sourceFile.objectHash,
|
|
15
|
+
proxyHash: input.proxyFile.objectHash,
|
|
16
|
+
sourceProfile: input.sourceProfile,
|
|
17
|
+
}).catch((error) => {
|
|
18
|
+
logger.error("proxy-assessment-enqueue-failed", { assetId: input.artifactId, error });
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
export function scheduleMediaDurationBackfill(input) {
|
|
22
|
+
const jobId = `backfill-media-duration-${input.bookId}-${input.artifactId}-${input.mediaFile.objectHash}`;
|
|
23
|
+
void queue.enqueue(jobId, {
|
|
24
|
+
type: "backfill_media_duration",
|
|
25
|
+
bookId: input.bookId,
|
|
26
|
+
durationAssetId: input.artifactId,
|
|
27
|
+
mediaFilename: input.mediaFile.name,
|
|
28
|
+
mediaHash: input.mediaFile.objectHash,
|
|
29
|
+
}).catch((error) => {
|
|
30
|
+
logger.error("duration-backfill-enqueue-failed", { assetId: input.artifactId, error });
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
async function currentFile(engine, artifactId, name, objectHash) {
|
|
34
|
+
const manifest = await engine.files.manifest(artifactId);
|
|
35
|
+
if (!manifest.ok)
|
|
36
|
+
return null;
|
|
37
|
+
return manifest.value.files.find((file) => file.name === name && file.objectHash === objectHash) ?? null;
|
|
38
|
+
}
|
|
39
|
+
export async function assessStoredVideoProxy(engine, assetId, sourceFile, proxyFile, sourceProfile) {
|
|
40
|
+
let assessment;
|
|
41
|
+
try {
|
|
42
|
+
const proxy = await probeStoredVideoFile(engine, assetId, proxyFile.name);
|
|
43
|
+
const error = videoProxyValidationError(sourceProfile, proxy);
|
|
44
|
+
assessment = error
|
|
45
|
+
? invalidVideoProxyMetadata(sourceFile, proxyFile, error)
|
|
46
|
+
: videoProxyMetadata(sourceFile, proxyFile, proxy);
|
|
47
|
+
}
|
|
48
|
+
catch (cause) {
|
|
49
|
+
const error = cause instanceof Error ? cause.message : String(cause);
|
|
50
|
+
assessment = invalidVideoProxyMetadata(sourceFile, proxyFile, error);
|
|
51
|
+
}
|
|
52
|
+
await engine.metadata.artifacts.write(assetId, "proxy", assessment);
|
|
53
|
+
return assessment.valid;
|
|
54
|
+
}
|
|
55
|
+
async function runVideoProxyAssessment(engine, payload) {
|
|
56
|
+
const { proxyAssetId } = payload;
|
|
57
|
+
const sourceFile = await currentFile(engine, proxyAssetId, payload.sourceFilename, payload.sourceHash);
|
|
58
|
+
const proxyFile = await currentFile(engine, proxyAssetId, PROXY_FILENAME, payload.proxyHash);
|
|
59
|
+
if (!sourceFile || !proxyFile)
|
|
60
|
+
return { skipped: true };
|
|
61
|
+
const existing = await engine.metadata.artifacts.read(proxyAssetId, "proxy");
|
|
62
|
+
if (existing.ok && videoProxyAssessmentMatches(existing.value, sourceFile, proxyFile)) {
|
|
63
|
+
return { skipped: true };
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
valid: await assessStoredVideoProxy(engine, proxyAssetId, sourceFile, proxyFile, payload.sourceProfile),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
async function runMediaDurationBackfill(engine, payload, signal) {
|
|
70
|
+
const { durationAssetId } = payload;
|
|
71
|
+
const metadata = await engine.metadata.artifacts.read(durationAssetId, "original");
|
|
72
|
+
if (!metadata.ok || typeof metadata.value.duration === "number")
|
|
73
|
+
return { skipped: true };
|
|
74
|
+
if (!await currentFile(engine, durationAssetId, payload.mediaFilename, payload.mediaHash)) {
|
|
75
|
+
return { skipped: true };
|
|
76
|
+
}
|
|
77
|
+
const dir = await engine.workspaces.resolveArtifact(durationAssetId);
|
|
78
|
+
if (!dir.ok)
|
|
79
|
+
throw new Error(`backfill_media_duration: ${dir.error.message}`);
|
|
80
|
+
const probe = await probeVideo(join(dir.value, payload.mediaFilename), { signal });
|
|
81
|
+
if (!Number.isFinite(probe.duration) || probe.duration <= 0)
|
|
82
|
+
return { skipped: true };
|
|
83
|
+
const written = await engine.metadata.artifacts.write(durationAssetId, "original", {
|
|
84
|
+
...metadata.value,
|
|
85
|
+
duration: probe.duration,
|
|
86
|
+
});
|
|
87
|
+
if (!written.ok)
|
|
88
|
+
throw new Error(written.error.message);
|
|
89
|
+
return { duration: probe.duration };
|
|
90
|
+
}
|
|
91
|
+
export function registerAssessMediaProbeJobs(engine) {
|
|
92
|
+
registerJobHandler("assess_video_proxy", async (payload) => runVideoProxyAssessment(engine, payload));
|
|
93
|
+
registerJobHandler("backfill_media_duration", async (payload, { signal }) => runMediaDurationBackfill(engine, payload, signal));
|
|
94
|
+
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { withHarCapture } from "../har-capture.js";
|
|
2
1
|
import { join } from "node:path";
|
|
3
2
|
import { tmpdir } from "node:os";
|
|
4
3
|
import { writeFile, unlink, readFile as readFsFile } from "node:fs/promises";
|
|
@@ -48,9 +47,7 @@ async function reclaimFailedCompletion(engine, task, ctx) {
|
|
|
48
47
|
return lease.ownerId;
|
|
49
48
|
}
|
|
50
49
|
export function registerCompleteProviderTaskJob(engine) {
|
|
51
|
-
registerJobHandler("complete_kie_task", async (payload, ctx) =>
|
|
52
|
-
provider: String(payload.taskType).split("_")[0] ?? "unknown", endpoint: "generation.complete", input: payload,
|
|
53
|
-
}, async () => {
|
|
50
|
+
registerJobHandler("complete_kie_task", async (payload, ctx) => {
|
|
54
51
|
const { taskId, taskType, assetId, bookId, meta, resultUrls, resultObject, } = payload;
|
|
55
52
|
// Pattern C: inherit the provider lease via getOwner, abort if the row
|
|
56
53
|
// already terminated, and register the lease so the wrapper's catch path
|
|
@@ -128,7 +125,7 @@ export function registerCompleteProviderTaskJob(engine) {
|
|
|
128
125
|
clearInterval(heartbeat);
|
|
129
126
|
await engine.jobs.locks.release(assetDir);
|
|
130
127
|
}
|
|
131
|
-
}
|
|
128
|
+
});
|
|
132
129
|
}
|
|
133
130
|
async function mediaOutputs(taskType, taskId, urls) {
|
|
134
131
|
if (taskType === MEDIA_TASK_TYPES.machgen)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { withHarLocalFailure } from "../har-capture.js";
|
|
2
|
+
import { generationHarEndpoint } from "../media/har-endpoint.js";
|
|
2
3
|
import { withBookContext } from "../book-context.js";
|
|
3
4
|
import { isSeedanceEdit } from "../media/seedance-edit.js";
|
|
4
5
|
import { registerJobHandler } from "../queue.js";
|
|
@@ -21,24 +22,33 @@ async function replaceAudioMeta(engine, ref) {
|
|
|
21
22
|
const input = await resolveRefInput(engine, "audio", ref);
|
|
22
23
|
return { replaceAudioWith: { assetId: input.assetId, filename: input.filename } };
|
|
23
24
|
}
|
|
25
|
+
async function sourceAudioSeconds(engine, assetIds) {
|
|
26
|
+
for (const assetId of assetIds) {
|
|
27
|
+
const metadata = await engine.metadata.artifacts.read(assetId, "original");
|
|
28
|
+
const seconds = metadata.ok ? metadata.value.duration : undefined;
|
|
29
|
+
if (typeof seconds === "number" && Number.isFinite(seconds) && seconds > 0)
|
|
30
|
+
return seconds;
|
|
31
|
+
}
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
24
34
|
export async function generationSourceCostHints(engine, refs, job) {
|
|
25
35
|
const draftHints = await seedanceDraftCostHints(engine, job.model, job.options ?? {});
|
|
26
36
|
if (Object.keys(draftHints).length > 0)
|
|
27
37
|
return draftHints;
|
|
38
|
+
const model = resolveModel(job.model);
|
|
39
|
+
if (model.entry.outputLength === "audio") {
|
|
40
|
+
const seconds = await sourceAudioSeconds(engine, refs.sourceAudioIds);
|
|
41
|
+
return seconds === undefined ? {} : { durationSeconds: seconds };
|
|
42
|
+
}
|
|
28
43
|
const videoSeconds = await referenceVideoDuration(engine, refs.sourceVideoIds);
|
|
29
44
|
if (videoSeconds !== undefined) {
|
|
30
|
-
|
|
45
|
+
const followsVideo = model.entry.outputLength === "video" || isSeedanceEdit(job.model, job.mode);
|
|
46
|
+
return { inputDurationSeconds: videoSeconds, ...(followsVideo ? { durationSeconds: videoSeconds } : {}) };
|
|
31
47
|
}
|
|
32
|
-
if (
|
|
48
|
+
if (model.provider !== "fal")
|
|
33
49
|
return {};
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const seconds = metadata.ok ? metadata.value.duration : undefined;
|
|
37
|
-
if (typeof seconds === "number" && Number.isFinite(seconds) && seconds > 0) {
|
|
38
|
-
return { inputDurationSeconds: seconds };
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
return {};
|
|
50
|
+
const seconds = await sourceAudioSeconds(engine, refs.sourceAudioIds);
|
|
51
|
+
return seconds === undefined ? {} : { inputDurationSeconds: seconds };
|
|
42
52
|
}
|
|
43
53
|
function generationMeta(job, model, refs) {
|
|
44
54
|
return {
|
|
@@ -87,7 +97,7 @@ async function generateMedia(engine, job, ctx) {
|
|
|
87
97
|
}
|
|
88
98
|
export async function runGenerateMedia(engine, job, ctx) {
|
|
89
99
|
return withBookContext({ bookId: job.bookId, engine, operation: "generate_media", artifactId: job.assetId,
|
|
90
|
-
attemptId: job.generationId ?? job.assetId, runId: job.runId, model: job.model }, () =>
|
|
100
|
+
attemptId: job.generationId ?? job.assetId, runId: job.runId, model: job.model }, () => withHarLocalFailure({ provider: resolveModel(job.model).provider, endpoint: generationHarEndpoint(resolveModel(job.model)), input: job }, () => generateMedia(engine, job, ctx)));
|
|
91
101
|
}
|
|
92
102
|
export function registerGenerateMediaJob(engine) {
|
|
93
103
|
registerJobHandler("generate_media", (payload, ctx) => runGenerateMedia(engine, payload, ctx));
|
|
@@ -4,6 +4,7 @@ import sharp from "sharp";
|
|
|
4
4
|
import { KIE_RESULT_TIMEOUT_MS, kieProvider, parseResultUrls, waitForKieResult, } from "./_kie-poll.js";
|
|
5
5
|
import { GROK_EXACT_MASK_ADAPTER_ID, imageEditAdapter, IMAGE_EDIT_ADAPTERS, } from "./_image-edit-adapter.js";
|
|
6
6
|
import { DEFAULT_IMAGE_EDIT_MODEL_ID } from "../image-edit-models.js";
|
|
7
|
+
import { requireApiKey } from "../api-key.js";
|
|
7
8
|
import { compositeImageRegionEdit, imageRegionEditPrompt } from "../image-region-edit.js";
|
|
8
9
|
import { IMAGE_SUBJECT_MAP_VERSION, applyGrokSegmentsToSubjects, composeImageSubjectRemoval, exactEditMaskIndexes, grokMaskIndex, parseGrokSegmentMapResult, persistSeedreamSubjectLayers, readImageSubjectMapRecord, resolveImageSubjectSource, writeCurrentImageSubjectAnalysisRecord, } from "../image-subject-map.js";
|
|
9
10
|
import { enqueueSearchIndex } from "./enqueue-search-index.js";
|
|
@@ -534,6 +535,7 @@ export async function previewImageSubjectEditRequest(engine, payload, imageUrls)
|
|
|
534
535
|
: adapter.buildSemanticRequest(input);
|
|
535
536
|
}
|
|
536
537
|
async function runAdapterImageSubjectEdit(engine, payload, adapter, route, prepared, sourceFilename, signal) {
|
|
538
|
+
requireApiKey(adapter.envKey);
|
|
537
539
|
const stored = await readStoredImageSubjectEditTask(engine, payload.imageAssetId);
|
|
538
540
|
const storedMatch = stored?.adapterId === adapter.id ? stored : null;
|
|
539
541
|
let request = storedMatch?.request ?? null;
|
package/dist/jobs/kie-paygate.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { withHarLocalFailure } from "../har-capture.js";
|
|
2
2
|
import { createKie } from "../usage-providers.js";
|
|
3
3
|
import { submitMediaJob } from "../usage-providers.js";
|
|
4
4
|
import { mintOtp } from "@apicity/cost";
|
|
@@ -177,8 +177,8 @@ async function submitQueuedKieTaskInner(engine, task) {
|
|
|
177
177
|
return writeResult.value.task;
|
|
178
178
|
}
|
|
179
179
|
export async function submitKieTask(engine, ctx, args) {
|
|
180
|
-
return
|
|
180
|
+
return withHarLocalFailure({ provider: "kie", endpoint: "api.v1.jobs.createTask", input: args.request }, () => submitKieTaskInner(engine, ctx, args));
|
|
181
181
|
}
|
|
182
182
|
export async function submitQueuedKieTask(engine, task) {
|
|
183
|
-
return
|
|
183
|
+
return withHarLocalFailure({ provider: "kie", endpoint: "api.v1.jobs.createTask", input: task }, () => submitQueuedKieTaskInner(engine, task));
|
|
184
184
|
}
|
|
@@ -38,6 +38,7 @@ import { registerLabelMusicSectionsJob } from "./label-music-sections.js";
|
|
|
38
38
|
import { registerEditingMomentsJob } from "./analyze-editing-moments.js";
|
|
39
39
|
import { registerExtractWaveformJob } from "./extract-waveform.js";
|
|
40
40
|
import { registerExtractVideoPosterJob } from "./extract-video-poster.js";
|
|
41
|
+
import { registerAssessMediaProbeJobs } from "./assess-media-probes.js";
|
|
41
42
|
import { registerReplaceVideoAudioJob } from "./replace-video-audio.js";
|
|
42
43
|
import { registerSpliceVideoJob } from "./splice-video.js";
|
|
43
44
|
import { registerSmoothSceneConcatJob } from "./smooth-scene-concat.js";
|
|
@@ -109,6 +110,7 @@ export function registerAllJobHandlers(engine, books = bookManager) {
|
|
|
109
110
|
registerExtractAudioJob(engine);
|
|
110
111
|
registerExtractWaveformJob(engine);
|
|
111
112
|
registerExtractVideoPosterJob(engine);
|
|
113
|
+
registerAssessMediaProbeJobs(engine);
|
|
112
114
|
registerAnalyzeMusicJob(engine);
|
|
113
115
|
registerPlanGoalJob(engine);
|
|
114
116
|
registerAssembleGoalJob(engine);
|
|
@@ -2609,6 +2609,21 @@ async function finishAwaitingNotebookCell(engine, input) {
|
|
|
2609
2609
|
throw new Error(refreshed.error.message);
|
|
2610
2610
|
return { outputArtifactId, document: refreshed.value };
|
|
2611
2611
|
}
|
|
2612
|
+
function withFailedGenerationPlan(document, cellId, runId, error, updatedAt) {
|
|
2613
|
+
return {
|
|
2614
|
+
...document,
|
|
2615
|
+
generationPlans: (document.generationPlans ?? []).map((record) => record.cellId === cellId
|
|
2616
|
+
&& (record.status === "queued" || record.status === "awaiting-output")
|
|
2617
|
+
&& generationPlanClaimRunId(record.plan) === runId
|
|
2618
|
+
? {
|
|
2619
|
+
...record,
|
|
2620
|
+
status: "failed",
|
|
2621
|
+
updatedAt,
|
|
2622
|
+
error: truncateGenerationError(error),
|
|
2623
|
+
}
|
|
2624
|
+
: record),
|
|
2625
|
+
};
|
|
2626
|
+
}
|
|
2612
2627
|
async function failAwaitingNotebookCell(engine, input, message) {
|
|
2613
2628
|
const updatedAt = new Date().toISOString();
|
|
2614
2629
|
const result = await mutateNotebook(engine, input.notebookId, "fail_notebook_provider_output", (document) => {
|
|
@@ -2628,7 +2643,7 @@ async function failAwaitingNotebookCell(engine, input, message) {
|
|
|
2628
2643
|
: undefined) ?? cell.id;
|
|
2629
2644
|
const current = executionState(document)[failTargetId]
|
|
2630
2645
|
?? executionState(document)[cell.id];
|
|
2631
|
-
const withFailure = withExecution(document, failTargetId, {
|
|
2646
|
+
const withFailure = withExecution(withFailedGenerationPlan(document, cell.id, input.runId, message, updatedAt), failTargetId, {
|
|
2632
2647
|
fingerprint: current?.fingerprint
|
|
2633
2648
|
?? notebookCellFingerprint(document, cell),
|
|
2634
2649
|
status: "error",
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { computeArtifactStatus } from "videobook-engine";
|
|
2
|
+
import { EMPTY_TAG_SUMMARY, matchesTagFilter, readAssetTagSummaries, } from "./asset-tags.js";
|
|
3
|
+
import { assetDuration, assetMediaUrls, assetStreamProfile, findAssetMediaFile, groupByArtifactId, indexByArtifactId, PARTIAL_DOWNLOAD_RE, scheduleMissingVideoPoster, servedVideoProxyFile, } from "./asset-media-projection.js";
|
|
4
|
+
function metadataNumber(meta, key) {
|
|
5
|
+
const value = meta?.[key];
|
|
6
|
+
return typeof value === "number" ? value : null;
|
|
7
|
+
}
|
|
8
|
+
function metadataString(meta, key) {
|
|
9
|
+
const value = meta?.[key];
|
|
10
|
+
return typeof value === "string" ? value : null;
|
|
11
|
+
}
|
|
12
|
+
function readJobIndexes(engine) {
|
|
13
|
+
const pending = engine.jobs.pending.findAll();
|
|
14
|
+
const failures = engine.jobs.failures.findAll();
|
|
15
|
+
const work = engine.jobs.artifactWork.list();
|
|
16
|
+
const streams = engine.streams.list();
|
|
17
|
+
return {
|
|
18
|
+
pending: indexByArtifactId(pending.ok ? pending.value : []),
|
|
19
|
+
failures: indexByArtifactId(failures.ok ? failures.value : []),
|
|
20
|
+
work: indexByArtifactId(work.ok ? work.value : []),
|
|
21
|
+
streams: groupByArtifactId(streams.ok ? streams.value : []),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
async function readCandidate(engine, artifact, jobs, tags) {
|
|
25
|
+
const [manifest, metadata] = await Promise.all([
|
|
26
|
+
engine.files.manifest(artifact.artifactId, { includeDotfiles: true }),
|
|
27
|
+
engine.metadata.artifacts.read(artifact.artifactId, "original"),
|
|
28
|
+
]);
|
|
29
|
+
const files = manifest.ok ? manifest.value.files : [];
|
|
30
|
+
const meta = metadata.ok ? metadata.value : null;
|
|
31
|
+
const mediaFile = findAssetMediaFile(files, artifact);
|
|
32
|
+
const work = jobs.work.get(artifact.artifactId) ?? null;
|
|
33
|
+
const status = computeArtifactStatus({
|
|
34
|
+
kind: artifact.kind,
|
|
35
|
+
fileNames: new Set(files.map((file) => file.name)),
|
|
36
|
+
primaryMediaName: mediaFile?.name ?? null,
|
|
37
|
+
hasOriginalMetadata: metadata.ok,
|
|
38
|
+
hasPartFile: files.some((file) => PARTIAL_DOWNLOAD_RE.test(file.name)),
|
|
39
|
+
lockData: engine.jobs.locks.get(artifact.path),
|
|
40
|
+
pendingTask: jobs.pending.get(artifact.artifactId) ?? null,
|
|
41
|
+
generationError: jobs.failures.get(artifact.artifactId) ?? null,
|
|
42
|
+
artifactRow: work
|
|
43
|
+
? { status: work.status, meta: work.meta, deadlineAt: work.deadlineAt }
|
|
44
|
+
: null,
|
|
45
|
+
});
|
|
46
|
+
return {
|
|
47
|
+
id: artifact.artifactId,
|
|
48
|
+
artifact_id: artifact.artifactId,
|
|
49
|
+
...(artifact.label ? { label: artifact.label } : {}),
|
|
50
|
+
type: artifact.kind,
|
|
51
|
+
created_at: new Date(artifact.createdAt).toISOString(),
|
|
52
|
+
status,
|
|
53
|
+
prompt: metadataString(meta, "prompt"),
|
|
54
|
+
object_hash: mediaFile?.objectHash ?? null,
|
|
55
|
+
tags,
|
|
56
|
+
source: {
|
|
57
|
+
artifact,
|
|
58
|
+
files,
|
|
59
|
+
meta,
|
|
60
|
+
mediaFile,
|
|
61
|
+
work,
|
|
62
|
+
streams: jobs.streams.get(artifact.artifactId) ?? [],
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
export async function readLibraryCandidates(engine, input) {
|
|
67
|
+
const artifacts = engine.artifacts.list({ sort: "newest" })
|
|
68
|
+
.filter((artifact) => input.kinds.has(artifact.kind));
|
|
69
|
+
const tagsByArtifact = readAssetTagSummaries(engine, artifacts.map((artifact) => artifact.artifactId));
|
|
70
|
+
const tagged = artifacts.filter((artifact) => matchesTagFilter(tagsByArtifact.get(artifact.artifactId), input.tags));
|
|
71
|
+
if (tagged.length === 0)
|
|
72
|
+
return [];
|
|
73
|
+
const jobs = readJobIndexes(engine);
|
|
74
|
+
return Promise.all(tagged.map((artifact) => readCandidate(engine, artifact, jobs, tagsByArtifact.get(artifact.artifactId) ?? EMPTY_TAG_SUMMARY)));
|
|
75
|
+
}
|
|
76
|
+
function libraryAssetWork(work) {
|
|
77
|
+
return work
|
|
78
|
+
? {
|
|
79
|
+
status: work.status,
|
|
80
|
+
meta: work.meta,
|
|
81
|
+
ownerKind: work.ownerKind,
|
|
82
|
+
deadlineAt: work.deadlineAt,
|
|
83
|
+
}
|
|
84
|
+
: null;
|
|
85
|
+
}
|
|
86
|
+
function framesPerSecond(meta, frameRate) {
|
|
87
|
+
const metadataFps = Number(meta?.fps);
|
|
88
|
+
if (Number.isFinite(metadataFps) && metadataFps > 0)
|
|
89
|
+
return metadataFps;
|
|
90
|
+
if (!frameRate || frameRate.denominator <= 0)
|
|
91
|
+
return null;
|
|
92
|
+
const streamFps = frameRate.numerator / frameRate.denominator;
|
|
93
|
+
return Number.isFinite(streamFps) && streamFps > 0 ? streamFps : null;
|
|
94
|
+
}
|
|
95
|
+
export async function projectLibraryAsset(engine, bookId, candidate) {
|
|
96
|
+
const { artifact, files, meta, mediaFile, work, streams } = candidate.source;
|
|
97
|
+
const profile = assetStreamProfile(streams, meta);
|
|
98
|
+
const proxyMetadata = artifact.kind === "video" && files.some((file) => file.name === "proxy.mp4")
|
|
99
|
+
? await engine.metadata.artifacts.read(artifact.artifactId, "proxy")
|
|
100
|
+
: { ok: false, error: { code: "NOT_FOUND", message: "no proxy" } };
|
|
101
|
+
const proxyFile = servedVideoProxyFile({
|
|
102
|
+
bookId,
|
|
103
|
+
artifact,
|
|
104
|
+
files,
|
|
105
|
+
mediaFile,
|
|
106
|
+
proxyMetadata,
|
|
107
|
+
work,
|
|
108
|
+
sourceProfile: profile.sourceProfile,
|
|
109
|
+
});
|
|
110
|
+
await scheduleMissingVideoPoster(engine, {
|
|
111
|
+
bookId,
|
|
112
|
+
artifact,
|
|
113
|
+
files,
|
|
114
|
+
mediaFile,
|
|
115
|
+
status: candidate.status,
|
|
116
|
+
});
|
|
117
|
+
return {
|
|
118
|
+
id: candidate.id,
|
|
119
|
+
artifact_id: candidate.artifact_id,
|
|
120
|
+
...(candidate.label ? { label: candidate.label } : {}),
|
|
121
|
+
type: candidate.type,
|
|
122
|
+
created_at: candidate.created_at,
|
|
123
|
+
...assetMediaUrls({ bookId, artifact, files, mediaFile, proxyFile }),
|
|
124
|
+
object_hash: candidate.object_hash,
|
|
125
|
+
status: candidate.status,
|
|
126
|
+
assetWork: libraryAssetWork(work),
|
|
127
|
+
width: metadataNumber(meta, "width"),
|
|
128
|
+
height: metadataNumber(meta, "height"),
|
|
129
|
+
origin: metadataString(meta, "origin"),
|
|
130
|
+
prompt: candidate.prompt,
|
|
131
|
+
duration: assetDuration({
|
|
132
|
+
bookId,
|
|
133
|
+
artifactId: artifact.artifactId,
|
|
134
|
+
meta,
|
|
135
|
+
mediaFile,
|
|
136
|
+
profile,
|
|
137
|
+
}),
|
|
138
|
+
fps: framesPerSecond(meta, profile.frameRate),
|
|
139
|
+
tags: candidate.tags,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
export const LIGHTNING_SCENE_MODEL = "kie:nano-banana-2-1";
|
|
2
|
-
export const LIGHTNING_SCENE_ASPECT_RATIO = "16
|
|
3
|
-
export const LIGHTNING_SCENE_RESOLUTION = "
|
|
2
|
+
export const LIGHTNING_SCENE_ASPECT_RATIO = "9:16";
|
|
3
|
+
export const LIGHTNING_SCENE_RESOLUTION = "4K";
|
|
4
4
|
export const LIGHTNING_SCENE_OUTPUT_FORMAT = "jpg";
|
|
5
5
|
export const LIGHTNING_SCENE_PROMPT_WORDS = 25;
|
|
6
6
|
export const LIGHTNING_VIDEO_MODEL = "kie:bytedance/seedance-2-5";
|
|
7
|
-
export const LIGHTNING_VIDEO_ASPECT_RATIO = "16
|
|
7
|
+
export const LIGHTNING_VIDEO_ASPECT_RATIO = "9:16";
|
|
8
8
|
export const LIGHTNING_VIDEO_RESOLUTION = "1080p";
|
|
9
|
-
export const LIGHTNING_VIDEO_DURATION_SECONDS =
|
|
9
|
+
export const LIGHTNING_VIDEO_DURATION_SECONDS = 12;
|
|
10
10
|
export const LIGHTNING_VIDEO_OUTPUT_FORMAT = "mp4";
|
|
11
11
|
export const LIGHTNING_VIDEO_GENERATE_AUDIO = true;
|
|
12
12
|
export const LIGHTNING_VIDEO_PROMPT_WORDS = 60;
|
package/dist/media/catalog.js
CHANGED
|
@@ -22,7 +22,7 @@ const SEEDANCE_US_REFERENCE_MODES = [
|
|
|
22
22
|
];
|
|
23
23
|
const XAI_MODES = [
|
|
24
24
|
{ id: "i2v", label: "Image to video", roles: ["first_frame"] },
|
|
25
|
-
{ id: "r2v", label: "References", roles: ["image"] },
|
|
25
|
+
{ id: "r2v", label: "References", roles: ["image"], defaults: { duration: 10 } },
|
|
26
26
|
{ id: "v2v", label: "Edit video", roles: ["video"], requireAny: ["video"] },
|
|
27
27
|
];
|
|
28
28
|
const MACHGEN_FRAMES = { id: "i2v", label: "Frames", roles: ["first_frame", "last_frame"], requireAny: ["first_frame"] };
|
|
@@ -157,10 +157,11 @@ export const MODEL_ENTRIES = [
|
|
|
157
157
|
},
|
|
158
158
|
{ id: "kie:grok-imagine/image-to-video", media: "video", operations: ["image_to_video"], guide: "grok_imagine" },
|
|
159
159
|
{ id: "kie:grok-imagine/text-to-video", media: "video", operations: ["text_to_video"], guide: "grok_imagine", defaults: { duration: 6, resolution: "480p" } },
|
|
160
|
+
{ id: "kie:grok-imagine-video-1-5-preview", media: "video", operations: ["image_to_video"], title: "Grok Imagine Video 1.5 Preview (Kie)", guide: "grok_imagine", dialect: "natural_strip_frames" },
|
|
160
161
|
{ id: "kie:grok-imagine/extend", media: "video", operations: ["extend_video"], hidden: true },
|
|
161
162
|
{ id: "kie:grok-imagine/upscale", media: "video", operations: ["upscale_video"], hidden: true },
|
|
162
163
|
{ id: "kie:kling-3.0-omni/text-to-video", media: "video", operations: ["text_to_video"], guide: "kling30", dialect: "natural", defaults: { customize_multi_shots: false } },
|
|
163
|
-
{ id: "kie:kling-3.0-omni/image-to-video", media: "video", operations: ["image_to_video"], guide: "kling30", dialect: "natural" },
|
|
164
|
+
{ id: "kie:kling-3.0-omni/image-to-video", media: "video", operations: ["image_to_video"], guide: "kling30", dialect: "natural", defaults: { aspect_ratio: "auto" }, pinnedDefaults: ["aspect_ratio"] },
|
|
164
165
|
{ id: "kie:kling-3.0-omni/reference-to-video", media: "video", operations: ["reference_to_video"], guide: "kling30", dialect: "natural", defaults: { audio: false } },
|
|
165
166
|
{
|
|
166
167
|
id: "kie:kling-3.0-omni/transformation",
|
|
@@ -173,21 +174,26 @@ export const MODEL_ENTRIES = [
|
|
|
173
174
|
{ id: "v2v_refs", label: "Edit with references", roles: ["video", "image"], requireAny: ["video"] },
|
|
174
175
|
],
|
|
175
176
|
},
|
|
176
|
-
{ id: "kie:kling-3.0/motion-control", media: "video", operations: ["motion_control"], guide: "kling30", dialect: "natural", defaults: { mode: "720p" } },
|
|
177
|
+
{ id: "kie:kling-3.0/motion-control", media: "video", operations: ["motion_control"], guide: "kling30", dialect: "natural", defaults: { mode: "720p" }, outputLength: "video" },
|
|
178
|
+
{ id: "kie:kling-4.0/reference-to-video", media: "video", operations: ["reference_to_video"], dialect: "kling4" },
|
|
179
|
+
{ id: "kie:kling-4.0/keyframe-to-video", media: "video", operations: ["image_to_video"], dialect: "kling4_keyframes", refs: { image: "keyframe_urls" } },
|
|
177
180
|
{ id: "kie:gemini-omni-video", media: "video", operations: ["image_to_video", "edit_video"], dialect: "natural_source_video", defaults: { duration: "8" }, modes: [{ id: "i2v", label: "Image to video", roles: ["image"] }, { id: "v2v", label: "Edit video", roles: ["video", "image"], requireAny: ["video"] }], refItem: { video: { start: 0, ends: 8 } }, aliases: { clip_start: "video_list.0.start", clip_end: "video_list.0.ends" } },
|
|
178
181
|
{ id: "kie:wan/3-0-video", media: "video", operations: ["text_to_video", "image_to_video", "reference_to_video"], dialect: "seedance", modes: SEEDANCE_MODES },
|
|
179
182
|
{ id: "kie:wan/3-0-video-prime", media: "video", operations: ["text_to_video", "image_to_video", "reference_to_video"], dialect: "seedance", modes: SEEDANCE_MODES },
|
|
180
183
|
{ id: "kie:happyhorse-1-1/text-to-video", media: "video", operations: ["text_to_video"] },
|
|
181
184
|
{ id: "kie:happyhorse-1-1/image-to-video", media: "video", operations: ["image_to_video"], dialect: "strip_frames" },
|
|
182
185
|
{ id: "kie:happyhorse-1-1/reference-to-video", media: "video", operations: ["reference_to_video"], dialect: "brackets" },
|
|
183
|
-
{ id: "kie:happyhorse/video-edit", media: "video", operations: ["edit_video"], dialect: "happyhorse_edit" },
|
|
186
|
+
{ id: "kie:happyhorse/video-edit", media: "video", operations: ["edit_video"], dialect: "happyhorse_edit", outputLength: "video" },
|
|
184
187
|
{ id: "kie:minimax-h3/text-to-video", media: "video", operations: ["text_to_video"] },
|
|
185
188
|
{ id: "kie:minimax-h3/image-to-video", media: "video", operations: ["image_to_video"], dialect: "strip_frames" },
|
|
186
189
|
{ id: "kie:minimax-h3/reference-to-video", media: "video", operations: ["reference_to_video"], dialect: "natural", rules: { refDurationMs: MiniMaxH3ReferenceClipDurationMs } },
|
|
187
|
-
{ id: "kie:volcengine/video-to-video-lip-sync", media: "video", operations: ["lip_sync"] },
|
|
188
|
-
{ id: "kie:omnihuman-1-5", media: "video", operations: ["image_to_video", "lip_sync"], dialect: "natural" },
|
|
190
|
+
{ id: "kie:volcengine/video-to-video-lip-sync", media: "video", operations: ["lip_sync"], outputLength: "audio" },
|
|
191
|
+
{ id: "kie:omnihuman-1-5", media: "video", operations: ["image_to_video", "lip_sync"], dialect: "natural", outputLength: "audio" },
|
|
192
|
+
{ id: "kie:omnihuman-2-0", media: "video", operations: ["image_to_video", "lip_sync"], dialect: "natural", defaults: { enable_watermark: false }, outputLength: "audio" },
|
|
189
193
|
{ id: "kie:vidu-q4/image-to-video", media: "video", operations: ["image_to_video"], dialect: "natural_kinds" },
|
|
190
194
|
{ id: "kie:vidu-q4/reference-to-video", media: "video", operations: ["reference_to_video"], dialect: "natural_kinds" },
|
|
195
|
+
{ id: "kie:pixverse-v6-max/text-to-video", media: "video", operations: ["text_to_video"], defaults: { quality: "1080p" } },
|
|
196
|
+
{ id: "kie:pixverse-v6-max/image-to-video", media: "video", operations: ["image_to_video"], dialect: "strip_frames", defaults: { quality: "1080p" } },
|
|
191
197
|
{ id: "kie:veo3", media: "video", operations: ["text_to_video", "image_to_video"], title: "Veo 3 (Kie)" },
|
|
192
198
|
{ id: "kie:veo3_fast", media: "video", operations: ["text_to_video", "image_to_video"], title: "Veo 3 Fast (Kie)" },
|
|
193
199
|
{ id: "xai:grok-imagine-video", media: "video", operations: ["text_to_video", "image_to_video", "reference_to_video", "edit_video"], guide: "grok_imagine", title: "Grok Imagine Video (xAI)", dialect: "natural", refs: { first_frame: "image", audio: null }, modes: XAI_MODES },
|
package/dist/media/descriptor.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { MODEL_DISPLAY } from "@apicity/cost";
|
|
2
2
|
import { modelEntry } from "./catalog.js";
|
|
3
3
|
import { parseModelId } from "./model-id.js";
|
|
4
|
+
import { preferredDefaults } from "./preferred-defaults.js";
|
|
4
5
|
import { claimedFields, resolveSlot } from "./ref-fields.js";
|
|
5
6
|
import { wireSpec } from "./wire.js";
|
|
6
|
-
import { enumValues, numberBounds, propertyAt, schemaTypes, stringMaxLength, toJsonSchema, } from "./json-schema.js";
|
|
7
|
+
import { acceptsNoValue, enumValues, numberBounds, propertyAt, schemaTypes, stringMaxLength, toJsonSchema, } from "./json-schema.js";
|
|
7
8
|
import { REF_ROLES, } from "./types.js";
|
|
8
9
|
const PROMPT_CANDIDATES = ["prompt", "text", "instruction", "description"];
|
|
9
10
|
const cache = new Map();
|
|
@@ -74,7 +75,7 @@ function resolveOptions(entry, input, excluded, fixed) {
|
|
|
74
75
|
const defaults = entry.defaults ?? {};
|
|
75
76
|
const options = [];
|
|
76
77
|
for (const [name, schema] of Object.entries(input.properties ?? {})) {
|
|
77
|
-
if (excluded.has(name) || name in fixed)
|
|
78
|
+
if (excluded.has(name) || name in fixed || acceptsNoValue(schema))
|
|
78
79
|
continue;
|
|
79
80
|
options.push(optionSpec(name, name, schema, required.has(name), defaults));
|
|
80
81
|
}
|
|
@@ -121,8 +122,10 @@ function build(entry) {
|
|
|
121
122
|
excluded.add(top);
|
|
122
123
|
if (promptTop)
|
|
123
124
|
excluded.add(promptTop);
|
|
125
|
+
const preferred = preferredDefaults(entry, resolveOptions(entry, input, excluded, wire.fixed));
|
|
126
|
+
const tuned = { ...entry, defaults: { ...entry.defaults, ...preferred } };
|
|
124
127
|
return {
|
|
125
|
-
entry,
|
|
128
|
+
entry: tuned,
|
|
126
129
|
id: entry.id,
|
|
127
130
|
provider: parsed.provider,
|
|
128
131
|
apiId: parsed.apiId,
|
|
@@ -132,10 +135,18 @@ function build(entry) {
|
|
|
132
135
|
promptField,
|
|
133
136
|
promptRequired: promptTop !== undefined && promptField === promptTop && (input.required ?? []).includes(promptTop),
|
|
134
137
|
slots,
|
|
135
|
-
options: resolveOptions(
|
|
138
|
+
options: resolveOptions(tuned, input, excluded, wire.fixed),
|
|
136
139
|
modes: entry.modes ? [...entry.modes] : defaultModes(slots),
|
|
137
140
|
};
|
|
138
141
|
}
|
|
142
|
+
export function resolveEntry(entry) {
|
|
143
|
+
const cached = cache.get(entry.id);
|
|
144
|
+
if (cached)
|
|
145
|
+
return cached;
|
|
146
|
+
const resolved = build(entry);
|
|
147
|
+
cache.set(entry.id, resolved);
|
|
148
|
+
return resolved;
|
|
149
|
+
}
|
|
139
150
|
export function resolveModel(model) {
|
|
140
151
|
const cached = cache.get(model);
|
|
141
152
|
if (cached)
|
|
@@ -143,9 +154,7 @@ export function resolveModel(model) {
|
|
|
143
154
|
const entry = modelEntry(model);
|
|
144
155
|
if (!entry)
|
|
145
156
|
throw new UnknownModelError(model);
|
|
146
|
-
|
|
147
|
-
cache.set(model, resolved);
|
|
148
|
-
return resolved;
|
|
157
|
+
return resolveEntry(entry);
|
|
149
158
|
}
|
|
150
159
|
export function tryResolveModel(model) {
|
|
151
160
|
try {
|
|
@@ -31,7 +31,6 @@ export const FRONTIER_CATALOG_GAPS = {
|
|
|
31
31
|
"fal:bytedance.seedance2p0.imageToVideo": "This exact endpoint is not in the Videobook catalog.",
|
|
32
32
|
"fal:blackforestlabs.flux3.imageToVideo": "This exact endpoint is not in the Videobook catalog.",
|
|
33
33
|
"fal:xai.grokImagineVideo.v1p5.imageToVideo": "This exact endpoint is not in the Videobook catalog.",
|
|
34
|
-
"kie:grok-imagine-video-1-5-preview": "This exact endpoint is not in the Videobook catalog.",
|
|
35
34
|
"xai:v1.videos.generations.imageToVideo": "The catalog uses grok-imagine-video, not the ranked 1.5 model id.",
|
|
36
35
|
"fal:alibaba.happyHorse.v1p1.imageToVideo": "This exact endpoint is not in the Videobook catalog.",
|
|
37
36
|
"fal:bytedance.seedance2p5.referenceToVideo": "This exact endpoint is not in the Videobook catalog.",
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { isKieVeoModel } from "./wire.js";
|
|
2
|
+
export function generationHarEndpoint(model) {
|
|
3
|
+
switch (model.provider) {
|
|
4
|
+
case "kie":
|
|
5
|
+
if (isKieVeoModel(model.apiId))
|
|
6
|
+
return "api.v1.veo.generate";
|
|
7
|
+
if (model.apiId === "suno/generate")
|
|
8
|
+
return "api.v1.generate";
|
|
9
|
+
if (model.apiId === "suno/sounds-generate")
|
|
10
|
+
return "api.v1.generate.sounds";
|
|
11
|
+
return "api.v1.jobs.createTask";
|
|
12
|
+
case "fal": return "v1.queue.submit";
|
|
13
|
+
case "xai": return "v1.videos.generations";
|
|
14
|
+
case "alibaba": return "api.v1.services.aigc.multimodalGeneration.generation";
|
|
15
|
+
case "machgen": return "api.v0.generate";
|
|
16
|
+
case "elevenlabs":
|
|
17
|
+
switch (model.apiId) {
|
|
18
|
+
case "sound-generation": return "v1.soundGeneration";
|
|
19
|
+
case "music": return "v1.music";
|
|
20
|
+
case "video-to-music": return "v1.music.videoToMusic";
|
|
21
|
+
case "text-to-speech": return "v1.textToSpeech";
|
|
22
|
+
default: throw new Error(`Unsupported ElevenLabs model ${model.apiId}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -43,6 +43,9 @@ export function isKeyframeItem(schema) {
|
|
|
43
43
|
&& branch.properties?.image_url !== undefined
|
|
44
44
|
&& branch.properties?.frame_index !== undefined);
|
|
45
45
|
}
|
|
46
|
+
export function acceptsNoValue(schema) {
|
|
47
|
+
return schema.not !== undefined && Object.keys(schema.not).length === 0;
|
|
48
|
+
}
|
|
46
49
|
export function isArraySchema(schema) {
|
|
47
50
|
return branches(schema).some((branch) => branch.type === "array");
|
|
48
51
|
}
|
package/dist/media/listing.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { modelFrontierRankings } from "./frontier.js";
|
|
2
2
|
import { PROVIDER_ENV_KEYS } from "./adapters/index.js";
|
|
3
3
|
import { MODEL_ENTRIES } from "./catalog.js";
|
|
4
|
-
import {
|
|
4
|
+
import { resolveEntry } from "./descriptor.js";
|
|
5
|
+
import { registerDiagnosticName } from "../diagnostics.js";
|
|
6
|
+
import { logger } from "../logger.js";
|
|
5
7
|
export function modelIsHidden(entry) {
|
|
6
8
|
return entry.hidden === true && modelFrontierRankings(entry.id).length === 0;
|
|
7
9
|
}
|
|
@@ -22,10 +24,30 @@ export function modelListing(model) {
|
|
|
22
24
|
frontier: modelFrontierRankings(model.id),
|
|
23
25
|
};
|
|
24
26
|
}
|
|
27
|
+
for (const entry of MODEL_ENTRIES)
|
|
28
|
+
registerDiagnosticName("model", entry.id);
|
|
29
|
+
const unresolvableReported = new Set();
|
|
30
|
+
function reportUnresolvable(entry, error) {
|
|
31
|
+
if (unresolvableReported.has(entry.id))
|
|
32
|
+
return;
|
|
33
|
+
unresolvableReported.add(entry.id);
|
|
34
|
+
logger.warn("model-unresolvable", { model: entry.id, error });
|
|
35
|
+
}
|
|
36
|
+
function resolveOrSkip(entry) {
|
|
37
|
+
try {
|
|
38
|
+
return [resolveEntry(entry)];
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
reportUnresolvable(entry, error);
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export function resolvableModels(entries) {
|
|
46
|
+
return entries.flatMap(resolveOrSkip);
|
|
47
|
+
}
|
|
25
48
|
export function listModels(filter = {}) {
|
|
26
|
-
return MODEL_ENTRIES
|
|
49
|
+
return resolvableModels(MODEL_ENTRIES
|
|
27
50
|
.filter((entry) => filter.includeHidden || !modelIsHidden(entry))
|
|
28
51
|
.filter((entry) => !filter.media || entry.media === filter.media)
|
|
29
|
-
.filter((entry) => !filter.operation || entry.operations.includes(filter.operation))
|
|
30
|
-
.map((entry) => resolveModel(entry.id));
|
|
52
|
+
.filter((entry) => !filter.operation || entry.operations.includes(filter.operation)));
|
|
31
53
|
}
|