pixelkiln 0.50.0 → 0.52.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 +358 -15
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +362 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +811 -82
- package/dist/index.d.ts +811 -82
- package/dist/index.js +360 -15
- package/dist/index.js.map +1 -1
- package/docs/ENDPOINTS.md +11 -0
- package/package.json +1 -1
- package/schema/manifest.schema.json +218 -2
- package/schema/recipe.schema.json +109 -1
- package/skills/pixelkiln/references/pixellab-roadmap.md +17 -9
- package/skills/pixelkiln/references/pixellab.md +59 -1
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"]);
|
|
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;
|
|
@@ -740,6 +741,14 @@ function tilesCost(tileSize, variations) {
|
|
|
740
741
|
if (px <= 2048) return 25;
|
|
741
742
|
return 40;
|
|
742
743
|
}
|
|
744
|
+
function terrainTileCount(transitionSize) {
|
|
745
|
+
return transitionSize === 1 ? 25 : 16;
|
|
746
|
+
}
|
|
747
|
+
function parseTerrainDescriptions(prompt) {
|
|
748
|
+
const parts = prompt.split(/\d+\s*\)\s*\./).map((part) => part.trim()).filter(Boolean);
|
|
749
|
+
if (parts.length < 2) return null;
|
|
750
|
+
return { lower: parts[0], upper: parts[1], transition: parts[2] };
|
|
751
|
+
}
|
|
743
752
|
var StyleImageSchema = z.object({
|
|
744
753
|
/** Path to a PNG/JPEG, relative to the manifest; the active provider validates limits. */
|
|
745
754
|
path: z.string()
|
|
@@ -1019,6 +1028,48 @@ var StyleObjectSchema = z.object({
|
|
|
1019
1028
|
* rather than inheriting.
|
|
1020
1029
|
*/
|
|
1021
1030
|
outlineMode: z.enum(["outline", "segmentation"]).optional(),
|
|
1031
|
+
/**
|
|
1032
|
+
* `terrain` generator only. Edge length of one tile in pixels. 16 or 32
|
|
1033
|
+
* work in both modes; 64 needs `terrainMode: "pro"`. The API default is
|
|
1034
|
+
* 16. See `parseTerrainDescriptions` for how an asset's `prompt` becomes
|
|
1035
|
+
* the lower/upper/transition terrain descriptions this endpoint wants.
|
|
1036
|
+
*/
|
|
1037
|
+
terrainTileSize: z.union([z.literal(16), z.literal(32), z.literal(64)]).optional(),
|
|
1038
|
+
/**
|
|
1039
|
+
* `terrain` generator only. `standard` is the classic Wang tileset
|
|
1040
|
+
* pipeline; `pro` is a newer corner-pair pipeline with its own shape
|
|
1041
|
+
* controls (`terrainSpreadX`, `terrainSlopeSize`, `terrainRaggedness`)
|
|
1042
|
+
* in place of `terrainShapeStyle`. The API default is `standard`.
|
|
1043
|
+
*/
|
|
1044
|
+
terrainMode: z.enum(["standard", "pro"]).optional(),
|
|
1045
|
+
/**
|
|
1046
|
+
* `terrain` generator only, `terrainMode: "standard"`. Procedural
|
|
1047
|
+
* boundary geometry: `square` or `round`, 16px or 32px tiles only.
|
|
1048
|
+
* Rejected together with `terrainMode: "pro"`, whose own shape controls
|
|
1049
|
+
* are `terrainSpreadX`/`terrainSlopeSize`/`terrainRaggedness`.
|
|
1050
|
+
*/
|
|
1051
|
+
terrainShapeStyle: z.enum(["square", "round"]).optional(),
|
|
1052
|
+
/** `terrain` generator only, `terrainMode: "pro"`. Boundary spread
|
|
1053
|
+
* between terrains (0 = steep, 1 = gradual). API default 0.5. */
|
|
1054
|
+
terrainSpreadX: z.number().min(0).max(1).optional(),
|
|
1055
|
+
/** `terrain` generator only, `terrainMode: "pro"`. Slope on the N/W/E
|
|
1056
|
+
* sides as a fraction of wall height. API default 0. */
|
|
1057
|
+
terrainSlopeSize: z.number().min(0).max(1).optional(),
|
|
1058
|
+
/** `terrain` generator only, `terrainMode: "pro"`. Terrain boundary
|
|
1059
|
+
* noise (0 = smooth, 1 = rough). API default 0. */
|
|
1060
|
+
terrainRaggedness: z.number().min(0).max(1).optional(),
|
|
1061
|
+
/**
|
|
1062
|
+
* `terrain` generator only. Visual height of the step between lower and
|
|
1063
|
+
* upper terrain. Without `terrainShapeStyle`, only 0, 0.25, 0.5, or 1 are
|
|
1064
|
+
* accepted (1 switches to the 25-tile cliff layout, where corners take a
|
|
1065
|
+
* third "transition" value); with it, any value from 0 to 1 works, but
|
|
1066
|
+
* above 0.5 switches to an extended 32-tile layout this adapter does not
|
|
1067
|
+
* model. API default 0.
|
|
1068
|
+
*/
|
|
1069
|
+
terrainTransitionSize: z.number().min(0).max(1).optional(),
|
|
1070
|
+
/** `terrain` generator only. Camera angle; the API default is `high
|
|
1071
|
+
* top-down`. */
|
|
1072
|
+
terrainView: z.enum(["low top-down", "high top-down"]).optional(),
|
|
1022
1073
|
/**
|
|
1023
1074
|
* `pixflux` only. Whether to strip the generated background.
|
|
1024
1075
|
*
|
|
@@ -1132,7 +1183,7 @@ var StyleSchema = StyleObjectSchema.refine((s) => !(s.tileFeature && s.styleImag
|
|
|
1132
1183
|
message: "tileFeature and styleImages cannot be combined; a connectable set derives its own tile geometry, so remove one or the other",
|
|
1133
1184
|
path: ["tileFeature"]
|
|
1134
1185
|
}).superRefine((style, ctx) => {
|
|
1135
|
-
if (style.quality && (style.generator === "tiles" || style.generator === "animation")) {
|
|
1186
|
+
if (style.quality && (style.generator === "tiles" || style.generator === "terrain" || style.generator === "animation")) {
|
|
1136
1187
|
ctx.addIssue({
|
|
1137
1188
|
code: z.ZodIssueCode.custom,
|
|
1138
1189
|
message: "quality profiles currently support single-image generators only",
|
|
@@ -1177,7 +1228,7 @@ var AssetSchema = z.object({
|
|
|
1177
1228
|
prompt: z.string().optional(),
|
|
1178
1229
|
/** Subdirectory under the style's outDir. Optional. */
|
|
1179
1230
|
category: z.string().optional(),
|
|
1180
|
-
/** Overrides the style default. `map`
|
|
1231
|
+
/** Overrides the style default. `map`, `pixflux`, and `imagePro` only. */
|
|
1181
1232
|
width: z.number().int().min(16).max(8192).optional(),
|
|
1182
1233
|
height: z.number().int().min(16).max(8192).optional(),
|
|
1183
1234
|
/** Overrides the style default. `1dir` generator only. */
|
|
@@ -5157,6 +5208,11 @@ var MapSubmitSchema = z2.object({
|
|
|
5157
5208
|
object_id: z2.string().min(1),
|
|
5158
5209
|
status: z2.string().default("processing")
|
|
5159
5210
|
}).passthrough();
|
|
5211
|
+
var UsageSchema = z2.object({
|
|
5212
|
+
type: z2.string().optional(),
|
|
5213
|
+
usd: z2.number().nullable().optional(),
|
|
5214
|
+
generations: z2.number().nullable().optional()
|
|
5215
|
+
}).passthrough().nullable().optional();
|
|
5160
5216
|
var TilesSubmitSchema = z2.object({
|
|
5161
5217
|
tile_id: z2.string().min(1),
|
|
5162
5218
|
background_job_id: z2.string().min(1),
|
|
@@ -5167,6 +5223,32 @@ var TilesProSchema = z2.object({
|
|
|
5167
5223
|
kind: z2.string().nullable().default(null),
|
|
5168
5224
|
tile_rules: z2.record(z2.unknown()).nullable().optional()
|
|
5169
5225
|
}).passthrough();
|
|
5226
|
+
var TilesetSubmitSchema = z2.object({
|
|
5227
|
+
tileset_id: z2.string().min(1),
|
|
5228
|
+
background_job_id: z2.string().min(1),
|
|
5229
|
+
status: z2.literal("processing").default("processing")
|
|
5230
|
+
}).passthrough();
|
|
5231
|
+
var TilesetTileSchema = z2.object({
|
|
5232
|
+
id: z2.string().min(1),
|
|
5233
|
+
name: z2.string(),
|
|
5234
|
+
image: z2.object({ base64: z2.string().min(1), format: z2.string().default("png") }).passthrough(),
|
|
5235
|
+
corners: z2.object({ NW: z2.string(), NE: z2.string(), SW: z2.string(), SE: z2.string() }).passthrough(),
|
|
5236
|
+
pattern_4x4: z2.object({
|
|
5237
|
+
row_0: z2.array(z2.number()),
|
|
5238
|
+
row_1: z2.array(z2.number()),
|
|
5239
|
+
row_2: z2.array(z2.number()),
|
|
5240
|
+
row_3: z2.array(z2.number())
|
|
5241
|
+
}).passthrough()
|
|
5242
|
+
}).passthrough();
|
|
5243
|
+
var TilesetGetSchema = z2.object({
|
|
5244
|
+
tileset: z2.object({
|
|
5245
|
+
total_tiles: z2.number().int().min(1),
|
|
5246
|
+
tile_size: z2.object({ width: z2.number().int().positive(), height: z2.number().int().positive() }).passthrough(),
|
|
5247
|
+
terrain_types: z2.array(z2.string()),
|
|
5248
|
+
tiles: z2.array(TilesetTileSchema).min(1)
|
|
5249
|
+
}).passthrough(),
|
|
5250
|
+
usage: UsageSchema
|
|
5251
|
+
}).passthrough();
|
|
5170
5252
|
var RevisionJobSubmitSchema = z2.object({
|
|
5171
5253
|
background_job_id: z2.string().min(1),
|
|
5172
5254
|
status: z2.string().default("processing")
|
|
@@ -5198,11 +5280,6 @@ var MapObjectSchema = z2.object({
|
|
|
5198
5280
|
var ObjectListSchema = z2.object({ objects: z2.array(PixelLabObjectSchema), total: z2.number().int().min(0) }).passthrough();
|
|
5199
5281
|
var PixfluxResponseSchema = z2.object({ image: z2.object({ base64: z2.string().min(1) }).passthrough(), usage: z2.unknown().optional() }).passthrough();
|
|
5200
5282
|
var SelectFramesSchema = z2.object({ created_object_ids: z2.array(z2.string()) }).passthrough();
|
|
5201
|
-
var UsageSchema = z2.object({
|
|
5202
|
-
type: z2.string().optional(),
|
|
5203
|
-
usd: z2.number().nullable().optional(),
|
|
5204
|
-
generations: z2.number().nullable().optional()
|
|
5205
|
-
}).passthrough().nullable().optional();
|
|
5206
5283
|
var CharacterSubmitSchema = z2.object({
|
|
5207
5284
|
background_job_id: z2.string().min(1),
|
|
5208
5285
|
character_id: z2.string().min(1),
|
|
@@ -5418,6 +5495,45 @@ var PixelLabClient = class {
|
|
|
5418
5495
|
"get tiles"
|
|
5419
5496
|
);
|
|
5420
5497
|
}
|
|
5498
|
+
/**
|
|
5499
|
+
* `/create-tileset`: two named terrain levels (`lower`/`upper`) and the
|
|
5500
|
+
* transition between them, laid out as a Wang corner set. Unlike
|
|
5501
|
+
* `/create-tiles-pro`, the descriptions are separate fields the API itself
|
|
5502
|
+
* places on the terrain vertex grid, not one prompt it splits by number.
|
|
5503
|
+
*/
|
|
5504
|
+
async createTileset(args) {
|
|
5505
|
+
const body = {
|
|
5506
|
+
lower_description: args.lowerDescription,
|
|
5507
|
+
upper_description: args.upperDescription
|
|
5508
|
+
};
|
|
5509
|
+
if (args.transitionDescription) body.transition_description = args.transitionDescription;
|
|
5510
|
+
if (args.tileSize != null) body.tile_size = { width: args.tileSize, height: args.tileSize };
|
|
5511
|
+
if (args.mode) body.mode = args.mode;
|
|
5512
|
+
if (args.shapeStyle) body.shape_style = args.shapeStyle;
|
|
5513
|
+
if (args.spreadX != null) body.spread_x = args.spreadX;
|
|
5514
|
+
if (args.slopeSize != null) body.slope_size = args.slopeSize;
|
|
5515
|
+
if (args.raggedness != null) body.raggedness = args.raggedness;
|
|
5516
|
+
if (args.transitionSize != null) body.transition_size = args.transitionSize;
|
|
5517
|
+
if (args.view) body.view = args.view;
|
|
5518
|
+
if (args.outline) body.outline = args.outline;
|
|
5519
|
+
if (args.shading) body.shading = args.shading;
|
|
5520
|
+
if (args.detail) body.detail = args.detail;
|
|
5521
|
+
if (args.seed != null) body.seed = args.seed;
|
|
5522
|
+
return validateResponse(
|
|
5523
|
+
TilesetSubmitSchema,
|
|
5524
|
+
await this.request("/create-tileset", { method: "POST", body: JSON.stringify(body) }),
|
|
5525
|
+
"create tileset"
|
|
5526
|
+
);
|
|
5527
|
+
}
|
|
5528
|
+
/** Throws PixelLabError(423) while the set is still drawing; see Tileset. */
|
|
5529
|
+
async getTileset(tilesetId) {
|
|
5530
|
+
const res = await validateResponse(
|
|
5531
|
+
TilesetGetSchema,
|
|
5532
|
+
await this.request(`/tilesets/${tilesetId}`),
|
|
5533
|
+
"get tileset"
|
|
5534
|
+
);
|
|
5535
|
+
return { tileset: res.tileset, usage: res.usage ?? null };
|
|
5536
|
+
}
|
|
5421
5537
|
/**
|
|
5422
5538
|
* Synchronous single-image generation. Returns the PNG inline rather than a
|
|
5423
5539
|
* job id, and is the only endpoint that honours a forced palette;
|
|
@@ -5718,6 +5834,34 @@ var PixelLabClient = class {
|
|
|
5718
5834
|
"edit-images-v2"
|
|
5719
5835
|
);
|
|
5720
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
|
+
}
|
|
5721
5865
|
async getBackgroundJob(jobId) {
|
|
5722
5866
|
const raw = await this.request(`/background-jobs/${encodeURIComponent(jobId)}`);
|
|
5723
5867
|
return validateResponse(BackgroundJobSchema, raw, "background-jobs/{id}");
|
|
@@ -5856,7 +6000,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5856
6000
|
return new _PixelLabProvider(new PixelLabClient("download-only"));
|
|
5857
6001
|
}
|
|
5858
6002
|
supports(generator) {
|
|
5859
|
-
return generator === "1dir" || generator === "map" || generator === "pixflux" || generator === "tiles" || generator === "character";
|
|
6003
|
+
return generator === "1dir" || generator === "map" || generator === "pixflux" || generator === "tiles" || generator === "terrain" || generator === "imagePro" || generator === "character";
|
|
5860
6004
|
}
|
|
5861
6005
|
/**
|
|
5862
6006
|
* `inpaint` (`/inpaint-v3`, a mask) and `image-to-image` (`/edit-images-v2`,
|
|
@@ -5892,7 +6036,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5892
6036
|
const height = spec.revision.sourceHeight ?? spec.height;
|
|
5893
6037
|
return { unit: "generations", amount: generationCost(width, height, "1dir"), candidates: 1 };
|
|
5894
6038
|
}
|
|
5895
|
-
if (spec.generator === "tiles") {
|
|
6039
|
+
if (spec.generator === "tiles" || spec.generator === "terrain") {
|
|
5896
6040
|
return { unit: "generations", amount: spec.cost, candidates: spec.candidates };
|
|
5897
6041
|
}
|
|
5898
6042
|
if (spec.generator === "character") {
|
|
@@ -5901,7 +6045,7 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5901
6045
|
return {
|
|
5902
6046
|
unit: "generations",
|
|
5903
6047
|
amount: generationCost(spec.width, spec.height, spec.generator),
|
|
5904
|
-
candidates: spec.generator === "1dir" ? candidateCount(spec.size) : 1
|
|
6048
|
+
candidates: spec.generator === "1dir" || spec.generator === "imagePro" ? candidateCount(spec.size) : 1
|
|
5905
6049
|
};
|
|
5906
6050
|
}
|
|
5907
6051
|
validate(spec, styleImages) {
|
|
@@ -5917,6 +6061,11 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5917
6061
|
if ((spec.generator === "map" || spec.generator === "pixflux") && (spec.width < 16 || spec.height < 16 || spec.width > 400 || spec.height > 400)) {
|
|
5918
6062
|
throw new Error(`PixelLab ${spec.generator} dimensions must be between 16 and 400 pixels`);
|
|
5919
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
|
+
}
|
|
5920
6069
|
if (spec.generator === "map") {
|
|
5921
6070
|
requirePixelLabOption("view", spec.view, ["low top-down", "high top-down", "side"]);
|
|
5922
6071
|
requirePixelLabOption("outline", spec.outline, [
|
|
@@ -5936,10 +6085,60 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
5936
6085
|
"high detail"
|
|
5937
6086
|
]);
|
|
5938
6087
|
}
|
|
6088
|
+
if (spec.generator === "terrain") {
|
|
6089
|
+
if (spec.outline) {
|
|
6090
|
+
requirePixelLabOption("outline", spec.outline, [
|
|
6091
|
+
"single color black outline",
|
|
6092
|
+
"single color outline",
|
|
6093
|
+
"selective outline",
|
|
6094
|
+
"lineless"
|
|
6095
|
+
]);
|
|
6096
|
+
}
|
|
6097
|
+
if (spec.shading) {
|
|
6098
|
+
requirePixelLabOption("shading", spec.shading, [
|
|
6099
|
+
"flat shading",
|
|
6100
|
+
"basic shading",
|
|
6101
|
+
"medium shading",
|
|
6102
|
+
"detailed shading",
|
|
6103
|
+
"highly detailed shading"
|
|
6104
|
+
]);
|
|
6105
|
+
}
|
|
6106
|
+
if (spec.detail) {
|
|
6107
|
+
requirePixelLabOption("detail", spec.detail, ["low detail", "medium detail", "highly detailed"]);
|
|
6108
|
+
}
|
|
6109
|
+
if (spec.terrainShapeStyle && spec.terrainMode === "pro") {
|
|
6110
|
+
throw new Error(
|
|
6111
|
+
"PixelLab terrain: terrainShapeStyle is standard-mode only; pro's own shape controls are terrainSpreadX/terrainSlopeSize/terrainRaggedness"
|
|
6112
|
+
);
|
|
6113
|
+
}
|
|
6114
|
+
if (spec.terrainTileSize === 64 && spec.terrainMode !== "pro") {
|
|
6115
|
+
throw new Error('PixelLab terrain: a 64px tile needs terrainMode: "pro"');
|
|
6116
|
+
}
|
|
6117
|
+
if (spec.terrainShapeStyle && spec.terrainTransitionSize != null && spec.terrainTransitionSize > 0.5) {
|
|
6118
|
+
throw new Error(
|
|
6119
|
+
"PixelLab terrain: terrainShapeStyle with terrainTransitionSize above 0.5 uses an extended 32-tile layout pixelkiln does not model; use 0, 0.25, or 0.5"
|
|
6120
|
+
);
|
|
6121
|
+
}
|
|
6122
|
+
if (!spec.terrainShapeStyle && spec.terrainTransitionSize != null && ![0, 0.25, 0.5, 1].includes(spec.terrainTransitionSize)) {
|
|
6123
|
+
throw new Error(
|
|
6124
|
+
"PixelLab terrain: terrainTransitionSize must be 0, 0.25, 0.5, or 1 unless terrainShapeStyle is set"
|
|
6125
|
+
);
|
|
6126
|
+
}
|
|
6127
|
+
}
|
|
5939
6128
|
if (spec.generator === "character") this.validateCharacter(spec, styleImages);
|
|
5940
6129
|
if ((spec.generator === "map" || spec.generator === "pixflux") && styleImages.length) {
|
|
5941
6130
|
throw new Error(`PixelLab ${spec.generator} does not support style images`);
|
|
5942
6131
|
}
|
|
6132
|
+
if (spec.generator === "terrain" && styleImages.length) {
|
|
6133
|
+
throw new Error(
|
|
6134
|
+
"PixelLab terrain does not support style images yet; use color_image/reference images directly against /create-tileset if this becomes a real need"
|
|
6135
|
+
);
|
|
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
|
+
}
|
|
5943
6142
|
for (const image of styleImages) {
|
|
5944
6143
|
if (image.width > 256 || image.height > 256) {
|
|
5945
6144
|
throw new Error(
|
|
@@ -6302,6 +6501,36 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
6302
6501
|
});
|
|
6303
6502
|
return { jobId: res2.tile_id, metadata: { backgroundJobId: res2.background_job_id } };
|
|
6304
6503
|
}
|
|
6504
|
+
if (spec.generator === "terrain") {
|
|
6505
|
+
const res2 = await this.client.createTileset({
|
|
6506
|
+
lowerDescription: spec.terrainLowerDescription,
|
|
6507
|
+
upperDescription: spec.terrainUpperDescription,
|
|
6508
|
+
transitionDescription: spec.terrainTransitionDescription,
|
|
6509
|
+
tileSize: spec.terrainTileSize,
|
|
6510
|
+
mode: spec.terrainMode,
|
|
6511
|
+
shapeStyle: spec.terrainShapeStyle,
|
|
6512
|
+
spreadX: spec.terrainSpreadX,
|
|
6513
|
+
slopeSize: spec.terrainSlopeSize,
|
|
6514
|
+
raggedness: spec.terrainRaggedness,
|
|
6515
|
+
transitionSize: spec.terrainTransitionSize,
|
|
6516
|
+
view: spec.terrainView,
|
|
6517
|
+
outline: spec.outline,
|
|
6518
|
+
shading: spec.shading,
|
|
6519
|
+
detail: spec.detail,
|
|
6520
|
+
seed: spec.seed
|
|
6521
|
+
});
|
|
6522
|
+
return { jobId: res2.tileset_id, metadata: { backgroundJobId: res2.background_job_id } };
|
|
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
|
+
}
|
|
6305
6534
|
if (spec.generator === "1dir") {
|
|
6306
6535
|
const res2 = await this.client.create1Direction({
|
|
6307
6536
|
description: spec.prompt,
|
|
@@ -6407,6 +6636,8 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
6407
6636
|
}
|
|
6408
6637
|
if (generator === "map") return this.pollMap(jobId, context);
|
|
6409
6638
|
if (generator === "tiles") return this.pollTiles(jobId, Boolean(context?.tileFeature), context);
|
|
6639
|
+
if (generator === "terrain") return this.pollTerrain(jobId, context);
|
|
6640
|
+
if (generator === "imagePro") return this.pollImagePro(jobId);
|
|
6410
6641
|
if (generator === "character") return this.pollCharacter(jobId, context);
|
|
6411
6642
|
const backgroundJobId = context?.metadata?.backgroundJobId;
|
|
6412
6643
|
const obj = await this.client.getObject(jobId);
|
|
@@ -6477,6 +6708,40 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
6477
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`
|
|
6478
6709
|
};
|
|
6479
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
|
+
}
|
|
6480
6745
|
/**
|
|
6481
6746
|
* Map objects need their own path because the `/map-objects/{id}` record is
|
|
6482
6747
|
* deleted upstream roughly 8 hours after creation while the image survives in
|
|
@@ -6545,6 +6810,43 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
6545
6810
|
throw err;
|
|
6546
6811
|
}
|
|
6547
6812
|
}
|
|
6813
|
+
/**
|
|
6814
|
+
* `/create-tileset` reports progress the same way `tiles` does (423 while
|
|
6815
|
+
* drawing, 200 once finished), but each tile's image comes back embedded
|
|
6816
|
+
* as base64 rather than a `storage_urls` link, so it is decoded straight
|
|
6817
|
+
* to a cache file the same way a revision result is (`pollRevision`). A
|
|
6818
|
+
* terrain set is always a connectable Wang tileset, never independent
|
|
6819
|
+
* candidates to review, so this goes straight to "ready" like a connectable
|
|
6820
|
+
* `tiles` set does.
|
|
6821
|
+
*/
|
|
6822
|
+
async pollTerrain(tilesetId, context) {
|
|
6823
|
+
try {
|
|
6824
|
+
const { tileset, usage } = await this.client.getTileset(tilesetId);
|
|
6825
|
+
const backgroundJobId = context?.metadata?.backgroundJobId;
|
|
6826
|
+
const billed = billedFromUsage(usage) ?? await this.billedForJob(backgroundJobId);
|
|
6827
|
+
const sources = [];
|
|
6828
|
+
const terrainTiles = [];
|
|
6829
|
+
for (const [index, tile] of tileset.tiles.entries()) {
|
|
6830
|
+
const slug = tile.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "tile";
|
|
6831
|
+
const role = `tile-${String(index).padStart(2, "0")}-${slug}`;
|
|
6832
|
+
const file = path12.join(_PixelLabProvider.cacheDir(), `${tilesetId}-${index}.png`);
|
|
6833
|
+
writeFileSync(file, Buffer.from(tile.image.base64, "base64"));
|
|
6834
|
+
sources.push({ url: `file://${file}`, role });
|
|
6835
|
+
terrainTiles.push({ role, corners: tile.corners, pattern4x4: tile.pattern_4x4 });
|
|
6836
|
+
}
|
|
6837
|
+
return {
|
|
6838
|
+
status: "ready",
|
|
6839
|
+
objectId: tilesetId,
|
|
6840
|
+
sourceUrl: sources[0]?.url ?? null,
|
|
6841
|
+
sources,
|
|
6842
|
+
metadata: { terrainTypes: tileset.terrain_types, terrainTiles },
|
|
6843
|
+
billed
|
|
6844
|
+
};
|
|
6845
|
+
} catch (err) {
|
|
6846
|
+
if (err instanceof PixelLabError && err.status === 423) return { status: "processing" };
|
|
6847
|
+
throw err;
|
|
6848
|
+
}
|
|
6849
|
+
}
|
|
6548
6850
|
async selectCandidate(jobId, index, commonTag, generator) {
|
|
6549
6851
|
if (generator === "tiles") {
|
|
6550
6852
|
const set = await this.client.getTilesPro(jobId);
|
|
@@ -6552,6 +6854,11 @@ var PixelLabProvider = class _PixelLabProvider {
|
|
|
6552
6854
|
if (!url) throw new Error(`tiles job ${jobId} has no variation at index ${index}`);
|
|
6553
6855
|
return { objectId: `${jobId}#${index}`, sourceUrl: url };
|
|
6554
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
|
+
}
|
|
6555
6862
|
const promoted = await this.client.selectFrames(jobId, [index], commonTag);
|
|
6556
6863
|
const objectId = promoted.created_object_ids?.[0];
|
|
6557
6864
|
if (!objectId) {
|
|
@@ -7895,6 +8202,10 @@ async function resolveSpecs(loaded, filter) {
|
|
|
7895
8202
|
size = asset.state?.canvas ? Math.max(asset.state.canvas.width, asset.state.canvas.height) : asset.size ?? style.size ?? 64;
|
|
7896
8203
|
width = asset.state?.canvas?.width ?? size;
|
|
7897
8204
|
height = asset.state?.canvas?.height ?? size;
|
|
8205
|
+
} else if (generator === "terrain") {
|
|
8206
|
+
size = style.terrainTileSize ?? 16;
|
|
8207
|
+
width = size;
|
|
8208
|
+
height = size;
|
|
7898
8209
|
} else {
|
|
7899
8210
|
width = asset.width ?? style.size ?? 64;
|
|
7900
8211
|
height = asset.height ?? style.size ?? 64;
|
|
@@ -7907,12 +8218,33 @@ async function resolveSpecs(loaded, filter) {
|
|
|
7907
8218
|
}
|
|
7908
8219
|
const characterKind = asset.animation ? "animation" : asset.state ? "state" : "base";
|
|
7909
8220
|
const subject = asset.promptByStyle[styleId] ?? asset.prompt ?? "";
|
|
7910
|
-
|
|
8221
|
+
let terrainLowerDescription;
|
|
8222
|
+
let terrainUpperDescription;
|
|
8223
|
+
let terrainTransitionDescription;
|
|
8224
|
+
let prompt;
|
|
8225
|
+
if (generator === "terrain") {
|
|
8226
|
+
const parsed = parseTerrainDescriptions(subject);
|
|
8227
|
+
if (!parsed) {
|
|
8228
|
+
throw new Error(
|
|
8229
|
+
`assets.${assetId}: terrain needs "1). <lower terrain> 2). <upper terrain>" in its prompt, optionally followed by "3). <transition>"`
|
|
8230
|
+
);
|
|
8231
|
+
}
|
|
8232
|
+
const wrap = (text) => [style.promptPrefix, text, style.promptSuffix].map((p) => p.trim()).filter(Boolean).join(", ");
|
|
8233
|
+
terrainLowerDescription = wrap(parsed.lower);
|
|
8234
|
+
terrainUpperDescription = wrap(parsed.upper);
|
|
8235
|
+
terrainTransitionDescription = parsed.transition ? wrap(parsed.transition) : void 0;
|
|
8236
|
+
prompt = subject.trim();
|
|
8237
|
+
} else if (generator === "character" && characterKind !== "base") {
|
|
8238
|
+
prompt = subject.trim();
|
|
8239
|
+
} else {
|
|
8240
|
+
prompt = [style.promptPrefix, subject, style.promptSuffix].map((p) => p.trim()).filter(Boolean).join(", ");
|
|
8241
|
+
}
|
|
7911
8242
|
const relFile = asset.file ?? path13.join(asset.category ?? "", `${assetId}.png`);
|
|
7912
8243
|
const outFile = path13.resolve(root, style.outDir, relFile);
|
|
7913
8244
|
const qualityOutFile = style.quality ? path13.resolve(root, style.quality.outDir, pngPath(relFile)) : void 0;
|
|
7914
8245
|
const tileSize = generator === "tiles" ? size : style.tileSize ?? 32;
|
|
7915
8246
|
const tileVariations = tileFeatureOutputCount(generator === "tiles" ? style.tileFeature : void 0) ?? tileVariationCount(countNumberedDescriptions(prompt));
|
|
8247
|
+
const terrainTiles = generator === "terrain" ? terrainTileCount(style.terrainTransitionSize) : 0;
|
|
7916
8248
|
const base = {
|
|
7917
8249
|
styleId,
|
|
7918
8250
|
assetId,
|
|
@@ -7949,10 +8281,21 @@ async function resolveSpecs(loaded, filter) {
|
|
|
7949
8281
|
buildingFloor2Description: generator === "tiles" ? style.buildingFloor2Description : void 0,
|
|
7950
8282
|
buildingWallAngle: generator === "tiles" ? style.buildingWallAngle : void 0,
|
|
7951
8283
|
outlineMode: generator === "tiles" ? style.outlineMode : void 0,
|
|
8284
|
+
terrainLowerDescription: generator === "terrain" ? terrainLowerDescription : void 0,
|
|
8285
|
+
terrainUpperDescription: generator === "terrain" ? terrainUpperDescription : void 0,
|
|
8286
|
+
terrainTransitionDescription: generator === "terrain" ? terrainTransitionDescription : void 0,
|
|
8287
|
+
terrainTileSize: generator === "terrain" ? size : void 0,
|
|
8288
|
+
terrainMode: generator === "terrain" ? style.terrainMode : void 0,
|
|
8289
|
+
terrainShapeStyle: generator === "terrain" ? style.terrainShapeStyle : void 0,
|
|
8290
|
+
terrainSpreadX: generator === "terrain" ? style.terrainSpreadX : void 0,
|
|
8291
|
+
terrainSlopeSize: generator === "terrain" ? style.terrainSlopeSize : void 0,
|
|
8292
|
+
terrainRaggedness: generator === "terrain" ? style.terrainRaggedness : void 0,
|
|
8293
|
+
terrainTransitionSize: generator === "terrain" ? style.terrainTransitionSize : void 0,
|
|
8294
|
+
terrainView: generator === "terrain" ? style.terrainView : void 0,
|
|
7952
8295
|
...generator === "character" ? { character: await resolveCharacterShape(asset, style, characterKind, { root, load: loadStyleImage }) } : {},
|
|
7953
|
-
cost: generator === "tiles" ? tilesCost(tileSize, tileVariations) : generationCost(width, height, generator),
|
|
8296
|
+
cost: generator === "tiles" ? tilesCost(tileSize, tileVariations) : generator === "terrain" ? tilesCost(size, terrainTiles) : generationCost(width, height, generator),
|
|
7954
8297
|
costUnit: "generations",
|
|
7955
|
-
candidates: generator === "tiles" ? tileVariations : generator === "1dir" ? candidateCount(size) : 1
|
|
8298
|
+
candidates: generator === "tiles" ? tileVariations : generator === "terrain" ? terrainTiles : generator === "1dir" || generator === "imagePro" ? candidateCount(size) : 1
|
|
7956
8299
|
};
|
|
7957
8300
|
const tags = [
|
|
7958
8301
|
.../* @__PURE__ */ new Set([
|
|
@@ -8000,7 +8343,7 @@ async function resolveSpecs(loaded, filter) {
|
|
|
8000
8343
|
if (asset.mirror) {
|
|
8001
8344
|
if (asset.mirror === assetId) throw new Error(`assets.${assetId}: an asset cannot mirror itself`);
|
|
8002
8345
|
const sourceSpec = await finalize(asset.mirror);
|
|
8003
|
-
if (sourceSpec.generator === "tiles") {
|
|
8346
|
+
if (sourceSpec.generator === "tiles" || sourceSpec.generator === "terrain") {
|
|
8004
8347
|
throw new Error(`assets.${assetId}: a tile set cannot be mirrored; its edges carry meaning`);
|
|
8005
8348
|
}
|
|
8006
8349
|
if (sourceSpec.character?.kind === "animation") {
|