@koda-sl/baker-cli 0.394.0-dev.d28cef496 → 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 +99 -48
- package/canvas/static-ad-composition/index.html +404 -0
- package/canvas/static-ad-composition/meta.json +53 -0
- package/dist/{chunk-FEJQJ64L.js → chunk-Q3LMLQAI.js} +399 -119
- package/dist/chunk-Q3LMLQAI.js.map +1 -0
- package/dist/cli.js +5971 -3049
- 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-FEJQJ64L.js.map +0 -1
|
@@ -20,9 +20,9 @@ import {
|
|
|
20
20
|
shouldEscalate
|
|
21
21
|
} from "./chunk-AOVFDDVW.js";
|
|
22
22
|
|
|
23
|
-
// ../../.pnpm-store/v10/links/@/safe-stable-stringify/2.5.0/
|
|
23
|
+
// ../../.pnpm-store/v10/links/@/safe-stable-stringify/2.5.0/810146e81bae4e3a061fe487864f2fde80c4b03b886877dc0f1fffbc6480b67e/node_modules/safe-stable-stringify/index.js
|
|
24
24
|
var require_safe_stable_stringify = __commonJS({
|
|
25
|
-
"../../.pnpm-store/v10/links/@/safe-stable-stringify/2.5.0/
|
|
25
|
+
"../../.pnpm-store/v10/links/@/safe-stable-stringify/2.5.0/810146e81bae4e3a061fe487864f2fde80c4b03b886877dc0f1fffbc6480b67e/node_modules/safe-stable-stringify/index.js"(exports, module) {
|
|
26
26
|
"use strict";
|
|
27
27
|
var { hasOwnProperty } = Object.prototype;
|
|
28
28
|
var stringify = configure2();
|
|
@@ -1123,7 +1123,7 @@ function resolveAdaptFormats(params) {
|
|
|
1123
1123
|
return params.formats ?? [];
|
|
1124
1124
|
}
|
|
1125
1125
|
|
|
1126
|
-
// ../../.pnpm-store/v10/links/@/safe-stable-stringify/2.5.0/
|
|
1126
|
+
// ../../.pnpm-store/v10/links/@/safe-stable-stringify/2.5.0/810146e81bae4e3a061fe487864f2fde80c4b03b886877dc0f1fffbc6480b67e/node_modules/safe-stable-stringify/esm/wrapper.js
|
|
1127
1127
|
var import__ = __toESM(require_safe_stable_stringify(), 1);
|
|
1128
1128
|
var configure = import__.default.configure;
|
|
1129
1129
|
var wrapper_default = import__.default;
|
|
@@ -1340,12 +1340,17 @@ var IMAGE_GENERATE_MODELS = [
|
|
|
1340
1340
|
"openai/gpt-image-2.5-sunburst",
|
|
1341
1341
|
"openai/gpt-image-2",
|
|
1342
1342
|
"openai/gpt-5.4-image-2",
|
|
1343
|
-
"google/gemini-3.1-flash-image
|
|
1343
|
+
"google/gemini-3.1-flash-image",
|
|
1344
1344
|
"google/gemini-3.1-flash-lite-image",
|
|
1345
|
-
"google/gemini-3-pro-image
|
|
1346
|
-
"recraft/recraft-v4.1-pro-vector"
|
|
1345
|
+
"google/gemini-3-pro-image",
|
|
1346
|
+
"recraft/recraft-v4.1-pro-vector",
|
|
1347
|
+
// LEGACY — the preview ids the Gemini image models shipped under until they went GA
|
|
1348
|
+
// on 2026-06-18. Kept so every canvas written with them still validates and re-runs;
|
|
1349
|
+
// never pick them for new work.
|
|
1350
|
+
"google/gemini-3.1-flash-image-preview",
|
|
1351
|
+
"google/gemini-3-pro-image-preview"
|
|
1347
1352
|
];
|
|
1348
|
-
var DEFAULT_IMAGE_GENERATE_MODEL = "google/gemini-3.1-flash-image
|
|
1353
|
+
var DEFAULT_IMAGE_GENERATE_MODEL = "google/gemini-3.1-flash-image";
|
|
1349
1354
|
var VIDEO_GENERATE_MODELS = [
|
|
1350
1355
|
"bytedance/seedance-2.0",
|
|
1351
1356
|
"bytedance/seedance-2.5",
|
|
@@ -1454,7 +1459,9 @@ var MODEL_REGISTRY = {
|
|
|
1454
1459
|
// Vision judgement — the selection contract (numbered images, JSON
|
|
1455
1460
|
// response) is baked into the backend prompt; `prompt` carries only the
|
|
1456
1461
|
// selection criteria. `count` has an engine-side default of 1, so it is
|
|
1457
|
-
// 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.
|
|
1458
1465
|
"~google/gemini-flash-latest": {
|
|
1459
1466
|
label: "Google Gemini Flash (latest, Vision)",
|
|
1460
1467
|
inputs: [{ kind: "image", mimes: OPENROUTER_IMAGE_MIMES }],
|
|
@@ -1463,7 +1470,8 @@ var MODEL_REGISTRY = {
|
|
|
1463
1470
|
prompt: { kind: "string" },
|
|
1464
1471
|
count: { kind: "number", min: 1 },
|
|
1465
1472
|
temperature: { kind: "number", min: 0, max: 2 },
|
|
1466
|
-
max_tokens: { kind: "number", min: 1 }
|
|
1473
|
+
max_tokens: { kind: "number", min: 1 },
|
|
1474
|
+
keep_reserved_areas: { kind: "boolean" }
|
|
1467
1475
|
}
|
|
1468
1476
|
},
|
|
1469
1477
|
"~google/gemini-pro-latest": {
|
|
@@ -1474,7 +1482,8 @@ var MODEL_REGISTRY = {
|
|
|
1474
1482
|
prompt: { kind: "string" },
|
|
1475
1483
|
count: { kind: "number", min: 1 },
|
|
1476
1484
|
temperature: { kind: "number", min: 0, max: 2 },
|
|
1477
|
-
max_tokens: { kind: "number", min: 1 }
|
|
1485
|
+
max_tokens: { kind: "number", min: 1 },
|
|
1486
|
+
keep_reserved_areas: { kind: "boolean" }
|
|
1478
1487
|
}
|
|
1479
1488
|
}
|
|
1480
1489
|
},
|
|
@@ -1536,6 +1545,32 @@ var MODEL_REGISTRY = {
|
|
|
1536
1545
|
quality: { kind: "string", enum: OPENROUTER_IMAGE_QUALITY }
|
|
1537
1546
|
}
|
|
1538
1547
|
},
|
|
1548
|
+
// The GA Nano Banana ids (2026-06-18). Same surface as the preview entries they
|
|
1549
|
+
// replace — read off GET /api/v1/images/models/google/gemini-3.1-flash-image/endpoints
|
|
1550
|
+
// and .../gemini-3-pro-image/endpoints — so a canvas moves between them unchanged.
|
|
1551
|
+
"google/gemini-3.1-flash-image": {
|
|
1552
|
+
label: "Google Gemini 3.1 Flash Image (Nano Banana 2)",
|
|
1553
|
+
inputs: [],
|
|
1554
|
+
optional_inputs: [{ kind: "image", mimes: OPENROUTER_IMAGE_MIMES, max: GEMINI_IMAGE_MAX_REFERENCES }],
|
|
1555
|
+
required: ["prompt"],
|
|
1556
|
+
params: {
|
|
1557
|
+
prompt: { kind: "string" },
|
|
1558
|
+
aspect_ratio: { kind: "string", enum: OPENROUTER_IMAGE_AR_EXTREME },
|
|
1559
|
+
image_size: { kind: "string", enum: OPENROUTER_IMAGE_SIZE_EXTENDED }
|
|
1560
|
+
}
|
|
1561
|
+
},
|
|
1562
|
+
"google/gemini-3-pro-image": {
|
|
1563
|
+
label: "Google Gemini 3 Pro Image (Nano Banana Pro)",
|
|
1564
|
+
inputs: [],
|
|
1565
|
+
optional_inputs: [{ kind: "image", mimes: OPENROUTER_IMAGE_MIMES, max: GEMINI_IMAGE_MAX_REFERENCES }],
|
|
1566
|
+
required: ["prompt"],
|
|
1567
|
+
params: {
|
|
1568
|
+
prompt: { kind: "string" },
|
|
1569
|
+
aspect_ratio: { kind: "string", enum: OPENROUTER_IMAGE_AR },
|
|
1570
|
+
image_size: { kind: "string", enum: OPENROUTER_IMAGE_SIZE }
|
|
1571
|
+
}
|
|
1572
|
+
},
|
|
1573
|
+
// LEGACY preview ids — see IMAGE_GENERATE_MODELS.
|
|
1539
1574
|
"google/gemini-3.1-flash-image-preview": {
|
|
1540
1575
|
label: "Google Gemini 3.1 Flash Image (Preview)",
|
|
1541
1576
|
inputs: [],
|
|
@@ -1573,7 +1608,7 @@ var MODEL_REGISTRY = {
|
|
|
1573
1608
|
}
|
|
1574
1609
|
},
|
|
1575
1610
|
"google/gemini-3-pro-image-preview": {
|
|
1576
|
-
label: "Google Gemini 3 Pro Image (
|
|
1611
|
+
label: "Google Gemini 3 Pro Image (Preview)",
|
|
1577
1612
|
inputs: [],
|
|
1578
1613
|
optional_inputs: [{ kind: "image", mimes: OPENROUTER_IMAGE_MIMES, max: GEMINI_IMAGE_MAX_REFERENCES }],
|
|
1579
1614
|
required: ["prompt"],
|
|
@@ -1616,6 +1651,35 @@ var MODEL_REGISTRY = {
|
|
|
1616
1651
|
// is baked into the backend handler — params here are semantic levers
|
|
1617
1652
|
// only. Both models consume MULTIPLE reference images via the `references`
|
|
1618
1653
|
// input slot (required: a sheet without source images is just image_generate).
|
|
1654
|
+
"google/gemini-3-pro-image": {
|
|
1655
|
+
label: "Google Gemini 3 Pro Image (Nano Banana Pro)",
|
|
1656
|
+
inputs: [{ kind: "image", mimes: OPENROUTER_IMAGE_MIMES }],
|
|
1657
|
+
required: ["subject_description", "subject_type"],
|
|
1658
|
+
params: {
|
|
1659
|
+
subject_description: { kind: "string" },
|
|
1660
|
+
subject_type: { kind: "string", enum: ["character", "person", "product", "location"] },
|
|
1661
|
+
views: { kind: "json" },
|
|
1662
|
+
style: { kind: "string" },
|
|
1663
|
+
prompt_override: { kind: "string" },
|
|
1664
|
+
aspect_ratio: { kind: "string", enum: OPENROUTER_IMAGE_AR },
|
|
1665
|
+
image_size: { kind: "string", enum: OPENROUTER_IMAGE_SIZE }
|
|
1666
|
+
}
|
|
1667
|
+
},
|
|
1668
|
+
"google/gemini-3.1-flash-image": {
|
|
1669
|
+
label: "Google Gemini 3.1 Flash Image (Nano Banana 2)",
|
|
1670
|
+
inputs: [{ kind: "image", mimes: OPENROUTER_IMAGE_MIMES }],
|
|
1671
|
+
required: ["subject_description", "subject_type"],
|
|
1672
|
+
params: {
|
|
1673
|
+
subject_description: { kind: "string" },
|
|
1674
|
+
subject_type: { kind: "string", enum: ["character", "person", "product", "location"] },
|
|
1675
|
+
views: { kind: "json" },
|
|
1676
|
+
style: { kind: "string" },
|
|
1677
|
+
prompt_override: { kind: "string" },
|
|
1678
|
+
aspect_ratio: { kind: "string", enum: OPENROUTER_IMAGE_AR_EXTREME },
|
|
1679
|
+
image_size: { kind: "string", enum: OPENROUTER_IMAGE_SIZE_EXTENDED }
|
|
1680
|
+
}
|
|
1681
|
+
},
|
|
1682
|
+
// LEGACY preview ids, kept so existing canvases re-run.
|
|
1619
1683
|
"google/gemini-3-pro-image-preview": {
|
|
1620
1684
|
label: "Google Gemini 3 Pro Image (Nano Banana Pro)",
|
|
1621
1685
|
inputs: [{ kind: "image", mimes: OPENROUTER_IMAGE_MIMES }],
|
|
@@ -1654,6 +1718,29 @@ var MODEL_REGISTRY = {
|
|
|
1654
1718
|
// registry can't express "one of" or gate array elements), so `formats`
|
|
1655
1719
|
// is not listed as registry-required. The source creative is a required
|
|
1656
1720
|
// input slot.
|
|
1721
|
+
"google/gemini-3-pro-image": {
|
|
1722
|
+
label: "Google Gemini 3 Pro Image (Nano Banana Pro)",
|
|
1723
|
+
inputs: [{ kind: "image", mimes: OPENROUTER_IMAGE_MIMES }],
|
|
1724
|
+
required: [],
|
|
1725
|
+
params: {
|
|
1726
|
+
formats: { kind: "json" },
|
|
1727
|
+
platform: { kind: "string", enum: AD_FORMAT_PLATFORMS },
|
|
1728
|
+
guidance: { kind: "string" },
|
|
1729
|
+
image_size: { kind: "string", enum: OPENROUTER_IMAGE_SIZE }
|
|
1730
|
+
}
|
|
1731
|
+
},
|
|
1732
|
+
"google/gemini-3.1-flash-image": {
|
|
1733
|
+
label: "Google Gemini 3.1 Flash Image (Nano Banana 2)",
|
|
1734
|
+
inputs: [{ kind: "image", mimes: OPENROUTER_IMAGE_MIMES }],
|
|
1735
|
+
required: [],
|
|
1736
|
+
params: {
|
|
1737
|
+
formats: { kind: "json" },
|
|
1738
|
+
platform: { kind: "string", enum: AD_FORMAT_PLATFORMS },
|
|
1739
|
+
guidance: { kind: "string" },
|
|
1740
|
+
image_size: { kind: "string", enum: OPENROUTER_IMAGE_SIZE_EXTENDED }
|
|
1741
|
+
}
|
|
1742
|
+
},
|
|
1743
|
+
// LEGACY preview ids, kept so existing canvases re-run.
|
|
1657
1744
|
"google/gemini-3-pro-image-preview": {
|
|
1658
1745
|
label: "Google Gemini 3 Pro Image (Nano Banana Pro)",
|
|
1659
1746
|
inputs: [{ kind: "image", mimes: OPENROUTER_IMAGE_MIMES }],
|
|
@@ -2262,8 +2349,8 @@ function promptMaxLength(kind, model) {
|
|
|
2262
2349
|
return schema?.kind === "string" ? schema.maxLength : void 0;
|
|
2263
2350
|
}
|
|
2264
2351
|
function inputSlot(kind, model, inputKind) {
|
|
2265
|
-
const
|
|
2266
|
-
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);
|
|
2267
2354
|
}
|
|
2268
2355
|
function maxInputSlot(kind, model, inputKind) {
|
|
2269
2356
|
return inputSlot(kind, model, inputKind)?.max;
|
|
@@ -2332,9 +2419,9 @@ function nearestSupportedImageSize(kind, model, target) {
|
|
|
2332
2419
|
function isManagedNodeKind(nodeType) {
|
|
2333
2420
|
return Object.keys(MODEL_REGISTRY).includes(nodeType);
|
|
2334
2421
|
}
|
|
2335
|
-
function checkRequiredParams(
|
|
2422
|
+
function checkRequiredParams(spec2, params, model) {
|
|
2336
2423
|
const errors = [];
|
|
2337
|
-
for (const req of
|
|
2424
|
+
for (const req of spec2.required) {
|
|
2338
2425
|
if (params[req] === void 0 || params[req] === null) {
|
|
2339
2426
|
errors.push({
|
|
2340
2427
|
code: "missing_required_param",
|
|
@@ -2345,12 +2432,12 @@ function checkRequiredParams(spec, params, model) {
|
|
|
2345
2432
|
}
|
|
2346
2433
|
return errors;
|
|
2347
2434
|
}
|
|
2348
|
-
function checkParamValues(
|
|
2435
|
+
function checkParamValues(spec2, params, model) {
|
|
2349
2436
|
const errors = [];
|
|
2350
2437
|
for (const [key, value] of Object.entries(params)) {
|
|
2351
2438
|
if (key === "model") continue;
|
|
2352
2439
|
if (value === void 0) continue;
|
|
2353
|
-
const schema =
|
|
2440
|
+
const schema = spec2.params[key];
|
|
2354
2441
|
if (!schema) {
|
|
2355
2442
|
errors.push({
|
|
2356
2443
|
code: "unknown_param",
|
|
@@ -2365,14 +2452,14 @@ function checkParamValues(spec, params, model) {
|
|
|
2365
2452
|
return errors;
|
|
2366
2453
|
}
|
|
2367
2454
|
function validateModelParams(nodeType, model, params) {
|
|
2368
|
-
const
|
|
2369
|
-
if (!
|
|
2455
|
+
const spec2 = MODEL_REGISTRY[nodeType][model];
|
|
2456
|
+
if (!spec2) {
|
|
2370
2457
|
return {
|
|
2371
2458
|
ok: false,
|
|
2372
2459
|
errors: [{ code: "unknown_model", message: `unknown model "${model}" for node "${nodeType}"` }]
|
|
2373
2460
|
};
|
|
2374
2461
|
}
|
|
2375
|
-
const errors = [...checkRequiredParams(
|
|
2462
|
+
const errors = [...checkRequiredParams(spec2, params, model), ...checkParamValues(spec2, params, model)];
|
|
2376
2463
|
if (errors.length > 0) return { ok: false, errors };
|
|
2377
2464
|
if (nodeType === "video_generate") {
|
|
2378
2465
|
const tooHeavy = clipTooHeavyToIngest(
|
|
@@ -2384,8 +2471,8 @@ function validateModelParams(nodeType, model, params) {
|
|
|
2384
2471
|
return { ok: true };
|
|
2385
2472
|
}
|
|
2386
2473
|
function validateModelInputs(nodeType, model, providedKinds) {
|
|
2387
|
-
const
|
|
2388
|
-
if (!
|
|
2474
|
+
const spec2 = MODEL_REGISTRY[nodeType][model];
|
|
2475
|
+
if (!spec2) {
|
|
2389
2476
|
return {
|
|
2390
2477
|
ok: false,
|
|
2391
2478
|
errors: [{ code: "unknown_model", message: `unknown model "${model}" for node "${nodeType}"` }]
|
|
@@ -2393,7 +2480,7 @@ function validateModelInputs(nodeType, model, providedKinds) {
|
|
|
2393
2480
|
}
|
|
2394
2481
|
const have = new Set(providedKinds);
|
|
2395
2482
|
const errors = [];
|
|
2396
|
-
for (const inputSpec of
|
|
2483
|
+
for (const inputSpec of spec2.inputs) {
|
|
2397
2484
|
if (!have.has(inputSpec.kind)) {
|
|
2398
2485
|
errors.push({
|
|
2399
2486
|
code: "missing_input",
|
|
@@ -3133,7 +3220,7 @@ var LocalAssetStore = class {
|
|
|
3133
3220
|
);
|
|
3134
3221
|
const written = await readFile(tmp);
|
|
3135
3222
|
const computed = sha256Hex(written);
|
|
3136
|
-
if (computed !== args.sha256) {
|
|
3223
|
+
if (computed !== args.sha256 && !cdnReencodedImage(res, args.kind)) {
|
|
3137
3224
|
throw new Error(`asset-store: sha mismatch for ${args.url} (got ${computed}, expected ${args.sha256})`);
|
|
3138
3225
|
}
|
|
3139
3226
|
await rename(tmp, dest);
|
|
@@ -3154,6 +3241,9 @@ var LocalAssetStore = class {
|
|
|
3154
3241
|
});
|
|
3155
3242
|
}
|
|
3156
3243
|
};
|
|
3244
|
+
function cdnReencodedImage(res, kind) {
|
|
3245
|
+
return kind === "image" && res.headers.get("cf-polished") !== null;
|
|
3246
|
+
}
|
|
3157
3247
|
function byteLimit(url, maxBytes) {
|
|
3158
3248
|
let total = 0;
|
|
3159
3249
|
return new Transform({
|
|
@@ -3600,9 +3690,9 @@ function parseSpineInputLens(args) {
|
|
|
3600
3690
|
}
|
|
3601
3691
|
|
|
3602
3692
|
// src/engine/engine/define.ts
|
|
3603
|
-
function resolveOutputKinds(
|
|
3604
|
-
if (!
|
|
3605
|
-
return typeof
|
|
3693
|
+
function resolveOutputKinds(spec2, params) {
|
|
3694
|
+
if (!spec2) return {};
|
|
3695
|
+
return typeof spec2 === "function" ? spec2(params) : spec2;
|
|
3606
3696
|
}
|
|
3607
3697
|
function defineNode(def) {
|
|
3608
3698
|
return def;
|
|
@@ -3680,9 +3770,9 @@ function cappedParamsOf(n) {
|
|
|
3680
3770
|
if (!isManagedNodeKind(n.type)) return [];
|
|
3681
3771
|
const model = n.params?.model;
|
|
3682
3772
|
if (typeof model !== "string") return [];
|
|
3683
|
-
const
|
|
3684
|
-
if (!
|
|
3685
|
-
return Object.entries(
|
|
3773
|
+
const spec2 = MODEL_REGISTRY[n.type][model];
|
|
3774
|
+
if (!spec2) return [];
|
|
3775
|
+
return Object.entries(spec2.params).flatMap(
|
|
3686
3776
|
([key, schema]) => schema.kind === "string" && schema.maxLength !== void 0 ? [{ key, max: schema.maxLength, model }] : []
|
|
3687
3777
|
);
|
|
3688
3778
|
}
|
|
@@ -4625,8 +4715,8 @@ function checkReferenceCompleteness(ctx, meta) {
|
|
|
4625
4715
|
}
|
|
4626
4716
|
}
|
|
4627
4717
|
function videoModelMaxDuration(model) {
|
|
4628
|
-
const
|
|
4629
|
-
const durations =
|
|
4718
|
+
const spec2 = MODEL_REGISTRY.video_generate[model];
|
|
4719
|
+
const durations = spec2?.params?.duration?.enum;
|
|
4630
4720
|
if (!durations) return void 0;
|
|
4631
4721
|
const nums = durations.filter((d) => typeof d === "number");
|
|
4632
4722
|
return nums.length > 0 ? Math.max(...nums) : void 0;
|
|
@@ -5275,6 +5365,7 @@ var Engine = class {
|
|
|
5275
5365
|
}
|
|
5276
5366
|
}
|
|
5277
5367
|
const t0 = Date.now();
|
|
5368
|
+
let charged;
|
|
5278
5369
|
const ctx = {
|
|
5279
5370
|
canvasRunId: runId,
|
|
5280
5371
|
nodeId: node.id,
|
|
@@ -5284,13 +5375,16 @@ var Engine = class {
|
|
|
5284
5375
|
client: this.client,
|
|
5285
5376
|
assets: this.assets,
|
|
5286
5377
|
log: this.log,
|
|
5287
|
-
signal: opts.signal
|
|
5378
|
+
signal: opts.signal,
|
|
5379
|
+
reportCharge: (credits2) => {
|
|
5380
|
+
charged = (charged ?? 0) + credits2;
|
|
5381
|
+
}
|
|
5288
5382
|
};
|
|
5289
5383
|
const preparedForExec = needsLocalMaterialization(def) ? { ...prepared, resolvedInputs: await this.materializeLocalInputs(prepared.resolvedInputs) } : prepared;
|
|
5290
5384
|
const { parsedInputs, parsedParams } = parseNodeArgs(def, preparedForExec, node.id, node.type);
|
|
5291
5385
|
const result = await invokeExecute(def, parsedInputs, parsedParams, ctx, node.id, node.type);
|
|
5292
5386
|
const elapsed = Date.now() - t0;
|
|
5293
|
-
const credits =
|
|
5387
|
+
const credits = charged ?? estimatedNodeCredits(def, parsedParams);
|
|
5294
5388
|
const outputsObj = result;
|
|
5295
5389
|
outputs[node.id] = outputsObj;
|
|
5296
5390
|
if (this.persistAssets) {
|
|
@@ -5428,6 +5522,9 @@ async function invokeExecute(def, parsedInputs, parsedParams, ctx, nodeId, nodeT
|
|
|
5428
5522
|
throw new NodeExecutionError(nodeId, nodeType, { kind: "local", cause: e });
|
|
5429
5523
|
}
|
|
5430
5524
|
}
|
|
5525
|
+
function estimatedNodeCredits(def, params) {
|
|
5526
|
+
return def.cost ? def.cost({ params }).credits : 0;
|
|
5527
|
+
}
|
|
5431
5528
|
function needsLocalMaterialization(def) {
|
|
5432
5529
|
return def.location === "local" && !def.passthroughRefs;
|
|
5433
5530
|
}
|
|
@@ -6280,17 +6377,17 @@ var FontRef = BaseAssetRef.extend({
|
|
|
6280
6377
|
var AssetRef = z4.discriminatedUnion("kind", [ImageRef, VideoRef, AudioRef, JsonRef, TextRef, FontRef]);
|
|
6281
6378
|
|
|
6282
6379
|
// src/engine/nodes/remote/delegate.ts
|
|
6283
|
-
function delegated(
|
|
6380
|
+
function delegated(spec2) {
|
|
6284
6381
|
return {
|
|
6285
|
-
...
|
|
6382
|
+
...spec2,
|
|
6286
6383
|
location: "remote",
|
|
6287
6384
|
execute: async ({ inputs, params, ctx }) => {
|
|
6288
6385
|
return await callBackendExec({
|
|
6289
|
-
nodeType:
|
|
6290
|
-
nodeVersion:
|
|
6386
|
+
nodeType: spec2.id,
|
|
6387
|
+
nodeVersion: spec2.version,
|
|
6291
6388
|
params,
|
|
6292
6389
|
inputs,
|
|
6293
|
-
outputKinds: resolveOutputKinds(
|
|
6390
|
+
outputKinds: resolveOutputKinds(spec2.outputKinds, params),
|
|
6294
6391
|
ctx
|
|
6295
6392
|
});
|
|
6296
6393
|
}
|
|
@@ -6312,6 +6409,9 @@ async function callBackendExec(args) {
|
|
|
6312
6409
|
},
|
|
6313
6410
|
args.ctx.signal
|
|
6314
6411
|
);
|
|
6412
|
+
if (typeof res.credits_charged === "number" && Number.isFinite(res.credits_charged)) {
|
|
6413
|
+
args.ctx.reportCharge?.(res.credits_charged);
|
|
6414
|
+
}
|
|
6315
6415
|
return await ingestOutputs(res.outputs, args.ctx, args.outputKinds);
|
|
6316
6416
|
} catch (e) {
|
|
6317
6417
|
throw mapClientError(args.ctx, e);
|
|
@@ -6663,22 +6763,22 @@ function inferMimeFromPath(absPath, sniffBytes) {
|
|
|
6663
6763
|
return "application/octet-stream";
|
|
6664
6764
|
}
|
|
6665
6765
|
async function rasterizeSvgToPng(bytes) {
|
|
6666
|
-
const { default:
|
|
6667
|
-
const base = await
|
|
6766
|
+
const { default: sharp2 } = await import("sharp");
|
|
6767
|
+
const base = await sharp2(bytes).metadata();
|
|
6668
6768
|
const longest = Math.max(base.width ?? 0, base.height ?? 0);
|
|
6669
6769
|
let density = SVG_BASE_DENSITY;
|
|
6670
6770
|
if (longest > 0) {
|
|
6671
6771
|
const scaled = Math.round(SVG_BASE_DENSITY * SVG_TARGET_LONGEST_PX / longest);
|
|
6672
6772
|
density = Math.min(SVG_MAX_DENSITY, Math.max(SVG_BASE_DENSITY, scaled));
|
|
6673
6773
|
}
|
|
6674
|
-
return await
|
|
6774
|
+
return await sharp2(bytes, { density }).png({ force: true, palette: false }).toBuffer();
|
|
6675
6775
|
}
|
|
6676
6776
|
var MODEL_SAFE_IMAGE_MIMES = /* @__PURE__ */ new Set(["image/jpeg", "image/png", "image/gif", "image/webp"]);
|
|
6677
6777
|
var MAX_MODEL_REFERENCE_BYTES = 30 * 1024 * 1024;
|
|
6678
6778
|
async function fitWithinReferenceLimit(bytes, mime, maxBytes) {
|
|
6679
6779
|
if (bytes.byteLength <= maxBytes) return { bytes, mime };
|
|
6680
|
-
const { default:
|
|
6681
|
-
const read = () =>
|
|
6780
|
+
const { default: sharp2 } = await import("sharp");
|
|
6781
|
+
const read = () => sharp2(bytes, { animated: true });
|
|
6682
6782
|
const meta = await read().metadata();
|
|
6683
6783
|
const animated = (meta.pages ?? 1) > 1;
|
|
6684
6784
|
const keepAlpha = meta.hasAlpha === true;
|
|
@@ -6716,11 +6816,11 @@ async function toModelSafeImage(bytes, opts) {
|
|
|
6716
6816
|
if (sniffSvg(bytes)) {
|
|
6717
6817
|
return await fit(await rasterizeSvgToPng(bytes), "image/png", "svg");
|
|
6718
6818
|
}
|
|
6719
|
-
const { default:
|
|
6819
|
+
const { default: sharp2 } = await import("sharp");
|
|
6720
6820
|
let png;
|
|
6721
6821
|
let format;
|
|
6722
6822
|
try {
|
|
6723
|
-
const img =
|
|
6823
|
+
const img = sharp2(bytes);
|
|
6724
6824
|
format = (await img.metadata()).format ?? "unknown";
|
|
6725
6825
|
png = await img.png({ force: true }).toBuffer();
|
|
6726
6826
|
} catch (e) {
|
|
@@ -7081,10 +7181,10 @@ function planSingleInputSlot(tmpDir, slot, ref, lookup, stagedInputs) {
|
|
|
7081
7181
|
}
|
|
7082
7182
|
function planOutputs(tmpDir, outputs, lookup) {
|
|
7083
7183
|
const outputPaths = [];
|
|
7084
|
-
for (const [name,
|
|
7085
|
-
const ext =
|
|
7184
|
+
for (const [name, spec2] of Object.entries(outputs)) {
|
|
7185
|
+
const ext = spec2.ext.startsWith(".") ? spec2.ext : `.${spec2.ext}`;
|
|
7086
7186
|
const absPath = path8.join(tmpDir, `out_${name}${ext}`);
|
|
7087
|
-
outputPaths.push({ name, absPath, spec });
|
|
7187
|
+
outputPaths.push({ name, absPath, spec: spec2 });
|
|
7088
7188
|
lookup.set(`out.${name}`, absPath);
|
|
7089
7189
|
}
|
|
7090
7190
|
return outputPaths;
|
|
@@ -7192,7 +7292,7 @@ async function runCli(opts) {
|
|
|
7192
7292
|
${tailLines(stderr, 40)}`);
|
|
7193
7293
|
}
|
|
7194
7294
|
const result = {};
|
|
7195
|
-
for (const { name, absPath, spec } of outputPaths) {
|
|
7295
|
+
for (const { name, absPath, spec: spec2 } of outputPaths) {
|
|
7196
7296
|
const s = await stat3(absPath).catch(() => null);
|
|
7197
7297
|
if (!s?.isFile() || s.size === 0) {
|
|
7198
7298
|
throw new Error(`cli-runner: declared output "${name}" missing or empty at ${absPath}`);
|
|
@@ -7200,8 +7300,8 @@ ${tailLines(stderr, 40)}`);
|
|
|
7200
7300
|
const bytes = await readFile6(absPath);
|
|
7201
7301
|
const ref = await ctx.assets.ingestBytes({
|
|
7202
7302
|
bytes: Buffer.from(bytes),
|
|
7203
|
-
kind:
|
|
7204
|
-
mime: mimeForExt(
|
|
7303
|
+
kind: spec2.kind,
|
|
7304
|
+
mime: mimeForExt(spec2.ext)
|
|
7205
7305
|
});
|
|
7206
7306
|
result[name] = ref;
|
|
7207
7307
|
}
|
|
@@ -7692,7 +7792,7 @@ async function collectFiles(root, current) {
|
|
|
7692
7792
|
import { readFile as readFile9 } from "fs/promises";
|
|
7693
7793
|
import path12 from "path";
|
|
7694
7794
|
import { z as z10 } from "zod";
|
|
7695
|
-
var InputKind = z10.enum(["video", "image", "audio", "json"]);
|
|
7795
|
+
var InputKind = z10.enum(["video", "image", "audio", "json", "font"]);
|
|
7696
7796
|
var InputSpec = z10.object({
|
|
7697
7797
|
kind: InputKind,
|
|
7698
7798
|
required: z10.boolean().optional().default(false),
|
|
@@ -7789,47 +7889,47 @@ async function loadCompositionMeta(compositionDir) {
|
|
|
7789
7889
|
}
|
|
7790
7890
|
function buildParamsSchema(meta) {
|
|
7791
7891
|
const shape = {};
|
|
7792
|
-
for (const [name,
|
|
7793
|
-
shape[name] = buildParamFieldSchema(name,
|
|
7892
|
+
for (const [name, spec2] of Object.entries(meta.params)) {
|
|
7893
|
+
shape[name] = buildParamFieldSchema(name, spec2);
|
|
7794
7894
|
}
|
|
7795
7895
|
return z10.object(shape).strict();
|
|
7796
7896
|
}
|
|
7797
|
-
function buildParamFieldSchema(name,
|
|
7798
|
-
switch (
|
|
7897
|
+
function buildParamFieldSchema(name, spec2) {
|
|
7898
|
+
switch (spec2.kind) {
|
|
7799
7899
|
case "string": {
|
|
7800
|
-
const s =
|
|
7801
|
-
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);
|
|
7802
7902
|
}
|
|
7803
7903
|
case "integer": {
|
|
7804
7904
|
let s = z10.number().int();
|
|
7805
|
-
if (
|
|
7806
|
-
if (
|
|
7807
|
-
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);
|
|
7808
7908
|
}
|
|
7809
7909
|
case "number": {
|
|
7810
7910
|
let s = z10.number();
|
|
7811
|
-
if (
|
|
7812
|
-
if (
|
|
7813
|
-
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);
|
|
7814
7914
|
}
|
|
7815
7915
|
case "boolean":
|
|
7816
|
-
return finalize(z10.boolean(),
|
|
7916
|
+
return finalize(z10.boolean(), spec2.default, spec2.required);
|
|
7817
7917
|
case "color": {
|
|
7818
7918
|
const s = z10.string().regex(/^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/, {
|
|
7819
7919
|
message: `param "${name}": must be a 3/6/8-digit hex color (e.g. "#ff0066")`
|
|
7820
7920
|
});
|
|
7821
|
-
return finalize(s,
|
|
7921
|
+
return finalize(s, spec2.default, spec2.required);
|
|
7822
7922
|
}
|
|
7823
7923
|
case "image":
|
|
7824
|
-
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);
|
|
7825
7925
|
case "json":
|
|
7826
7926
|
return finalize(
|
|
7827
7927
|
z10.unknown().refine((v) => v !== void 0, { message: `param "${name}" is required` }),
|
|
7828
|
-
|
|
7829
|
-
|
|
7928
|
+
spec2.default,
|
|
7929
|
+
spec2.required
|
|
7830
7930
|
);
|
|
7831
7931
|
default: {
|
|
7832
|
-
const _exhaustive =
|
|
7932
|
+
const _exhaustive = spec2;
|
|
7833
7933
|
throw new Error(`buildParamFieldSchema: unknown kind ${_exhaustive.kind}`);
|
|
7834
7934
|
}
|
|
7835
7935
|
}
|
|
@@ -7841,6 +7941,7 @@ function finalize(schema, dflt, required) {
|
|
|
7841
7941
|
}
|
|
7842
7942
|
function defaultFilenameForInput(key, kind) {
|
|
7843
7943
|
if (kind === "video") return `${key}.mp4`;
|
|
7944
|
+
if (kind === "font") return `${key}.font`;
|
|
7844
7945
|
if (kind === "audio") return `${key}.mp3`;
|
|
7845
7946
|
if (kind === "json") return `${key}.json`;
|
|
7846
7947
|
return `${key}.png`;
|
|
@@ -8335,7 +8436,7 @@ async function validateComposition(rawParams) {
|
|
|
8335
8436
|
}
|
|
8336
8437
|
return issues;
|
|
8337
8438
|
}
|
|
8338
|
-
async function validateCompositionParams(rawParams) {
|
|
8439
|
+
async function validateCompositionParams(rawParams, ownedKeys = NODE_OWNED_PARAM_KEYS) {
|
|
8339
8440
|
const issues = [];
|
|
8340
8441
|
if (!rawParams || typeof rawParams !== "object") return issues;
|
|
8341
8442
|
const params = rawParams;
|
|
@@ -8357,7 +8458,7 @@ async function validateCompositionParams(rawParams) {
|
|
|
8357
8458
|
}
|
|
8358
8459
|
const dynamic = {};
|
|
8359
8460
|
for (const [k, v] of Object.entries(params)) {
|
|
8360
|
-
if (
|
|
8461
|
+
if (ownedKeys.has(k)) continue;
|
|
8361
8462
|
dynamic[k] = v;
|
|
8362
8463
|
}
|
|
8363
8464
|
for (const key of Object.keys(dynamic)) {
|
|
@@ -8418,12 +8519,12 @@ async function stageInputs2(tmp, inputs, meta, ctx) {
|
|
|
8418
8519
|
assertInputsMatchMeta(inputs, meta, "hyperframe_render");
|
|
8419
8520
|
let primaryDuration = null;
|
|
8420
8521
|
for (const [key, ref] of Object.entries(inputs ?? {})) {
|
|
8421
|
-
const
|
|
8422
|
-
if (!
|
|
8423
|
-
const filename =
|
|
8522
|
+
const spec2 = meta.inputs[key];
|
|
8523
|
+
if (!spec2) continue;
|
|
8524
|
+
const filename = spec2.staged_as ?? defaultFilenameForInput(key, spec2.kind);
|
|
8424
8525
|
await stageAsset(ref, tmp, filename);
|
|
8425
|
-
ctx.log(`staged ${
|
|
8426
|
-
if (
|
|
8526
|
+
ctx.log(`staged ${spec2.kind} \u2192 ${filename}`);
|
|
8527
|
+
if (spec2.kind === "video" && primaryDuration === null) {
|
|
8427
8528
|
primaryDuration = await probeDurationSeconds(path15.join(tmp, filename));
|
|
8428
8529
|
}
|
|
8429
8530
|
}
|
|
@@ -8438,24 +8539,24 @@ function assertInputsMatchMeta(inputs, meta, nodeId) {
|
|
|
8438
8539
|
throw new Error(`${nodeId}: inputs.${key} is not declared in composition meta.json (declares: ${declared})`);
|
|
8439
8540
|
}
|
|
8440
8541
|
}
|
|
8441
|
-
for (const [key,
|
|
8442
|
-
if (
|
|
8443
|
-
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})`);
|
|
8444
8545
|
}
|
|
8445
8546
|
}
|
|
8446
8547
|
for (const [key, ref] of Object.entries(inputs ?? {})) {
|
|
8447
|
-
const
|
|
8448
|
-
if (!
|
|
8449
|
-
if (ref.kind !==
|
|
8450
|
-
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}"`);
|
|
8451
8552
|
}
|
|
8452
8553
|
}
|
|
8453
8554
|
}
|
|
8454
8555
|
async function buildSubstitutionValues(compositionParams, meta, duration) {
|
|
8455
8556
|
const out = {};
|
|
8456
8557
|
for (const [name, value] of Object.entries(compositionParams)) {
|
|
8457
|
-
const
|
|
8458
|
-
if (
|
|
8558
|
+
const spec2 = meta.params[name];
|
|
8559
|
+
if (spec2?.kind === "image") {
|
|
8459
8560
|
out[name] = await coerceImageParam(value);
|
|
8460
8561
|
continue;
|
|
8461
8562
|
}
|
|
@@ -8567,7 +8668,9 @@ var hyperframeSnapshotNode = defineNode({
|
|
|
8567
8668
|
params: HyperframeSnapshotParams,
|
|
8568
8669
|
outputs: HyperframeSnapshotOutputs,
|
|
8569
8670
|
cost: () => ({ credits: 0, seconds_estimate: 3 }),
|
|
8570
|
-
|
|
8671
|
+
// The snapshot's own keys (`wait_for`) are not composition params: checked against the
|
|
8672
|
+
// render node's key set, a canvas that waited on a selector failed validate every time.
|
|
8673
|
+
validateExtra: ({ rawParams }) => validateCompositionParams(rawParams, NODE_OWNED_PARAM_KEYS2),
|
|
8571
8674
|
cacheKeyExtras: async ({ params }) => {
|
|
8572
8675
|
const out = {};
|
|
8573
8676
|
const composition = params.composition;
|
|
@@ -8674,18 +8777,18 @@ async function vendorGsap2(tmp, ctx) {
|
|
|
8674
8777
|
async function stageInputs3(tmp, inputs, meta, ctx) {
|
|
8675
8778
|
assertInputsMatchMeta(inputs, meta, "hyperframe_snapshot");
|
|
8676
8779
|
for (const [key, ref] of Object.entries(inputs ?? {})) {
|
|
8677
|
-
const
|
|
8678
|
-
if (!
|
|
8679
|
-
const filename =
|
|
8780
|
+
const spec2 = meta.inputs[key];
|
|
8781
|
+
if (!spec2) continue;
|
|
8782
|
+
const filename = spec2.staged_as ?? defaultFilenameForInput(key, spec2.kind);
|
|
8680
8783
|
await stageAsset(ref, tmp, filename);
|
|
8681
|
-
ctx.log(`staged ${
|
|
8784
|
+
ctx.log(`staged ${spec2.kind} \u2192 ${filename}`);
|
|
8682
8785
|
}
|
|
8683
8786
|
}
|
|
8684
8787
|
async function buildSubstitutionValues2(compositionParams, meta) {
|
|
8685
8788
|
const out = {};
|
|
8686
8789
|
for (const [name, value] of Object.entries(compositionParams)) {
|
|
8687
|
-
const
|
|
8688
|
-
if (
|
|
8790
|
+
const spec2 = meta.params[name];
|
|
8791
|
+
if (spec2?.kind === "image") {
|
|
8689
8792
|
out[name] = await coerceImageParam2(value);
|
|
8690
8793
|
continue;
|
|
8691
8794
|
}
|
|
@@ -8918,12 +9021,26 @@ var imageGenerateNode = delegated({
|
|
|
8918
9021
|
params: ImageGenerateParams,
|
|
8919
9022
|
outputs: z17.object({ images: z17.array(ImageRef).min(1) }).strict(),
|
|
8920
9023
|
outputKinds: { images: "image" },
|
|
8921
|
-
|
|
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
|
+
})
|
|
8922
9030
|
});
|
|
8923
9031
|
|
|
8924
9032
|
// src/engine/nodes/remote/imageAspectAdapt.ts
|
|
8925
9033
|
import { z as z18 } from "zod";
|
|
8926
|
-
var ASPECT_ADAPT_MODELS = [
|
|
9034
|
+
var ASPECT_ADAPT_MODELS = [
|
|
9035
|
+
"google/gemini-3-pro-image",
|
|
9036
|
+
"google/gemini-3.1-flash-image",
|
|
9037
|
+
"google/gemini-3-pro-image-preview",
|
|
9038
|
+
"google/gemini-3.1-flash-image-preview"
|
|
9039
|
+
];
|
|
9040
|
+
var PRO_ADAPT_MODELS = /* @__PURE__ */ new Set([
|
|
9041
|
+
"google/gemini-3-pro-image",
|
|
9042
|
+
"google/gemini-3-pro-image-preview"
|
|
9043
|
+
]);
|
|
8927
9044
|
var ASPECT_ADAPT_FORMATS = ["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9"];
|
|
8928
9045
|
var ImageAspectAdaptParams = z18.object({
|
|
8929
9046
|
model: z18.enum(ASPECT_ADAPT_MODELS),
|
|
@@ -8940,7 +9057,7 @@ var imageAspectAdaptNode = delegated({
|
|
|
8940
9057
|
version: "1.0.0",
|
|
8941
9058
|
category: "image",
|
|
8942
9059
|
summary: "Adapt ONE creative into multiple aspect ratios (Meta: 9:16 stories, 1:1 feed, 4:5, 16:9\u2026) in a single step. Give it either an explicit `formats` list OR a `platform` preset (meta_feed, meta_feed_stories, meta_all, linkedin, google_demandgen, tiktok, youtube) that expands to that platform's canonical set. AI recomposes the layout per format \u2014 identical subject, text, logos, colors, and style; the scene is extended/restructured, never stretched or cropped. Formats that already match the source ratio pass through unchanged at zero cost. Outputs are ordered exactly as the resolved format list.",
|
|
8943
|
-
when_to_use: "Use after a hero creative exists (image_generate, ingest, image_search) to fan it out to every placement format \u2014 wire the creative into `source` and set EITHER `formats` (exact ratios) OR `platform` (e.g. `meta_feed_stories`). Cost is estimated per format; formats matching the source ratio are free pass-throughs. Off-grid platform targets (e.g. Google 1.91:1) are not rendered here \u2014 crop them from the nearest ratio with `baker images crop`. Pick `google/gemini-3.1-flash-image
|
|
9060
|
+
when_to_use: "Use after a hero creative exists (image_generate, ingest, image_search) to fan it out to every placement format \u2014 wire the creative into `source` and set EITHER `formats` (exact ratios) OR `platform` (e.g. `meta_feed_stories`). Cost is estimated per format; formats matching the source ratio are free pass-throughs. Off-grid platform targets (e.g. Google 1.91:1) are not rendered here \u2014 crop them from the nearest ratio with `baker images crop`. Pick `google/gemini-3.1-flash-image` (Nano Banana 2) while iterating, `google/gemini-3-pro-image` (Nano Banana Pro) for final-quality adaptation.",
|
|
8944
9061
|
inputs: z18.object({ source: ImageRef }).loose(),
|
|
8945
9062
|
params: ImageAspectAdaptParams,
|
|
8946
9063
|
outputs: z18.object({ images: z18.array(ImageRef).min(1) }).strict(),
|
|
@@ -8949,7 +9066,7 @@ var imageAspectAdaptNode = delegated({
|
|
|
8949
9066
|
const p = params;
|
|
8950
9067
|
const count = Math.max(1, resolveAdaptFormats(p ?? {}).length);
|
|
8951
9068
|
return {
|
|
8952
|
-
credits: count * (p?.model
|
|
9069
|
+
credits: count * (PRO_ADAPT_MODELS.has(String(p?.model)) ? 20 : 5),
|
|
8953
9070
|
seconds_estimate: 30 + 30 * count
|
|
8954
9071
|
};
|
|
8955
9072
|
}
|
|
@@ -9035,7 +9152,16 @@ var imageLayerizeNode = delegated({
|
|
|
9035
9152
|
|
|
9036
9153
|
// src/engine/nodes/remote/imageReferenceSheet.ts
|
|
9037
9154
|
import { z as z22 } from "zod";
|
|
9038
|
-
var REFERENCE_SHEET_MODELS = [
|
|
9155
|
+
var REFERENCE_SHEET_MODELS = [
|
|
9156
|
+
"google/gemini-3-pro-image",
|
|
9157
|
+
"google/gemini-3.1-flash-image",
|
|
9158
|
+
"google/gemini-3-pro-image-preview",
|
|
9159
|
+
"google/gemini-3.1-flash-image-preview"
|
|
9160
|
+
];
|
|
9161
|
+
var PRO_SHEET_MODELS = /* @__PURE__ */ new Set([
|
|
9162
|
+
"google/gemini-3-pro-image",
|
|
9163
|
+
"google/gemini-3-pro-image-preview"
|
|
9164
|
+
]);
|
|
9039
9165
|
var ImageReferenceSheetParams = z22.object({
|
|
9040
9166
|
model: z22.enum(REFERENCE_SHEET_MODELS),
|
|
9041
9167
|
subject_description: z22.string().min(1),
|
|
@@ -9053,13 +9179,13 @@ var imageReferenceSheetNode = delegated({
|
|
|
9053
9179
|
version: "1.0.0",
|
|
9054
9180
|
category: "image",
|
|
9055
9181
|
summary: "Fuse 1\u20136 images of a single subject (person, character, product, or location/set) into ONE multi-view reference sheet \u2014 a labeled grid in consistent style and lighting: a turnaround (FRONT / SIDE / BACK\u2026) for a person/character/product, or several camera angles of the same room (WIDE / REVERSE / DETAIL\u2026) for a location. Curated models: Gemini 3 Pro Image (best fusion + labels), Gemini 3.1 Flash Image (cheap iteration).",
|
|
9056
|
-
when_to_use: "Use before image_generate / video_generate when a subject must stay consistent across many creatives \u2014 wire the `sheet` output into their `reference` input instead of re-describing the subject per prompt. `subject_description` should be the exact wording you reuse downstream. Pick `google/gemini-3-pro-image
|
|
9182
|
+
when_to_use: "Use before image_generate / video_generate when a subject must stay consistent across many creatives \u2014 wire the `sheet` output into their `reference` input instead of re-describing the subject per prompt. `subject_description` should be the exact wording you reuse downstream. Pick `google/gemini-3-pro-image` for final 6-view sheets at 2K+, `google/gemini-3.1-flash-image` while iterating.",
|
|
9057
9183
|
inputs: z22.object({ references: z22.array(ImageRef).min(1).max(6) }).loose(),
|
|
9058
9184
|
params: ImageReferenceSheetParams,
|
|
9059
9185
|
outputs: z22.object({ sheet: ImageRef }).strict(),
|
|
9060
9186
|
outputKinds: { sheet: "image" },
|
|
9061
9187
|
cost: ({ params }) => ({
|
|
9062
|
-
credits: params?.model
|
|
9188
|
+
credits: PRO_SHEET_MODELS.has(String(params?.model)) ? 20 : 5,
|
|
9063
9189
|
seconds_estimate: 30
|
|
9064
9190
|
})
|
|
9065
9191
|
});
|
|
@@ -9084,24 +9210,103 @@ var imageSearchNode = delegated({
|
|
|
9084
9210
|
});
|
|
9085
9211
|
|
|
9086
9212
|
// src/engine/nodes/remote/imageSelect.ts
|
|
9213
|
+
import { readFile as readFile13 } from "fs/promises";
|
|
9087
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
|
|
9088
9284
|
var IMAGE_SELECT_MODELS = ["~google/gemini-flash-latest", "~google/gemini-pro-latest"];
|
|
9089
|
-
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 = {
|
|
9090
9301
|
id: "image_select",
|
|
9091
9302
|
version: "1.0.0",
|
|
9092
9303
|
category: "vision",
|
|
9093
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.",
|
|
9094
|
-
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.",
|
|
9095
|
-
inputs:
|
|
9096
|
-
params:
|
|
9097
|
-
|
|
9098
|
-
|
|
9099
|
-
count: z24.number().int().min(1).default(1),
|
|
9100
|
-
temperature: z24.number().min(0).max(2).optional(),
|
|
9101
|
-
max_tokens: z24.number().int().positive().optional()
|
|
9102
|
-
}).strict(),
|
|
9103
|
-
outputs: z24.object({ images: z24.array(ImageRef).min(1), reasoning: TextRef }).strict(),
|
|
9104
|
-
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" },
|
|
9105
9310
|
cost: () => ({ credits: 1, seconds_estimate: 5 }),
|
|
9106
9311
|
// Arity is only knowable at validate time when `images` is a literal array
|
|
9107
9312
|
// of refs; a single `$ref:` string to an upstream array output is deferred
|
|
@@ -9122,7 +9327,76 @@ var imageSelectNode = delegated({
|
|
|
9122
9327
|
}
|
|
9123
9328
|
return issues;
|
|
9124
9329
|
}
|
|
9125
|
-
}
|
|
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
|
+
};
|
|
9126
9400
|
|
|
9127
9401
|
// src/engine/nodes/remote/music.ts
|
|
9128
9402
|
import { z as z25 } from "zod";
|
|
@@ -9468,7 +9742,7 @@ var videoLipsyncNode = delegated({
|
|
|
9468
9742
|
});
|
|
9469
9743
|
|
|
9470
9744
|
// src/engine/nodes/remote/videoTranscribe.ts
|
|
9471
|
-
import { mkdtemp as mkdtemp6, readFile as
|
|
9745
|
+
import { mkdtemp as mkdtemp6, readFile as readFile14, rm as rm6 } from "fs/promises";
|
|
9472
9746
|
import { tmpdir as tmpdir6 } from "os";
|
|
9473
9747
|
import path17 from "path";
|
|
9474
9748
|
import { z as z33 } from "zod";
|
|
@@ -9615,7 +9889,7 @@ async function tryExtractAudio(inputs, ctx) {
|
|
|
9615
9889
|
["-i", video.path, "-vn", "-ac", "1", "-ar", "16000", "-b:a", "64k", "-f", "mp3", "-y", audioPath],
|
|
9616
9890
|
{ timeout_ms: AUDIO_EXTRACT_TIMEOUT_MS }
|
|
9617
9891
|
);
|
|
9618
|
-
const bytes = await
|
|
9892
|
+
const bytes = await readFile14(audioPath);
|
|
9619
9893
|
if (bytes.byteLength === 0) {
|
|
9620
9894
|
ctx.log("video_transcribe: extracted audio is empty, sending full video");
|
|
9621
9895
|
return null;
|
|
@@ -9810,7 +10084,7 @@ function safeCost(def) {
|
|
|
9810
10084
|
|
|
9811
10085
|
// src/engine/storage/cache-store.ts
|
|
9812
10086
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
9813
|
-
import { mkdir as mkdir3, readFile as
|
|
10087
|
+
import { mkdir as mkdir3, readFile as readFile15, rename as rename2, writeFile as writeFile8 } from "fs/promises";
|
|
9814
10088
|
import path18 from "path";
|
|
9815
10089
|
var LocalCacheStore = class {
|
|
9816
10090
|
rootDir;
|
|
@@ -9822,7 +10096,7 @@ var LocalCacheStore = class {
|
|
|
9822
10096
|
}
|
|
9823
10097
|
async get(cacheKey) {
|
|
9824
10098
|
try {
|
|
9825
|
-
const buf = await
|
|
10099
|
+
const buf = await readFile15(this.filePath(cacheKey), "utf8");
|
|
9826
10100
|
return JSON.parse(buf);
|
|
9827
10101
|
} catch (e) {
|
|
9828
10102
|
if (e.code === "ENOENT") return null;
|
|
@@ -9920,6 +10194,7 @@ export {
|
|
|
9920
10194
|
runFailureCode,
|
|
9921
10195
|
AD_FORMAT_PLATFORMS,
|
|
9922
10196
|
platformFormats,
|
|
10197
|
+
resolveAdaptFormats,
|
|
9923
10198
|
withoutCastName,
|
|
9924
10199
|
frameRealismDirection,
|
|
9925
10200
|
SEEDANCE_DURATIONS,
|
|
@@ -9946,6 +10221,7 @@ export {
|
|
|
9946
10221
|
bestSupportedQuality,
|
|
9947
10222
|
nearestSupportedAspectRatio,
|
|
9948
10223
|
nearestSupportedImageSize,
|
|
10224
|
+
CREDITS_PER_USD,
|
|
9949
10225
|
estimateVideoCredits,
|
|
9950
10226
|
resolveConcurrency,
|
|
9951
10227
|
ulid,
|
|
@@ -9975,6 +10251,10 @@ export {
|
|
|
9975
10251
|
canonicalUrl,
|
|
9976
10252
|
coercePathUrlToUrl,
|
|
9977
10253
|
toModelSafeImage,
|
|
10254
|
+
loadCompositionMeta,
|
|
10255
|
+
readReservedAreas,
|
|
10256
|
+
reservedAreasOf,
|
|
10257
|
+
spaceVerdict,
|
|
9978
10258
|
catalogView,
|
|
9979
10259
|
BackendClient2,
|
|
9980
10260
|
Engine2 as Engine,
|
|
@@ -9986,4 +10266,4 @@ export {
|
|
|
9986
10266
|
defaultRegistry,
|
|
9987
10267
|
createEngineFromEnv
|
|
9988
10268
|
};
|
|
9989
|
-
//# sourceMappingURL=chunk-
|
|
10269
|
+
//# sourceMappingURL=chunk-Q3LMLQAI.js.map
|