@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.
@@ -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 spec = MODEL_REGISTRY[kind]?.[model];
2349
- return spec?.optional_inputs?.find((input) => input.kind === inputKind) ?? spec?.inputs.find((input) => input.kind === inputKind);
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(spec, params, model) {
2422
+ function checkRequiredParams(spec2, params, model) {
2419
2423
  const errors = [];
2420
- for (const req of spec.required) {
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(spec, params, model) {
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 = spec.params[key];
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 spec = MODEL_REGISTRY[nodeType][model];
2452
- if (!spec) {
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(spec, params, model), ...checkParamValues(spec, params, model)];
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 spec = MODEL_REGISTRY[nodeType][model];
2471
- if (!spec) {
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 spec.inputs) {
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(spec, params) {
3690
- if (!spec) return {};
3691
- return typeof spec === "function" ? spec(params) : spec;
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 spec = MODEL_REGISTRY[n.type][model];
3770
- if (!spec) return [];
3771
- return Object.entries(spec.params).flatMap(
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 spec = MODEL_REGISTRY.video_generate[model];
4715
- const durations = spec?.params?.duration?.enum;
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 = def.cost ? def.cost({ params: parsedParams }).credits : 0;
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(spec) {
6380
+ function delegated(spec2) {
6370
6381
  return {
6371
- ...spec,
6382
+ ...spec2,
6372
6383
  location: "remote",
6373
6384
  execute: async ({ inputs, params, ctx }) => {
6374
6385
  return await callBackendExec({
6375
- nodeType: spec.id,
6376
- nodeVersion: spec.version,
6386
+ nodeType: spec2.id,
6387
+ nodeVersion: spec2.version,
6377
6388
  params,
6378
6389
  inputs,
6379
- outputKinds: resolveOutputKinds(spec.outputKinds, params),
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: sharp } = await import("sharp");
6753
- const base = await sharp(bytes).metadata();
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 sharp(bytes, { density }).png({ force: true, palette: false }).toBuffer();
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: sharp } = await import("sharp");
6767
- const read = () => sharp(bytes, { animated: true });
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: sharp } = await import("sharp");
6819
+ const { default: sharp2 } = await import("sharp");
6806
6820
  let png;
6807
6821
  let format;
6808
6822
  try {
6809
- const img = sharp(bytes);
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, spec] of Object.entries(outputs)) {
7171
- const ext = spec.ext.startsWith(".") ? spec.ext : `.${spec.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: spec.kind,
7290
- mime: mimeForExt(spec.ext)
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, spec] of Object.entries(meta.params)) {
7879
- shape[name] = buildParamFieldSchema(name, spec);
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, spec) {
7884
- switch (spec.kind) {
7897
+ function buildParamFieldSchema(name, spec2) {
7898
+ switch (spec2.kind) {
7885
7899
  case "string": {
7886
- const s = spec.enum && spec.enum.length > 0 ? z10.enum(spec.enum) : z10.string();
7887
- return finalize(s, spec.default, spec.required);
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 (spec.min !== void 0) s = s.min(spec.min);
7892
- if (spec.max !== void 0) s = s.max(spec.max);
7893
- return finalize(s, spec.default, spec.required);
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 (spec.min !== void 0) s = s.min(spec.min);
7898
- if (spec.max !== void 0) s = s.max(spec.max);
7899
- return finalize(s, spec.default, spec.required);
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(), spec.default, spec.required);
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, spec.default, spec.required);
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())]), spec.default, spec.required);
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
- spec.default,
7915
- spec.required
7928
+ spec2.default,
7929
+ spec2.required
7916
7930
  );
7917
7931
  default: {
7918
- const _exhaustive = spec;
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 spec = meta.inputs[key];
8509
- if (!spec) continue;
8510
- const filename = spec.staged_as ?? defaultFilenameForInput(key, spec.kind);
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 ${spec.kind} \u2192 ${filename}`);
8513
- if (spec.kind === "video" && primaryDuration === null) {
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, spec] of Object.entries(meta.inputs)) {
8529
- if (spec.required && !providedKeys.has(key)) {
8530
- throw new Error(`${nodeId}: inputs.${key} is required (kind=${spec.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 spec = meta.inputs[key];
8535
- if (!spec) continue;
8536
- if (ref.kind !== spec.kind) {
8537
- throw new Error(`${nodeId}: inputs.${key} expected kind "${spec.kind}", got "${ref.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 spec = meta.params[name];
8545
- if (spec?.kind === "image") {
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 spec = meta.inputs[key];
8767
- if (!spec) continue;
8768
- const filename = spec.staged_as ?? defaultFilenameForInput(key, spec.kind);
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 ${spec.kind} \u2192 ${filename}`);
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 spec = meta.params[name];
8777
- if (spec?.kind === "image") {
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
- cost: () => ({ credits: 5, seconds_estimate: 10 })
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 imageSelectNode = delegated({
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: z24.object({ images: z24.array(ImageRef).min(2) }).loose(),
9203
- params: z24.object({
9204
- model: z24.enum(IMAGE_SELECT_MODELS),
9205
- prompt: z24.string().min(1),
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 readFile13, rm as rm6 } from "fs/promises";
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 readFile13(audioPath);
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 readFile14, rename as rename2, writeFile as writeFile8 } from "fs/promises";
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 readFile14(this.filePath(cacheKey), "utf8");
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-JLKUHDBB.js.map
10269
+ //# sourceMappingURL=chunk-Q3LMLQAI.js.map