@koda-sl/baker-cli 0.393.0-dev.799d3f88c → 0.395.0-dev.f8ecbaa42
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 +33 -1
- package/canvas/static-ad-composition/index.html +154 -3
- package/canvas/static-ad-composition/meta.json +1 -1
- package/dist/{chunk-JLKUHDBB.js → chunk-Q3LMLQAI.js} +271 -100
- package/dist/chunk-Q3LMLQAI.js.map +1 -0
- package/dist/cli.js +8512 -7419
- package/dist/cli.js.map +1 -1
- package/dist/engine/index.d.ts +6 -0
- package/dist/engine/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-JLKUHDBB.js.map +0 -1
|
@@ -1459,7 +1459,9 @@ var MODEL_REGISTRY = {
|
|
|
1459
1459
|
// Vision judgement — the selection contract (numbered images, JSON
|
|
1460
1460
|
// response) is baked into the backend prompt; `prompt` carries only the
|
|
1461
1461
|
// selection criteria. `count` has an engine-side default of 1, so it is
|
|
1462
|
-
// not registry-required.
|
|
1462
|
+
// not registry-required. `keep_reserved_areas` is read by the Runtime, which
|
|
1463
|
+
// drops a candidate that filled the copy's areas before the judge, and is
|
|
1464
|
+
// never sent on to the backend.
|
|
1463
1465
|
"~google/gemini-flash-latest": {
|
|
1464
1466
|
label: "Google Gemini Flash (latest, Vision)",
|
|
1465
1467
|
inputs: [{ kind: "image", mimes: OPENROUTER_IMAGE_MIMES }],
|
|
@@ -1468,7 +1470,8 @@ var MODEL_REGISTRY = {
|
|
|
1468
1470
|
prompt: { kind: "string" },
|
|
1469
1471
|
count: { kind: "number", min: 1 },
|
|
1470
1472
|
temperature: { kind: "number", min: 0, max: 2 },
|
|
1471
|
-
max_tokens: { kind: "number", min: 1 }
|
|
1473
|
+
max_tokens: { kind: "number", min: 1 },
|
|
1474
|
+
keep_reserved_areas: { kind: "boolean" }
|
|
1472
1475
|
}
|
|
1473
1476
|
},
|
|
1474
1477
|
"~google/gemini-pro-latest": {
|
|
@@ -1479,7 +1482,8 @@ var MODEL_REGISTRY = {
|
|
|
1479
1482
|
prompt: { kind: "string" },
|
|
1480
1483
|
count: { kind: "number", min: 1 },
|
|
1481
1484
|
temperature: { kind: "number", min: 0, max: 2 },
|
|
1482
|
-
max_tokens: { kind: "number", min: 1 }
|
|
1485
|
+
max_tokens: { kind: "number", min: 1 },
|
|
1486
|
+
keep_reserved_areas: { kind: "boolean" }
|
|
1483
1487
|
}
|
|
1484
1488
|
}
|
|
1485
1489
|
},
|
|
@@ -2345,8 +2349,8 @@ function promptMaxLength(kind, model) {
|
|
|
2345
2349
|
return schema?.kind === "string" ? schema.maxLength : void 0;
|
|
2346
2350
|
}
|
|
2347
2351
|
function inputSlot(kind, model, inputKind) {
|
|
2348
|
-
const
|
|
2349
|
-
return
|
|
2352
|
+
const spec2 = MODEL_REGISTRY[kind]?.[model];
|
|
2353
|
+
return spec2?.optional_inputs?.find((input) => input.kind === inputKind) ?? spec2?.inputs.find((input) => input.kind === inputKind);
|
|
2350
2354
|
}
|
|
2351
2355
|
function maxInputSlot(kind, model, inputKind) {
|
|
2352
2356
|
return inputSlot(kind, model, inputKind)?.max;
|
|
@@ -2415,9 +2419,9 @@ function nearestSupportedImageSize(kind, model, target) {
|
|
|
2415
2419
|
function isManagedNodeKind(nodeType) {
|
|
2416
2420
|
return Object.keys(MODEL_REGISTRY).includes(nodeType);
|
|
2417
2421
|
}
|
|
2418
|
-
function checkRequiredParams(
|
|
2422
|
+
function checkRequiredParams(spec2, params, model) {
|
|
2419
2423
|
const errors = [];
|
|
2420
|
-
for (const req of
|
|
2424
|
+
for (const req of spec2.required) {
|
|
2421
2425
|
if (params[req] === void 0 || params[req] === null) {
|
|
2422
2426
|
errors.push({
|
|
2423
2427
|
code: "missing_required_param",
|
|
@@ -2428,12 +2432,12 @@ function checkRequiredParams(spec, params, model) {
|
|
|
2428
2432
|
}
|
|
2429
2433
|
return errors;
|
|
2430
2434
|
}
|
|
2431
|
-
function checkParamValues(
|
|
2435
|
+
function checkParamValues(spec2, params, model) {
|
|
2432
2436
|
const errors = [];
|
|
2433
2437
|
for (const [key, value] of Object.entries(params)) {
|
|
2434
2438
|
if (key === "model") continue;
|
|
2435
2439
|
if (value === void 0) continue;
|
|
2436
|
-
const schema =
|
|
2440
|
+
const schema = spec2.params[key];
|
|
2437
2441
|
if (!schema) {
|
|
2438
2442
|
errors.push({
|
|
2439
2443
|
code: "unknown_param",
|
|
@@ -2448,14 +2452,14 @@ function checkParamValues(spec, params, model) {
|
|
|
2448
2452
|
return errors;
|
|
2449
2453
|
}
|
|
2450
2454
|
function validateModelParams(nodeType, model, params) {
|
|
2451
|
-
const
|
|
2452
|
-
if (!
|
|
2455
|
+
const spec2 = MODEL_REGISTRY[nodeType][model];
|
|
2456
|
+
if (!spec2) {
|
|
2453
2457
|
return {
|
|
2454
2458
|
ok: false,
|
|
2455
2459
|
errors: [{ code: "unknown_model", message: `unknown model "${model}" for node "${nodeType}"` }]
|
|
2456
2460
|
};
|
|
2457
2461
|
}
|
|
2458
|
-
const errors = [...checkRequiredParams(
|
|
2462
|
+
const errors = [...checkRequiredParams(spec2, params, model), ...checkParamValues(spec2, params, model)];
|
|
2459
2463
|
if (errors.length > 0) return { ok: false, errors };
|
|
2460
2464
|
if (nodeType === "video_generate") {
|
|
2461
2465
|
const tooHeavy = clipTooHeavyToIngest(
|
|
@@ -2467,8 +2471,8 @@ function validateModelParams(nodeType, model, params) {
|
|
|
2467
2471
|
return { ok: true };
|
|
2468
2472
|
}
|
|
2469
2473
|
function validateModelInputs(nodeType, model, providedKinds) {
|
|
2470
|
-
const
|
|
2471
|
-
if (!
|
|
2474
|
+
const spec2 = MODEL_REGISTRY[nodeType][model];
|
|
2475
|
+
if (!spec2) {
|
|
2472
2476
|
return {
|
|
2473
2477
|
ok: false,
|
|
2474
2478
|
errors: [{ code: "unknown_model", message: `unknown model "${model}" for node "${nodeType}"` }]
|
|
@@ -2476,7 +2480,7 @@ function validateModelInputs(nodeType, model, providedKinds) {
|
|
|
2476
2480
|
}
|
|
2477
2481
|
const have = new Set(providedKinds);
|
|
2478
2482
|
const errors = [];
|
|
2479
|
-
for (const inputSpec of
|
|
2483
|
+
for (const inputSpec of spec2.inputs) {
|
|
2480
2484
|
if (!have.has(inputSpec.kind)) {
|
|
2481
2485
|
errors.push({
|
|
2482
2486
|
code: "missing_input",
|
|
@@ -3686,9 +3690,9 @@ function parseSpineInputLens(args) {
|
|
|
3686
3690
|
}
|
|
3687
3691
|
|
|
3688
3692
|
// src/engine/engine/define.ts
|
|
3689
|
-
function resolveOutputKinds(
|
|
3690
|
-
if (!
|
|
3691
|
-
return typeof
|
|
3693
|
+
function resolveOutputKinds(spec2, params) {
|
|
3694
|
+
if (!spec2) return {};
|
|
3695
|
+
return typeof spec2 === "function" ? spec2(params) : spec2;
|
|
3692
3696
|
}
|
|
3693
3697
|
function defineNode(def) {
|
|
3694
3698
|
return def;
|
|
@@ -3766,9 +3770,9 @@ function cappedParamsOf(n) {
|
|
|
3766
3770
|
if (!isManagedNodeKind(n.type)) return [];
|
|
3767
3771
|
const model = n.params?.model;
|
|
3768
3772
|
if (typeof model !== "string") return [];
|
|
3769
|
-
const
|
|
3770
|
-
if (!
|
|
3771
|
-
return Object.entries(
|
|
3773
|
+
const spec2 = MODEL_REGISTRY[n.type][model];
|
|
3774
|
+
if (!spec2) return [];
|
|
3775
|
+
return Object.entries(spec2.params).flatMap(
|
|
3772
3776
|
([key, schema]) => schema.kind === "string" && schema.maxLength !== void 0 ? [{ key, max: schema.maxLength, model }] : []
|
|
3773
3777
|
);
|
|
3774
3778
|
}
|
|
@@ -4711,8 +4715,8 @@ function checkReferenceCompleteness(ctx, meta) {
|
|
|
4711
4715
|
}
|
|
4712
4716
|
}
|
|
4713
4717
|
function videoModelMaxDuration(model) {
|
|
4714
|
-
const
|
|
4715
|
-
const durations =
|
|
4718
|
+
const spec2 = MODEL_REGISTRY.video_generate[model];
|
|
4719
|
+
const durations = spec2?.params?.duration?.enum;
|
|
4716
4720
|
if (!durations) return void 0;
|
|
4717
4721
|
const nums = durations.filter((d) => typeof d === "number");
|
|
4718
4722
|
return nums.length > 0 ? Math.max(...nums) : void 0;
|
|
@@ -5361,6 +5365,7 @@ var Engine = class {
|
|
|
5361
5365
|
}
|
|
5362
5366
|
}
|
|
5363
5367
|
const t0 = Date.now();
|
|
5368
|
+
let charged;
|
|
5364
5369
|
const ctx = {
|
|
5365
5370
|
canvasRunId: runId,
|
|
5366
5371
|
nodeId: node.id,
|
|
@@ -5370,13 +5375,16 @@ var Engine = class {
|
|
|
5370
5375
|
client: this.client,
|
|
5371
5376
|
assets: this.assets,
|
|
5372
5377
|
log: this.log,
|
|
5373
|
-
signal: opts.signal
|
|
5378
|
+
signal: opts.signal,
|
|
5379
|
+
reportCharge: (credits2) => {
|
|
5380
|
+
charged = (charged ?? 0) + credits2;
|
|
5381
|
+
}
|
|
5374
5382
|
};
|
|
5375
5383
|
const preparedForExec = needsLocalMaterialization(def) ? { ...prepared, resolvedInputs: await this.materializeLocalInputs(prepared.resolvedInputs) } : prepared;
|
|
5376
5384
|
const { parsedInputs, parsedParams } = parseNodeArgs(def, preparedForExec, node.id, node.type);
|
|
5377
5385
|
const result = await invokeExecute(def, parsedInputs, parsedParams, ctx, node.id, node.type);
|
|
5378
5386
|
const elapsed = Date.now() - t0;
|
|
5379
|
-
const credits =
|
|
5387
|
+
const credits = charged ?? estimatedNodeCredits(def, parsedParams);
|
|
5380
5388
|
const outputsObj = result;
|
|
5381
5389
|
outputs[node.id] = outputsObj;
|
|
5382
5390
|
if (this.persistAssets) {
|
|
@@ -5514,6 +5522,9 @@ async function invokeExecute(def, parsedInputs, parsedParams, ctx, nodeId, nodeT
|
|
|
5514
5522
|
throw new NodeExecutionError(nodeId, nodeType, { kind: "local", cause: e });
|
|
5515
5523
|
}
|
|
5516
5524
|
}
|
|
5525
|
+
function estimatedNodeCredits(def, params) {
|
|
5526
|
+
return def.cost ? def.cost({ params }).credits : 0;
|
|
5527
|
+
}
|
|
5517
5528
|
function needsLocalMaterialization(def) {
|
|
5518
5529
|
return def.location === "local" && !def.passthroughRefs;
|
|
5519
5530
|
}
|
|
@@ -6366,17 +6377,17 @@ var FontRef = BaseAssetRef.extend({
|
|
|
6366
6377
|
var AssetRef = z4.discriminatedUnion("kind", [ImageRef, VideoRef, AudioRef, JsonRef, TextRef, FontRef]);
|
|
6367
6378
|
|
|
6368
6379
|
// src/engine/nodes/remote/delegate.ts
|
|
6369
|
-
function delegated(
|
|
6380
|
+
function delegated(spec2) {
|
|
6370
6381
|
return {
|
|
6371
|
-
...
|
|
6382
|
+
...spec2,
|
|
6372
6383
|
location: "remote",
|
|
6373
6384
|
execute: async ({ inputs, params, ctx }) => {
|
|
6374
6385
|
return await callBackendExec({
|
|
6375
|
-
nodeType:
|
|
6376
|
-
nodeVersion:
|
|
6386
|
+
nodeType: spec2.id,
|
|
6387
|
+
nodeVersion: spec2.version,
|
|
6377
6388
|
params,
|
|
6378
6389
|
inputs,
|
|
6379
|
-
outputKinds: resolveOutputKinds(
|
|
6390
|
+
outputKinds: resolveOutputKinds(spec2.outputKinds, params),
|
|
6380
6391
|
ctx
|
|
6381
6392
|
});
|
|
6382
6393
|
}
|
|
@@ -6398,6 +6409,9 @@ async function callBackendExec(args) {
|
|
|
6398
6409
|
},
|
|
6399
6410
|
args.ctx.signal
|
|
6400
6411
|
);
|
|
6412
|
+
if (typeof res.credits_charged === "number" && Number.isFinite(res.credits_charged)) {
|
|
6413
|
+
args.ctx.reportCharge?.(res.credits_charged);
|
|
6414
|
+
}
|
|
6401
6415
|
return await ingestOutputs(res.outputs, args.ctx, args.outputKinds);
|
|
6402
6416
|
} catch (e) {
|
|
6403
6417
|
throw mapClientError(args.ctx, e);
|
|
@@ -6749,22 +6763,22 @@ function inferMimeFromPath(absPath, sniffBytes) {
|
|
|
6749
6763
|
return "application/octet-stream";
|
|
6750
6764
|
}
|
|
6751
6765
|
async function rasterizeSvgToPng(bytes) {
|
|
6752
|
-
const { default:
|
|
6753
|
-
const base = await
|
|
6766
|
+
const { default: sharp2 } = await import("sharp");
|
|
6767
|
+
const base = await sharp2(bytes).metadata();
|
|
6754
6768
|
const longest = Math.max(base.width ?? 0, base.height ?? 0);
|
|
6755
6769
|
let density = SVG_BASE_DENSITY;
|
|
6756
6770
|
if (longest > 0) {
|
|
6757
6771
|
const scaled = Math.round(SVG_BASE_DENSITY * SVG_TARGET_LONGEST_PX / longest);
|
|
6758
6772
|
density = Math.min(SVG_MAX_DENSITY, Math.max(SVG_BASE_DENSITY, scaled));
|
|
6759
6773
|
}
|
|
6760
|
-
return await
|
|
6774
|
+
return await sharp2(bytes, { density }).png({ force: true, palette: false }).toBuffer();
|
|
6761
6775
|
}
|
|
6762
6776
|
var MODEL_SAFE_IMAGE_MIMES = /* @__PURE__ */ new Set(["image/jpeg", "image/png", "image/gif", "image/webp"]);
|
|
6763
6777
|
var MAX_MODEL_REFERENCE_BYTES = 30 * 1024 * 1024;
|
|
6764
6778
|
async function fitWithinReferenceLimit(bytes, mime, maxBytes) {
|
|
6765
6779
|
if (bytes.byteLength <= maxBytes) return { bytes, mime };
|
|
6766
|
-
const { default:
|
|
6767
|
-
const read = () =>
|
|
6780
|
+
const { default: sharp2 } = await import("sharp");
|
|
6781
|
+
const read = () => sharp2(bytes, { animated: true });
|
|
6768
6782
|
const meta = await read().metadata();
|
|
6769
6783
|
const animated = (meta.pages ?? 1) > 1;
|
|
6770
6784
|
const keepAlpha = meta.hasAlpha === true;
|
|
@@ -6802,11 +6816,11 @@ async function toModelSafeImage(bytes, opts) {
|
|
|
6802
6816
|
if (sniffSvg(bytes)) {
|
|
6803
6817
|
return await fit(await rasterizeSvgToPng(bytes), "image/png", "svg");
|
|
6804
6818
|
}
|
|
6805
|
-
const { default:
|
|
6819
|
+
const { default: sharp2 } = await import("sharp");
|
|
6806
6820
|
let png;
|
|
6807
6821
|
let format;
|
|
6808
6822
|
try {
|
|
6809
|
-
const img =
|
|
6823
|
+
const img = sharp2(bytes);
|
|
6810
6824
|
format = (await img.metadata()).format ?? "unknown";
|
|
6811
6825
|
png = await img.png({ force: true }).toBuffer();
|
|
6812
6826
|
} catch (e) {
|
|
@@ -7167,10 +7181,10 @@ function planSingleInputSlot(tmpDir, slot, ref, lookup, stagedInputs) {
|
|
|
7167
7181
|
}
|
|
7168
7182
|
function planOutputs(tmpDir, outputs, lookup) {
|
|
7169
7183
|
const outputPaths = [];
|
|
7170
|
-
for (const [name,
|
|
7171
|
-
const ext =
|
|
7184
|
+
for (const [name, spec2] of Object.entries(outputs)) {
|
|
7185
|
+
const ext = spec2.ext.startsWith(".") ? spec2.ext : `.${spec2.ext}`;
|
|
7172
7186
|
const absPath = path8.join(tmpDir, `out_${name}${ext}`);
|
|
7173
|
-
outputPaths.push({ name, absPath, spec });
|
|
7187
|
+
outputPaths.push({ name, absPath, spec: spec2 });
|
|
7174
7188
|
lookup.set(`out.${name}`, absPath);
|
|
7175
7189
|
}
|
|
7176
7190
|
return outputPaths;
|
|
@@ -7278,7 +7292,7 @@ async function runCli(opts) {
|
|
|
7278
7292
|
${tailLines(stderr, 40)}`);
|
|
7279
7293
|
}
|
|
7280
7294
|
const result = {};
|
|
7281
|
-
for (const { name, absPath, spec } of outputPaths) {
|
|
7295
|
+
for (const { name, absPath, spec: spec2 } of outputPaths) {
|
|
7282
7296
|
const s = await stat3(absPath).catch(() => null);
|
|
7283
7297
|
if (!s?.isFile() || s.size === 0) {
|
|
7284
7298
|
throw new Error(`cli-runner: declared output "${name}" missing or empty at ${absPath}`);
|
|
@@ -7286,8 +7300,8 @@ ${tailLines(stderr, 40)}`);
|
|
|
7286
7300
|
const bytes = await readFile6(absPath);
|
|
7287
7301
|
const ref = await ctx.assets.ingestBytes({
|
|
7288
7302
|
bytes: Buffer.from(bytes),
|
|
7289
|
-
kind:
|
|
7290
|
-
mime: mimeForExt(
|
|
7303
|
+
kind: spec2.kind,
|
|
7304
|
+
mime: mimeForExt(spec2.ext)
|
|
7291
7305
|
});
|
|
7292
7306
|
result[name] = ref;
|
|
7293
7307
|
}
|
|
@@ -7875,47 +7889,47 @@ async function loadCompositionMeta(compositionDir) {
|
|
|
7875
7889
|
}
|
|
7876
7890
|
function buildParamsSchema(meta) {
|
|
7877
7891
|
const shape = {};
|
|
7878
|
-
for (const [name,
|
|
7879
|
-
shape[name] = buildParamFieldSchema(name,
|
|
7892
|
+
for (const [name, spec2] of Object.entries(meta.params)) {
|
|
7893
|
+
shape[name] = buildParamFieldSchema(name, spec2);
|
|
7880
7894
|
}
|
|
7881
7895
|
return z10.object(shape).strict();
|
|
7882
7896
|
}
|
|
7883
|
-
function buildParamFieldSchema(name,
|
|
7884
|
-
switch (
|
|
7897
|
+
function buildParamFieldSchema(name, spec2) {
|
|
7898
|
+
switch (spec2.kind) {
|
|
7885
7899
|
case "string": {
|
|
7886
|
-
const s =
|
|
7887
|
-
return finalize(s,
|
|
7900
|
+
const s = spec2.enum && spec2.enum.length > 0 ? z10.enum(spec2.enum) : z10.string();
|
|
7901
|
+
return finalize(s, spec2.default, spec2.required);
|
|
7888
7902
|
}
|
|
7889
7903
|
case "integer": {
|
|
7890
7904
|
let s = z10.number().int();
|
|
7891
|
-
if (
|
|
7892
|
-
if (
|
|
7893
|
-
return finalize(s,
|
|
7905
|
+
if (spec2.min !== void 0) s = s.min(spec2.min);
|
|
7906
|
+
if (spec2.max !== void 0) s = s.max(spec2.max);
|
|
7907
|
+
return finalize(s, spec2.default, spec2.required);
|
|
7894
7908
|
}
|
|
7895
7909
|
case "number": {
|
|
7896
7910
|
let s = z10.number();
|
|
7897
|
-
if (
|
|
7898
|
-
if (
|
|
7899
|
-
return finalize(s,
|
|
7911
|
+
if (spec2.min !== void 0) s = s.min(spec2.min);
|
|
7912
|
+
if (spec2.max !== void 0) s = s.max(spec2.max);
|
|
7913
|
+
return finalize(s, spec2.default, spec2.required);
|
|
7900
7914
|
}
|
|
7901
7915
|
case "boolean":
|
|
7902
|
-
return finalize(z10.boolean(),
|
|
7916
|
+
return finalize(z10.boolean(), spec2.default, spec2.required);
|
|
7903
7917
|
case "color": {
|
|
7904
7918
|
const s = z10.string().regex(/^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/, {
|
|
7905
7919
|
message: `param "${name}": must be a 3/6/8-digit hex color (e.g. "#ff0066")`
|
|
7906
7920
|
});
|
|
7907
|
-
return finalize(s,
|
|
7921
|
+
return finalize(s, spec2.default, spec2.required);
|
|
7908
7922
|
}
|
|
7909
7923
|
case "image":
|
|
7910
|
-
return finalize(z10.union([z10.string().min(1), z10.record(z10.string(), z10.unknown())]),
|
|
7924
|
+
return finalize(z10.union([z10.string().min(1), z10.record(z10.string(), z10.unknown())]), spec2.default, spec2.required);
|
|
7911
7925
|
case "json":
|
|
7912
7926
|
return finalize(
|
|
7913
7927
|
z10.unknown().refine((v) => v !== void 0, { message: `param "${name}" is required` }),
|
|
7914
|
-
|
|
7915
|
-
|
|
7928
|
+
spec2.default,
|
|
7929
|
+
spec2.required
|
|
7916
7930
|
);
|
|
7917
7931
|
default: {
|
|
7918
|
-
const _exhaustive =
|
|
7932
|
+
const _exhaustive = spec2;
|
|
7919
7933
|
throw new Error(`buildParamFieldSchema: unknown kind ${_exhaustive.kind}`);
|
|
7920
7934
|
}
|
|
7921
7935
|
}
|
|
@@ -8505,12 +8519,12 @@ async function stageInputs2(tmp, inputs, meta, ctx) {
|
|
|
8505
8519
|
assertInputsMatchMeta(inputs, meta, "hyperframe_render");
|
|
8506
8520
|
let primaryDuration = null;
|
|
8507
8521
|
for (const [key, ref] of Object.entries(inputs ?? {})) {
|
|
8508
|
-
const
|
|
8509
|
-
if (!
|
|
8510
|
-
const filename =
|
|
8522
|
+
const spec2 = meta.inputs[key];
|
|
8523
|
+
if (!spec2) continue;
|
|
8524
|
+
const filename = spec2.staged_as ?? defaultFilenameForInput(key, spec2.kind);
|
|
8511
8525
|
await stageAsset(ref, tmp, filename);
|
|
8512
|
-
ctx.log(`staged ${
|
|
8513
|
-
if (
|
|
8526
|
+
ctx.log(`staged ${spec2.kind} \u2192 ${filename}`);
|
|
8527
|
+
if (spec2.kind === "video" && primaryDuration === null) {
|
|
8514
8528
|
primaryDuration = await probeDurationSeconds(path15.join(tmp, filename));
|
|
8515
8529
|
}
|
|
8516
8530
|
}
|
|
@@ -8525,24 +8539,24 @@ function assertInputsMatchMeta(inputs, meta, nodeId) {
|
|
|
8525
8539
|
throw new Error(`${nodeId}: inputs.${key} is not declared in composition meta.json (declares: ${declared})`);
|
|
8526
8540
|
}
|
|
8527
8541
|
}
|
|
8528
|
-
for (const [key,
|
|
8529
|
-
if (
|
|
8530
|
-
throw new Error(`${nodeId}: inputs.${key} is required (kind=${
|
|
8542
|
+
for (const [key, spec2] of Object.entries(meta.inputs)) {
|
|
8543
|
+
if (spec2.required && !providedKeys.has(key)) {
|
|
8544
|
+
throw new Error(`${nodeId}: inputs.${key} is required (kind=${spec2.kind})`);
|
|
8531
8545
|
}
|
|
8532
8546
|
}
|
|
8533
8547
|
for (const [key, ref] of Object.entries(inputs ?? {})) {
|
|
8534
|
-
const
|
|
8535
|
-
if (!
|
|
8536
|
-
if (ref.kind !==
|
|
8537
|
-
throw new Error(`${nodeId}: inputs.${key} expected kind "${
|
|
8548
|
+
const spec2 = meta.inputs[key];
|
|
8549
|
+
if (!spec2) continue;
|
|
8550
|
+
if (ref.kind !== spec2.kind) {
|
|
8551
|
+
throw new Error(`${nodeId}: inputs.${key} expected kind "${spec2.kind}", got "${ref.kind}"`);
|
|
8538
8552
|
}
|
|
8539
8553
|
}
|
|
8540
8554
|
}
|
|
8541
8555
|
async function buildSubstitutionValues(compositionParams, meta, duration) {
|
|
8542
8556
|
const out = {};
|
|
8543
8557
|
for (const [name, value] of Object.entries(compositionParams)) {
|
|
8544
|
-
const
|
|
8545
|
-
if (
|
|
8558
|
+
const spec2 = meta.params[name];
|
|
8559
|
+
if (spec2?.kind === "image") {
|
|
8546
8560
|
out[name] = await coerceImageParam(value);
|
|
8547
8561
|
continue;
|
|
8548
8562
|
}
|
|
@@ -8763,18 +8777,18 @@ async function vendorGsap2(tmp, ctx) {
|
|
|
8763
8777
|
async function stageInputs3(tmp, inputs, meta, ctx) {
|
|
8764
8778
|
assertInputsMatchMeta(inputs, meta, "hyperframe_snapshot");
|
|
8765
8779
|
for (const [key, ref] of Object.entries(inputs ?? {})) {
|
|
8766
|
-
const
|
|
8767
|
-
if (!
|
|
8768
|
-
const filename =
|
|
8780
|
+
const spec2 = meta.inputs[key];
|
|
8781
|
+
if (!spec2) continue;
|
|
8782
|
+
const filename = spec2.staged_as ?? defaultFilenameForInput(key, spec2.kind);
|
|
8769
8783
|
await stageAsset(ref, tmp, filename);
|
|
8770
|
-
ctx.log(`staged ${
|
|
8784
|
+
ctx.log(`staged ${spec2.kind} \u2192 ${filename}`);
|
|
8771
8785
|
}
|
|
8772
8786
|
}
|
|
8773
8787
|
async function buildSubstitutionValues2(compositionParams, meta) {
|
|
8774
8788
|
const out = {};
|
|
8775
8789
|
for (const [name, value] of Object.entries(compositionParams)) {
|
|
8776
|
-
const
|
|
8777
|
-
if (
|
|
8790
|
+
const spec2 = meta.params[name];
|
|
8791
|
+
if (spec2?.kind === "image") {
|
|
8778
8792
|
out[name] = await coerceImageParam2(value);
|
|
8779
8793
|
continue;
|
|
8780
8794
|
}
|
|
@@ -9007,7 +9021,12 @@ var imageGenerateNode = delegated({
|
|
|
9007
9021
|
params: ImageGenerateParams,
|
|
9008
9022
|
outputs: z17.object({ images: z17.array(ImageRef).min(1) }).strict(),
|
|
9009
9023
|
outputKinds: { images: "image" },
|
|
9010
|
-
|
|
9024
|
+
// An estimate for the pre-flight cap only: the run reports what the backend billed. A
|
|
9025
|
+
// top-quality render measured 8-12 credits on gpt-image-2.5 (2026-09), so it is priced 10.
|
|
9026
|
+
cost: ({ params }) => ({
|
|
9027
|
+
credits: params.quality === "xhigh" || params.quality === "max" ? 10 : 5,
|
|
9028
|
+
seconds_estimate: 10
|
|
9029
|
+
})
|
|
9011
9030
|
});
|
|
9012
9031
|
|
|
9013
9032
|
// src/engine/nodes/remote/imageAspectAdapt.ts
|
|
@@ -9191,24 +9210,103 @@ var imageSearchNode = delegated({
|
|
|
9191
9210
|
});
|
|
9192
9211
|
|
|
9193
9212
|
// src/engine/nodes/remote/imageSelect.ts
|
|
9213
|
+
import { readFile as readFile13 } from "fs/promises";
|
|
9194
9214
|
import { z as z24 } from "zod";
|
|
9215
|
+
|
|
9216
|
+
// src/engine/nodes/local/lib/plate-space.ts
|
|
9217
|
+
import sharp from "sharp";
|
|
9218
|
+
var CELL = 6;
|
|
9219
|
+
var SAMPLE_WIDTH = 384;
|
|
9220
|
+
var CELL_EDGE = 0.02;
|
|
9221
|
+
var BUSY_LIMIT = 0.08;
|
|
9222
|
+
function clampSpan([a, b]) {
|
|
9223
|
+
const lo = Math.max(0, Math.min(100, Math.min(a, b)));
|
|
9224
|
+
const hi = Math.max(0, Math.min(100, Math.max(a, b)));
|
|
9225
|
+
return [lo, hi];
|
|
9226
|
+
}
|
|
9227
|
+
function cellGradient(pixels, width, height, cx, cy) {
|
|
9228
|
+
let sum = 0;
|
|
9229
|
+
let n = 0;
|
|
9230
|
+
for (let y = cy; y < cy + CELL && y + 1 < height; y++) {
|
|
9231
|
+
for (let x = cx; x < cx + CELL && x + 1 < width; x++) {
|
|
9232
|
+
const at = pixels[y * width + x] ?? 0;
|
|
9233
|
+
sum += Math.abs((pixels[y * width + x + 1] ?? at) - at) + Math.abs((pixels[(y + 1) * width + x] ?? at) - at);
|
|
9234
|
+
n += 2;
|
|
9235
|
+
}
|
|
9236
|
+
}
|
|
9237
|
+
return n === 0 ? 0 : sum / n / 255;
|
|
9238
|
+
}
|
|
9239
|
+
function areaBusy(pixels, width, height, area) {
|
|
9240
|
+
const [x0, x1] = clampSpan(area.x_pct).map((p) => Math.round(p / 100 * width));
|
|
9241
|
+
const [y0, y1] = clampSpan(area.y_pct).map((p) => Math.round(p / 100 * height));
|
|
9242
|
+
let cells = 0;
|
|
9243
|
+
let busy = 0;
|
|
9244
|
+
for (let cy = y0; cy + CELL <= y1; cy += CELL) {
|
|
9245
|
+
for (let cx = x0; cx + CELL <= x1; cx += CELL) {
|
|
9246
|
+
cells++;
|
|
9247
|
+
if (cellGradient(pixels, width, height, cx, cy) > CELL_EDGE) busy++;
|
|
9248
|
+
}
|
|
9249
|
+
}
|
|
9250
|
+
return cells === 0 ? 0 : busy / cells;
|
|
9251
|
+
}
|
|
9252
|
+
async function readReservedAreas(image, areas) {
|
|
9253
|
+
if (areas.length === 0) return [];
|
|
9254
|
+
const { data, info } = await sharp(image).greyscale().resize({ width: SAMPLE_WIDTH }).raw().toBuffer({ resolveWithObject: true });
|
|
9255
|
+
const pixels = new Uint8Array(data.buffer, data.byteOffset, info.width * info.height);
|
|
9256
|
+
return areas.map((area) => ({
|
|
9257
|
+
for: area.for ?? "copy",
|
|
9258
|
+
busy: Math.round(areaBusy(pixels, info.width, info.height, area) * 1e3) / 1e3
|
|
9259
|
+
}));
|
|
9260
|
+
}
|
|
9261
|
+
var spanOf = (v) => Array.isArray(v) && v.length === 2 && typeof v[0] === "number" && typeof v[1] === "number" ? [v[0], v[1]] : null;
|
|
9262
|
+
function reservedAreasOf(spec2) {
|
|
9263
|
+
const raw = spec2?.reserved_for_overlay;
|
|
9264
|
+
if (!Array.isArray(raw)) return [];
|
|
9265
|
+
return raw.flatMap((entry) => {
|
|
9266
|
+
const e = entry;
|
|
9267
|
+
const x = spanOf(e.x_pct);
|
|
9268
|
+
const y = spanOf(e.y_pct);
|
|
9269
|
+
return x && y ? [{ for: typeof e.for === "string" ? e.for : "copy", x_pct: x, y_pct: y }] : [];
|
|
9270
|
+
});
|
|
9271
|
+
}
|
|
9272
|
+
function spaceVerdict(readings, limit = BUSY_LIMIT) {
|
|
9273
|
+
const worst = readings.map((areas) => Math.max(0, ...areas.map((a) => a.busy)));
|
|
9274
|
+
const calm = worst.flatMap((w, i) => w <= limit ? [i] : []);
|
|
9275
|
+
const calmest = worst.reduce((best, w, i) => w < (worst[best] ?? Number.POSITIVE_INFINITY) ? i : best, 0);
|
|
9276
|
+
const crowded = readings.flatMap((areas, image) => {
|
|
9277
|
+
const over = areas.filter((a) => a.busy > limit);
|
|
9278
|
+
return over.length > 0 ? [{ image, areas: over }] : [];
|
|
9279
|
+
});
|
|
9280
|
+
return { calm, calmest, crowded };
|
|
9281
|
+
}
|
|
9282
|
+
|
|
9283
|
+
// src/engine/nodes/remote/imageSelect.ts
|
|
9195
9284
|
var IMAGE_SELECT_MODELS = ["~google/gemini-flash-latest", "~google/gemini-pro-latest"];
|
|
9196
|
-
var
|
|
9285
|
+
var ImageSelectParams = z24.object({
|
|
9286
|
+
model: z24.enum(IMAGE_SELECT_MODELS),
|
|
9287
|
+
prompt: z24.string().min(1),
|
|
9288
|
+
count: z24.number().int().min(1).default(1),
|
|
9289
|
+
temperature: z24.number().min(0).max(2).optional(),
|
|
9290
|
+
max_tokens: z24.number().int().positive().optional(),
|
|
9291
|
+
/**
|
|
9292
|
+
* Drop, before the judge, every candidate that filled an area the plate spec
|
|
9293
|
+
* (`inputs.plate_spec`, prompt.plate.json) reserves for the copy. Read off the pixels in
|
|
9294
|
+
* the Runtime: no model call. Never sent to the backend.
|
|
9295
|
+
*/
|
|
9296
|
+
keep_reserved_areas: z24.boolean().optional()
|
|
9297
|
+
}).strict();
|
|
9298
|
+
var ImageSelectInputs = z24.object({ images: z24.array(ImageRef).min(2) }).loose();
|
|
9299
|
+
var ImageSelectOutputs = z24.object({ images: z24.array(ImageRef).min(1), reasoning: TextRef, space: JsonRef.optional() }).strict();
|
|
9300
|
+
var spec = {
|
|
9197
9301
|
id: "image_select",
|
|
9198
9302
|
version: "1.0.0",
|
|
9199
9303
|
category: "vision",
|
|
9200
9304
|
summary: "Pick the best `count` images out of 2+ candidates with a vision LLM, judged against a prompt. Outputs a passthrough subset of the input refs (no new pixels) plus the model's comparative reasoning.",
|
|
9201
|
-
when_to_use: "Use after fanning out several image_generate variants (or any pool of 2+ images) to keep only the strongest before expensive downstream steps \u2014 video generation, reference sheets, final delivery. `count` fixes the output size, so `images#0`\u2026`images#count-1` are always safe to wire. Pick `~google/gemini-flash-latest` for cheap/fast picks and `~google/gemini-pro-latest` for harder aesthetic judgement.",
|
|
9202
|
-
inputs:
|
|
9203
|
-
params:
|
|
9204
|
-
|
|
9205
|
-
|
|
9206
|
-
count: z24.number().int().min(1).default(1),
|
|
9207
|
-
temperature: z24.number().min(0).max(2).optional(),
|
|
9208
|
-
max_tokens: z24.number().int().positive().optional()
|
|
9209
|
-
}).strict(),
|
|
9210
|
-
outputs: z24.object({ images: z24.array(ImageRef).min(1), reasoning: TextRef }).strict(),
|
|
9211
|
-
outputKinds: { images: "image", reasoning: "text" },
|
|
9305
|
+
when_to_use: "Use after fanning out several image_generate variants (or any pool of 2+ images) to keep only the strongest before expensive downstream steps \u2014 video generation, reference sheets, final delivery. `count` fixes the output size, so `images#0`\u2026`images#count-1` are always safe to wire. Pick `~google/gemini-flash-latest` for cheap/fast picks and `~google/gemini-pro-latest` for harder aesthetic judgement. With `keep_reserved_areas` and a `plate_spec` input, a candidate that put its subject where the copy goes is dropped before the judge (deterministic, free).",
|
|
9306
|
+
inputs: ImageSelectInputs,
|
|
9307
|
+
params: ImageSelectParams,
|
|
9308
|
+
outputs: ImageSelectOutputs,
|
|
9309
|
+
outputKinds: { images: "image", reasoning: "text", space: "json" },
|
|
9212
9310
|
cost: () => ({ credits: 1, seconds_estimate: 5 }),
|
|
9213
9311
|
// Arity is only knowable at validate time when `images` is a literal array
|
|
9214
9312
|
// of refs; a single `$ref:` string to an upstream array output is deferred
|
|
@@ -9229,7 +9327,76 @@ var imageSelectNode = delegated({
|
|
|
9229
9327
|
}
|
|
9230
9328
|
return issues;
|
|
9231
9329
|
}
|
|
9232
|
-
}
|
|
9330
|
+
};
|
|
9331
|
+
async function refBytes(ref, ctx) {
|
|
9332
|
+
if (ref.path) return readFile13(ref.path);
|
|
9333
|
+
if (!ref.url) throw new Error("image_select: an input has neither a local path nor a url");
|
|
9334
|
+
const local = await ctx.assets.ingestRemote({
|
|
9335
|
+
kind: ref.kind,
|
|
9336
|
+
url: ref.url,
|
|
9337
|
+
sha256: ref.sha256,
|
|
9338
|
+
mime: ref.mime
|
|
9339
|
+
});
|
|
9340
|
+
return readFile13(local.path ?? "");
|
|
9341
|
+
}
|
|
9342
|
+
async function localText(ctx, body) {
|
|
9343
|
+
const ref = await ctx.assets.ingestBytes({ bytes: Buffer.from(body, "utf8"), kind: "text", mime: "text/plain" });
|
|
9344
|
+
return TextRef.parse(ref);
|
|
9345
|
+
}
|
|
9346
|
+
async function localJson(ctx, value) {
|
|
9347
|
+
const bytes = Buffer.from(JSON.stringify(value), "utf8");
|
|
9348
|
+
return JsonRef.parse(await ctx.assets.ingestBytes({ bytes, kind: "json", mime: "application/json" }));
|
|
9349
|
+
}
|
|
9350
|
+
function judge(inputs, params, ctx) {
|
|
9351
|
+
return callBackendExec({
|
|
9352
|
+
nodeType: spec.id,
|
|
9353
|
+
nodeVersion: spec.version,
|
|
9354
|
+
params,
|
|
9355
|
+
inputs,
|
|
9356
|
+
outputKinds: { images: "image", reasoning: "text" },
|
|
9357
|
+
ctx
|
|
9358
|
+
});
|
|
9359
|
+
}
|
|
9360
|
+
async function selectKeepingSpace(inputs, params, ctx) {
|
|
9361
|
+
const { keep_reserved_areas: _keep, ...backendParams } = params;
|
|
9362
|
+
const specRef = inputs.plate_spec;
|
|
9363
|
+
const parsedSpec = JsonRef.safeParse(specRef);
|
|
9364
|
+
const areas = parsedSpec.success ? reservedAreasOf(JSON.parse((await refBytes(parsedSpec.data, ctx)).toString("utf8"))) : [];
|
|
9365
|
+
if (areas.length === 0) return judge(inputs, backendParams, ctx);
|
|
9366
|
+
const readings = await Promise.all(
|
|
9367
|
+
inputs.images.map(async (image) => readReservedAreas(await refBytes(image, ctx), areas))
|
|
9368
|
+
);
|
|
9369
|
+
const verdict = spaceVerdict(readings);
|
|
9370
|
+
const kept = verdict.calm.length > 0 ? verdict.calm : [verdict.calmest];
|
|
9371
|
+
for (const { image, areas: over } of verdict.crowded) {
|
|
9372
|
+
ctx.log(
|
|
9373
|
+
`[space ] ${ctx.nodeId}: picture ${image + 1} put its subject where the ${over.map((a) => a.for).join(", ")} goes${verdict.calm.length > 0 ? " \u2014 dropped before the judge" : ""}`
|
|
9374
|
+
);
|
|
9375
|
+
}
|
|
9376
|
+
const report = {
|
|
9377
|
+
none_calm: verdict.calm.length === 0,
|
|
9378
|
+
kept: kept.map((i) => i + 1),
|
|
9379
|
+
dropped: verdict.crowded.map((c) => c.image + 1).filter((n) => !kept.includes(n - 1)),
|
|
9380
|
+
crowded: verdict.crowded.map((c) => ({ picture: c.image + 1, areas: c.areas }))
|
|
9381
|
+
};
|
|
9382
|
+
const space = await localJson(ctx, report);
|
|
9383
|
+
if (kept.length < Math.max(2, params.count)) {
|
|
9384
|
+
const chosen = kept.map((i) => inputs.images[i]).filter((i) => i !== void 0);
|
|
9385
|
+
const why = verdict.calm.length === 0 ? `No picture kept the room the copy needs; picture ${(kept[0] ?? 0) + 1} crowds it least.` : `Only picture ${(kept[0] ?? 0) + 1} kept the room the copy needs; the others were dropped before judging.`;
|
|
9386
|
+
return { images: chosen, reasoning: await localText(ctx, why), space };
|
|
9387
|
+
}
|
|
9388
|
+
const subset = { ...inputs, images: kept.map((i) => inputs.images[i]).filter((i) => i !== void 0) };
|
|
9389
|
+
return { ...await judge(subset, backendParams, ctx), space };
|
|
9390
|
+
}
|
|
9391
|
+
var remote = delegated(spec);
|
|
9392
|
+
var imageSelectNode = {
|
|
9393
|
+
...remote,
|
|
9394
|
+
execute: ({ inputs, params, ctx }) => {
|
|
9395
|
+
if (params.keep_reserved_areas) return selectKeepingSpace(inputs, params, ctx);
|
|
9396
|
+
const { keep_reserved_areas: _keep, ...backendParams } = params;
|
|
9397
|
+
return judge(inputs, backendParams, ctx);
|
|
9398
|
+
}
|
|
9399
|
+
};
|
|
9233
9400
|
|
|
9234
9401
|
// src/engine/nodes/remote/music.ts
|
|
9235
9402
|
import { z as z25 } from "zod";
|
|
@@ -9575,7 +9742,7 @@ var videoLipsyncNode = delegated({
|
|
|
9575
9742
|
});
|
|
9576
9743
|
|
|
9577
9744
|
// src/engine/nodes/remote/videoTranscribe.ts
|
|
9578
|
-
import { mkdtemp as mkdtemp6, readFile as
|
|
9745
|
+
import { mkdtemp as mkdtemp6, readFile as readFile14, rm as rm6 } from "fs/promises";
|
|
9579
9746
|
import { tmpdir as tmpdir6 } from "os";
|
|
9580
9747
|
import path17 from "path";
|
|
9581
9748
|
import { z as z33 } from "zod";
|
|
@@ -9722,7 +9889,7 @@ async function tryExtractAudio(inputs, ctx) {
|
|
|
9722
9889
|
["-i", video.path, "-vn", "-ac", "1", "-ar", "16000", "-b:a", "64k", "-f", "mp3", "-y", audioPath],
|
|
9723
9890
|
{ timeout_ms: AUDIO_EXTRACT_TIMEOUT_MS }
|
|
9724
9891
|
);
|
|
9725
|
-
const bytes = await
|
|
9892
|
+
const bytes = await readFile14(audioPath);
|
|
9726
9893
|
if (bytes.byteLength === 0) {
|
|
9727
9894
|
ctx.log("video_transcribe: extracted audio is empty, sending full video");
|
|
9728
9895
|
return null;
|
|
@@ -9917,7 +10084,7 @@ function safeCost(def) {
|
|
|
9917
10084
|
|
|
9918
10085
|
// src/engine/storage/cache-store.ts
|
|
9919
10086
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
9920
|
-
import { mkdir as mkdir3, readFile as
|
|
10087
|
+
import { mkdir as mkdir3, readFile as readFile15, rename as rename2, writeFile as writeFile8 } from "fs/promises";
|
|
9921
10088
|
import path18 from "path";
|
|
9922
10089
|
var LocalCacheStore = class {
|
|
9923
10090
|
rootDir;
|
|
@@ -9929,7 +10096,7 @@ var LocalCacheStore = class {
|
|
|
9929
10096
|
}
|
|
9930
10097
|
async get(cacheKey) {
|
|
9931
10098
|
try {
|
|
9932
|
-
const buf = await
|
|
10099
|
+
const buf = await readFile15(this.filePath(cacheKey), "utf8");
|
|
9933
10100
|
return JSON.parse(buf);
|
|
9934
10101
|
} catch (e) {
|
|
9935
10102
|
if (e.code === "ENOENT") return null;
|
|
@@ -10054,6 +10221,7 @@ export {
|
|
|
10054
10221
|
bestSupportedQuality,
|
|
10055
10222
|
nearestSupportedAspectRatio,
|
|
10056
10223
|
nearestSupportedImageSize,
|
|
10224
|
+
CREDITS_PER_USD,
|
|
10057
10225
|
estimateVideoCredits,
|
|
10058
10226
|
resolveConcurrency,
|
|
10059
10227
|
ulid,
|
|
@@ -10084,6 +10252,9 @@ export {
|
|
|
10084
10252
|
coercePathUrlToUrl,
|
|
10085
10253
|
toModelSafeImage,
|
|
10086
10254
|
loadCompositionMeta,
|
|
10255
|
+
readReservedAreas,
|
|
10256
|
+
reservedAreasOf,
|
|
10257
|
+
spaceVerdict,
|
|
10087
10258
|
catalogView,
|
|
10088
10259
|
BackendClient2,
|
|
10089
10260
|
Engine2 as Engine,
|
|
@@ -10095,4 +10266,4 @@ export {
|
|
|
10095
10266
|
defaultRegistry,
|
|
10096
10267
|
createEngineFromEnv
|
|
10097
10268
|
};
|
|
10098
|
-
//# sourceMappingURL=chunk-
|
|
10269
|
+
//# sourceMappingURL=chunk-Q3LMLQAI.js.map
|