pixelkiln 0.51.0 → 0.53.0

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/dist/cli.js CHANGED
@@ -707,7 +707,7 @@ import path2 from "path";
707
707
  // src/types.ts
708
708
  import { z } from "zod";
709
709
  var MediaTypeSchema = z.enum(["image/png", "image/gif"]);
710
- var GeneratorSchema = z.enum(["1dir", "map", "pixflux", "tiles", "animation", "frames", "character", "terrain"]);
710
+ var GeneratorSchema = z.enum(["1dir", "map", "pixflux", "tiles", "animation", "frames", "character", "terrain", "imagePro"]);
711
711
  var GridConfidenceSchema = z.enum(["low", "medium", "high"]);
712
712
  var RevisionModeSchema = z.enum(["image-to-image", "inpaint", "outpaint"]);
713
713
  function tileVariationCount(descriptions) {
@@ -729,6 +729,7 @@ function candidateCount(size) {
729
729
  }
730
730
  function generationCost(width, height, generator = "map") {
731
731
  if (generator === "map" || generator === "pixflux") return 1;
732
+ if (generator === "imagePro") return 40;
732
733
  const px = width * height;
733
734
  if (px <= 1024) return 20;
734
735
  if (px <= 2048) return 25;
@@ -1227,7 +1228,7 @@ var AssetSchema = z.object({
1227
1228
  prompt: z.string().optional(),
1228
1229
  /** Subdirectory under the style's outDir. Optional. */
1229
1230
  category: z.string().optional(),
1230
- /** Overrides the style default. `map` generator only. */
1231
+ /** Overrides the style default. `map`, `pixflux`, and `imagePro` only. */
1231
1232
  width: z.number().int().min(16).max(8192).optional(),
1232
1233
  height: z.number().int().min(16).max(8192).optional(),
1233
1234
  /** Overrides the style default. `1dir` generator only. */
@@ -5833,6 +5834,34 @@ var PixelLabClient = class {
5833
5834
  "edit-images-v2"
5834
5835
  );
5835
5836
  }
5837
+ /**
5838
+ * PixelLab's Pro image tier, `/generate-image-v2`: a flat 40 generations
5839
+ * (docs/ENDPOINTS.md, "Single-image generators, measured") for real style
5840
+ * transfer and non-square canvases up to 792x688, where `pixflux` is
5841
+ * limited to 400x400 and no style reference. Like a revision, this hands
5842
+ * back a plain background job with no resource of its own; unlike a
5843
+ * revision, one call returns several candidate images to pick from (the
5844
+ * same 4/16/64-by-size tiering as `1dir`), not a single result, and its
5845
+ * completed shape has not been exercised live yet — `pollImagePro` in
5846
+ * pixellab.ts fails loudly rather than guess if `last_response.images`
5847
+ * turns out not to be the real field name.
5848
+ *
5849
+ * Reference images and a style image (`reference_images`, `style_image` +
5850
+ * `style_options`) exist on this endpoint but are not modeled here yet.
5851
+ */
5852
+ async createImagePro(args) {
5853
+ const body = {
5854
+ description: args.description,
5855
+ image_size: { width: args.width, height: args.height }
5856
+ };
5857
+ if (args.noBackground != null) body.no_background = args.noBackground;
5858
+ if (args.seed != null) body.seed = args.seed;
5859
+ return validateResponse(
5860
+ RevisionJobSubmitSchema,
5861
+ await this.request("/generate-image-v2", { method: "POST", body: JSON.stringify(body) }),
5862
+ "generate-image-v2"
5863
+ );
5864
+ }
5836
5865
  async getBackgroundJob(jobId) {
5837
5866
  const raw = await this.request(`/background-jobs/${encodeURIComponent(jobId)}`);
5838
5867
  return validateResponse(BackgroundJobSchema, raw, "background-jobs/{id}");
@@ -5971,7 +6000,7 @@ var PixelLabProvider = class _PixelLabProvider {
5971
6000
  return new _PixelLabProvider(new PixelLabClient("download-only"));
5972
6001
  }
5973
6002
  supports(generator) {
5974
- return generator === "1dir" || generator === "map" || generator === "pixflux" || generator === "tiles" || generator === "terrain" || generator === "character";
6003
+ return generator === "1dir" || generator === "map" || generator === "pixflux" || generator === "tiles" || generator === "terrain" || generator === "imagePro" || generator === "character";
5975
6004
  }
5976
6005
  /**
5977
6006
  * `inpaint` (`/inpaint-v3`, a mask) and `image-to-image` (`/edit-images-v2`,
@@ -6016,7 +6045,7 @@ var PixelLabProvider = class _PixelLabProvider {
6016
6045
  return {
6017
6046
  unit: "generations",
6018
6047
  amount: generationCost(spec.width, spec.height, spec.generator),
6019
- candidates: spec.generator === "1dir" ? candidateCount(spec.size) : 1
6048
+ candidates: spec.generator === "1dir" || spec.generator === "imagePro" ? candidateCount(spec.size) : 1
6020
6049
  };
6021
6050
  }
6022
6051
  validate(spec, styleImages) {
@@ -6032,6 +6061,11 @@ var PixelLabProvider = class _PixelLabProvider {
6032
6061
  if ((spec.generator === "map" || spec.generator === "pixflux") && (spec.width < 16 || spec.height < 16 || spec.width > 400 || spec.height > 400)) {
6033
6062
  throw new Error(`PixelLab ${spec.generator} dimensions must be between 16 and 400 pixels`);
6034
6063
  }
6064
+ if (spec.generator === "imagePro" && (spec.width < 16 || spec.height < 16 || spec.width > 792 || spec.height > 688)) {
6065
+ throw new Error(
6066
+ `PixelLab imagePro is ${spec.width}x${spec.height}; the API takes 16 to 792 wide and 16 to 688 tall (the exact ceiling also depends on aspect ratio)`
6067
+ );
6068
+ }
6035
6069
  if (spec.generator === "map") {
6036
6070
  requirePixelLabOption("view", spec.view, ["low top-down", "high top-down", "side"]);
6037
6071
  requirePixelLabOption("outline", spec.outline, [
@@ -6100,6 +6134,11 @@ var PixelLabProvider = class _PixelLabProvider {
6100
6134
  "PixelLab terrain does not support style images yet; use color_image/reference images directly against /create-tileset if this becomes a real need"
6101
6135
  );
6102
6136
  }
6137
+ if (spec.generator === "imagePro" && styleImages.length) {
6138
+ throw new Error(
6139
+ "PixelLab imagePro does not support style images yet; /generate-image-v2's own reference_images and style_image are not modeled here"
6140
+ );
6141
+ }
6103
6142
  for (const image of styleImages) {
6104
6143
  if (image.width > 256 || image.height > 256) {
6105
6144
  throw new Error(
@@ -6482,6 +6521,16 @@ var PixelLabProvider = class _PixelLabProvider {
6482
6521
  });
6483
6522
  return { jobId: res2.tileset_id, metadata: { backgroundJobId: res2.background_job_id } };
6484
6523
  }
6524
+ if (spec.generator === "imagePro") {
6525
+ const res2 = await this.client.createImagePro({
6526
+ description: spec.prompt,
6527
+ width: spec.width,
6528
+ height: spec.height,
6529
+ noBackground: spec.noBackground,
6530
+ seed: spec.seed
6531
+ });
6532
+ return { jobId: res2.background_job_id };
6533
+ }
6485
6534
  if (spec.generator === "1dir") {
6486
6535
  const res2 = await this.client.create1Direction({
6487
6536
  description: spec.prompt,
@@ -6588,6 +6637,7 @@ var PixelLabProvider = class _PixelLabProvider {
6588
6637
  if (generator === "map") return this.pollMap(jobId, context);
6589
6638
  if (generator === "tiles") return this.pollTiles(jobId, Boolean(context?.tileFeature), context);
6590
6639
  if (generator === "terrain") return this.pollTerrain(jobId, context);
6640
+ if (generator === "imagePro") return this.pollImagePro(jobId);
6591
6641
  if (generator === "character") return this.pollCharacter(jobId, context);
6592
6642
  const backgroundJobId = context?.metadata?.backgroundJobId;
6593
6643
  const obj = await this.client.getObject(jobId);
@@ -6658,6 +6708,40 @@ var PixelLabProvider = class _PixelLabProvider {
6658
6708
  error: `Invalid PixelLab response for revision job ${jobId}: completed with no recognized image field (got: ${Object.keys(done).join(", ") || "no keys"}); update pollRevision in src/providers/pixellab.ts with the real shape`
6659
6709
  };
6660
6710
  }
6711
+ /**
6712
+ * `/generate-image-v2` hands back a plain background job, the same as a
6713
+ * revision, but a completed one carries several candidate images to
6714
+ * review rather than a single result: the Python client's own usage
6715
+ * sample reads `response.images`, and `edit-images-v2` (a sibling Pro
6716
+ * endpoint) confirmed live that its own array lands at
6717
+ * `last_response.images` with each entry `extractBase64`-shaped, so this
6718
+ * assumes the same field name and shape here. That assumption is
6719
+ * UNVERIFIED for this specific endpoint — no live call has been made to
6720
+ * it — so an unrecognized response still fails loudly rather than
6721
+ * guessing, exactly like pollRevision.
6722
+ */
6723
+ async pollImagePro(jobId) {
6724
+ const job = await this.client.getBackgroundJob(jobId);
6725
+ if (job.status === "failed") return { status: "failed", error: "imagePro job failed upstream" };
6726
+ if (job.status !== "completed") return { status: "processing" };
6727
+ const billed = billedFromUsage(job.usage);
6728
+ const done = job.last_response ?? {};
6729
+ if (Array.isArray(done.images) && done.images.length) {
6730
+ const urls = [];
6731
+ for (const [index, candidate] of done.images.entries()) {
6732
+ const base64 = extractBase64(candidate);
6733
+ if (!base64) continue;
6734
+ const file = path12.join(_PixelLabProvider.cacheDir(), `${jobId}-${index}.png`);
6735
+ writeFileSync(file, Buffer.from(base64, "base64"));
6736
+ urls.push(`file://${file}`);
6737
+ }
6738
+ if (urls.length) return { status: "review", candidateUrls: urls, billed };
6739
+ }
6740
+ return {
6741
+ status: "failed",
6742
+ error: `Invalid PixelLab response for imagePro job ${jobId}: completed with no recognized images array (got: ${Object.keys(done).join(", ") || "no keys"}); this endpoint's completed shape has not been exercised live yet -- update pollImagePro in src/providers/pixellab.ts with the real shape`
6743
+ };
6744
+ }
6661
6745
  /**
6662
6746
  * Map objects need their own path because the `/map-objects/{id}` record is
6663
6747
  * deleted upstream roughly 8 hours after creation while the image survives in
@@ -6770,6 +6854,11 @@ var PixelLabProvider = class _PixelLabProvider {
6770
6854
  if (!url) throw new Error(`tiles job ${jobId} has no variation at index ${index}`);
6771
6855
  return { objectId: `${jobId}#${index}`, sourceUrl: url };
6772
6856
  }
6857
+ if (generator === "imagePro") {
6858
+ const file = path12.join(_PixelLabProvider.cacheDir(), `${jobId}-${index}.png`);
6859
+ if (!existsSync7(file)) throw new Error(`imagePro job ${jobId} has no cached candidate at index ${index}`);
6860
+ return { objectId: `${jobId}#${index}`, sourceUrl: `file://${file}` };
6861
+ }
6773
6862
  const promoted = await this.client.selectFrames(jobId, [index], commonTag);
6774
6863
  const objectId = promoted.created_object_ids?.[0];
6775
6864
  if (!objectId) {
@@ -8206,7 +8295,7 @@ async function resolveSpecs(loaded, filter) {
8206
8295
  ...generator === "character" ? { character: await resolveCharacterShape(asset, style, characterKind, { root, load: loadStyleImage }) } : {},
8207
8296
  cost: generator === "tiles" ? tilesCost(tileSize, tileVariations) : generator === "terrain" ? tilesCost(size, terrainTiles) : generationCost(width, height, generator),
8208
8297
  costUnit: "generations",
8209
- candidates: generator === "tiles" ? tileVariations : generator === "terrain" ? terrainTiles : generator === "1dir" ? candidateCount(size) : 1
8298
+ candidates: generator === "tiles" ? tileVariations : generator === "terrain" ? terrainTiles : generator === "1dir" || generator === "imagePro" ? candidateCount(size) : 1
8210
8299
  };
8211
8300
  const tags = [
8212
8301
  .../* @__PURE__ */ new Set([
@@ -10666,7 +10755,19 @@ var NewAssetSchema = z5.object({
10666
10755
  size: z5.number().int().optional(),
10667
10756
  tags: z5.array(z5.string()).optional(),
10668
10757
  /** Restrict to these styles; empty or omitted means every style. */
10669
- styles: z5.array(z5.string().min(1)).optional()
10758
+ styles: z5.array(z5.string().min(1)).optional(),
10759
+ /**
10760
+ * A controlled regeneration of another asset in the same manifest,
10761
+ * instead of a fresh generation. `inpaint` needs a mask upload the
10762
+ * gallery does not offer yet, so only `image-to-image` can be created
10763
+ * here; hand-edit the manifest for the other modes.
10764
+ */
10765
+ revision: z5.object({
10766
+ mode: z5.literal("image-to-image"),
10767
+ /** Asset id of the parent this revises; must already exist. */
10768
+ from: z5.string().min(1),
10769
+ strength: z5.number().min(0).max(1).optional()
10770
+ }).strict().optional()
10670
10771
  }).strict();
10671
10772
  var CANDIDATE_OPTION = {
10672
10773
  retrodiffusion: "numImages",
@@ -10832,6 +10933,9 @@ function applyEdit(raw, edit) {
10832
10933
  }
10833
10934
  asset2.styles = styles;
10834
10935
  }
10936
+ if (edit.asset.revision && !Object.hasOwn(raw.assets, edit.asset.revision.from)) {
10937
+ throw new ManifestEditError(`revision parent "${edit.asset.revision.from}" is not declared by the manifest`);
10938
+ }
10835
10939
  raw.assets[edit.assetId] = asset2;
10836
10940
  return;
10837
10941
  }