Preserve Replicate compatibility when shared defaults leak across providers
Addressed the new PR review findings by teaching baoyu-imagine to track where aspect-ratio defaults came from, mirroring the earlier imageSize fix, so unsupported Replicate models can still run prompt-only requests when the value was inherited from shared config. Also corrected Seedream 4.5 custom size encoding to use the API's custom width/height schema instead of sending literal WxH strings. Constraint: Shared EXTEND defaults still need to apply globally for providers that support them Constraint: Seedream 4.5 custom sizes must follow Replicate's documented custom size schema Rejected: Ignore all aspect ratios for unknown Replicate models | would hide explicit unsupported CLI/task input Rejected: Keep Seedream custom sizes as literal strings | validated locally but fails against the provider API Confidence: high Scope-risk: narrow Reversibility: clean Directive: Any future inherited-default validation for provider-specific flags should record the source explicitly before rejecting it Tested: node --import tsx --test skills/baoyu-imagine/scripts/main.test.ts skills/baoyu-imagine/scripts/providers/replicate.test.ts Tested: npm test Not-tested: Live Replicate API calls for Seedream 4.5 custom-size requests
This commit is contained in:
parent
ed401cc7a5
commit
56e72e8c34
|
|
@ -28,6 +28,7 @@ function makeArgs(overrides: Partial<CliArgs> = {}): CliArgs {
|
||||||
provider: null,
|
provider: null,
|
||||||
model: null,
|
model: null,
|
||||||
aspectRatio: null,
|
aspectRatio: null,
|
||||||
|
aspectRatioSource: null,
|
||||||
size: null,
|
size: null,
|
||||||
quality: null,
|
quality: null,
|
||||||
imageSize: null,
|
imageSize: null,
|
||||||
|
|
@ -98,6 +99,7 @@ test("parseArgs parses the main baoyu-imagine CLI flags", () => {
|
||||||
assert.equal(args.imagePath, "out/hero");
|
assert.equal(args.imagePath, "out/hero");
|
||||||
assert.equal(args.provider, "zai");
|
assert.equal(args.provider, "zai");
|
||||||
assert.equal(args.quality, "2k");
|
assert.equal(args.quality, "2k");
|
||||||
|
assert.equal(args.aspectRatioSource, null);
|
||||||
assert.equal(args.imageSize, "4K");
|
assert.equal(args.imageSize, "4K");
|
||||||
assert.equal(args.imageSizeSource, "cli");
|
assert.equal(args.imageSizeSource, "cli");
|
||||||
assert.deepEqual(args.referenceImages, ["ref/one.png", "ref/two.jpg"]);
|
assert.deepEqual(args.referenceImages, ["ref/one.png", "ref/two.jpg"]);
|
||||||
|
|
@ -256,6 +258,7 @@ test("mergeConfig only fills values missing from CLI args", () => {
|
||||||
assert.equal(merged.provider, "openai");
|
assert.equal(merged.provider, "openai");
|
||||||
assert.equal(merged.quality, "2k");
|
assert.equal(merged.quality, "2k");
|
||||||
assert.equal(merged.aspectRatio, "3:2");
|
assert.equal(merged.aspectRatio, "3:2");
|
||||||
|
assert.equal(merged.aspectRatioSource, "config");
|
||||||
assert.equal(merged.imageSize, "4K");
|
assert.equal(merged.imageSize, "4K");
|
||||||
assert.equal(merged.imageSizeSource, "cli");
|
assert.equal(merged.imageSizeSource, "cli");
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -165,6 +165,7 @@ export function parseArgs(argv: string[]): CliArgs {
|
||||||
provider: null,
|
provider: null,
|
||||||
model: null,
|
model: null,
|
||||||
aspectRatio: null,
|
aspectRatio: null,
|
||||||
|
aspectRatioSource: null,
|
||||||
size: null,
|
size: null,
|
||||||
quality: null,
|
quality: null,
|
||||||
imageSize: null,
|
imageSize: null,
|
||||||
|
|
@ -272,6 +273,7 @@ export function parseArgs(argv: string[]): CliArgs {
|
||||||
const v = argv[++i];
|
const v = argv[++i];
|
||||||
if (!v) throw new Error("Missing value for --ar");
|
if (!v) throw new Error("Missing value for --ar");
|
||||||
out.aspectRatio = v;
|
out.aspectRatio = v;
|
||||||
|
out.aspectRatioSource = "cli";
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -544,12 +546,16 @@ export async function loadExtendConfig(
|
||||||
}
|
}
|
||||||
|
|
||||||
export function mergeConfig(args: CliArgs, extend: Partial<ExtendConfig>): CliArgs {
|
export function mergeConfig(args: CliArgs, extend: Partial<ExtendConfig>): CliArgs {
|
||||||
|
const aspectRatio = args.aspectRatio ?? extend.default_aspect_ratio ?? null;
|
||||||
const imageSize = args.imageSize ?? extend.default_image_size ?? null;
|
const imageSize = args.imageSize ?? extend.default_image_size ?? null;
|
||||||
return {
|
return {
|
||||||
...args,
|
...args,
|
||||||
provider: args.provider ?? extend.default_provider ?? null,
|
provider: args.provider ?? extend.default_provider ?? null,
|
||||||
quality: args.quality ?? extend.default_quality ?? null,
|
quality: args.quality ?? extend.default_quality ?? null,
|
||||||
aspectRatio: args.aspectRatio ?? extend.default_aspect_ratio ?? null,
|
aspectRatio,
|
||||||
|
aspectRatioSource:
|
||||||
|
args.aspectRatioSource ??
|
||||||
|
(args.aspectRatio !== null ? "cli" : (aspectRatio !== null ? "config" : null)),
|
||||||
imageSize,
|
imageSize,
|
||||||
imageSizeSource:
|
imageSizeSource:
|
||||||
args.imageSizeSource ??
|
args.imageSizeSource ??
|
||||||
|
|
@ -880,6 +886,7 @@ export function createTaskArgs(baseArgs: CliArgs, task: BatchTaskInput, batchDir
|
||||||
provider: task.provider ?? baseArgs.provider ?? null,
|
provider: task.provider ?? baseArgs.provider ?? null,
|
||||||
model: task.model ?? baseArgs.model ?? null,
|
model: task.model ?? baseArgs.model ?? null,
|
||||||
aspectRatio: task.ar ?? baseArgs.aspectRatio ?? null,
|
aspectRatio: task.ar ?? baseArgs.aspectRatio ?? null,
|
||||||
|
aspectRatioSource: task.ar != null ? "task" : (baseArgs.aspectRatioSource ?? null),
|
||||||
size: task.size ?? baseArgs.size ?? null,
|
size: task.size ?? baseArgs.size ?? null,
|
||||||
quality: task.quality ?? baseArgs.quality ?? null,
|
quality: task.quality ?? baseArgs.quality ?? null,
|
||||||
imageSize: task.imageSize ?? baseArgs.imageSize ?? null,
|
imageSize: task.imageSize ?? baseArgs.imageSize ?? null,
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ function makeArgs(overrides: Partial<CliArgs> = {}): CliArgs {
|
||||||
provider: null,
|
provider: null,
|
||||||
model: null,
|
model: null,
|
||||||
aspectRatio: null,
|
aspectRatio: null,
|
||||||
|
aspectRatioSource: null,
|
||||||
size: null,
|
size: null,
|
||||||
quality: null,
|
quality: null,
|
||||||
imageSize: null,
|
imageSize: null,
|
||||||
|
|
@ -124,6 +125,21 @@ test("Replicate Seedream and Wan inputs use family-specific request fields", ()
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
buildInput(
|
||||||
|
"bytedance/seedream-4.5",
|
||||||
|
"A cinematic portrait",
|
||||||
|
makeArgs({ size: "1536x1024" }),
|
||||||
|
[],
|
||||||
|
),
|
||||||
|
{
|
||||||
|
prompt: "A cinematic portrait",
|
||||||
|
size: "custom",
|
||||||
|
width: 1536,
|
||||||
|
height: 1024,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
buildInput(
|
buildInput(
|
||||||
"bytedance/seedream-5-lite",
|
"bytedance/seedream-5-lite",
|
||||||
|
|
@ -237,6 +253,22 @@ test("Replicate validateArgs blocks misleading multi-output and unsupported fami
|
||||||
/do not use --imageSize/,
|
/do not use --imageSize/,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
assert.doesNotThrow(() =>
|
||||||
|
validateArgs(
|
||||||
|
"stability-ai/sdxl",
|
||||||
|
makeArgs({ aspectRatio: "16:9", aspectRatioSource: "config" }),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.throws(
|
||||||
|
() =>
|
||||||
|
validateArgs(
|
||||||
|
"stability-ai/sdxl",
|
||||||
|
makeArgs({ aspectRatio: "16:9", aspectRatioSource: "cli" }),
|
||||||
|
),
|
||||||
|
/compatibility list/,
|
||||||
|
);
|
||||||
|
|
||||||
assert.doesNotThrow(() =>
|
assert.doesNotThrow(() =>
|
||||||
validateArgs(
|
validateArgs(
|
||||||
"stability-ai/sdxl",
|
"stability-ai/sdxl",
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,8 @@ type PixelSize = {
|
||||||
height: number;
|
height: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type Seedream45Size = "2K" | "4K" | { width: number; height: number };
|
||||||
|
|
||||||
export function getDefaultModel(): string {
|
export function getDefaultModel(): string {
|
||||||
return process.env.REPLICATE_IMAGE_MODEL || DEFAULT_MODEL;
|
return process.env.REPLICATE_IMAGE_MODEL || DEFAULT_MODEL;
|
||||||
}
|
}
|
||||||
|
|
@ -194,7 +196,7 @@ function getNanoBananaResolution(args: CliArgs): "1K" | "2K" {
|
||||||
return getQualityPreset(args) === "normal" ? "1K" : "2K";
|
return getQualityPreset(args) === "normal" ? "1K" : "2K";
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveSeedream45Size(args: CliArgs): "2K" | "4K" | string {
|
function resolveSeedream45Size(args: CliArgs): Seedream45Size {
|
||||||
if (args.size) {
|
if (args.size) {
|
||||||
const upper = args.size.trim().toUpperCase();
|
const upper = args.size.trim().toUpperCase();
|
||||||
if (upper === "2K" || upper === "4K") {
|
if (upper === "2K" || upper === "4K") {
|
||||||
|
|
@ -208,7 +210,7 @@ function resolveSeedream45Size(args: CliArgs): "2K" | "4K" | string {
|
||||||
if (parsed.width < 1024 || parsed.width > 4096 || parsed.height < 1024 || parsed.height > 4096) {
|
if (parsed.width < 1024 || parsed.width > 4096 || parsed.height < 1024 || parsed.height > 4096) {
|
||||||
throw new Error("Replicate Seedream 4.5 custom --size must keep width and height between 1024 and 4096.");
|
throw new Error("Replicate Seedream 4.5 custom --size must keep width and height between 1024 and 4096.");
|
||||||
}
|
}
|
||||||
return `${parsed.width}x${parsed.height}`;
|
return parsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
return getQualityPreset(args) === "normal" ? "2K" : "4K";
|
return getQualityPreset(args) === "normal" ? "2K" : "4K";
|
||||||
|
|
@ -317,11 +319,19 @@ function buildSeedreamInput(
|
||||||
args: CliArgs,
|
args: CliArgs,
|
||||||
referenceImages: string[],
|
referenceImages: string[],
|
||||||
): Record<string, unknown> {
|
): Record<string, unknown> {
|
||||||
|
const size = family === "seedream45" ? resolveSeedream45Size(args) : resolveSeedream5LiteSize(args);
|
||||||
const input: Record<string, unknown> = {
|
const input: Record<string, unknown> = {
|
||||||
prompt,
|
prompt,
|
||||||
size: family === "seedream45" ? resolveSeedream45Size(args) : resolveSeedream5LiteSize(args),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (family === "seedream45" && typeof size === "object") {
|
||||||
|
input.size = "custom";
|
||||||
|
input.width = size.width;
|
||||||
|
input.height = size.height;
|
||||||
|
} else {
|
||||||
|
input.size = size;
|
||||||
|
}
|
||||||
|
|
||||||
if (referenceImages.length > 0) {
|
if (referenceImages.length > 0) {
|
||||||
input.image_input = referenceImages;
|
input.image_input = referenceImages;
|
||||||
}
|
}
|
||||||
|
|
@ -417,7 +427,9 @@ export function validateArgs(model: string, args: CliArgs): void {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.referenceImages.length > 0 || args.aspectRatio || args.size) {
|
const hasExplicitAspectRatio = !!args.aspectRatio && args.aspectRatioSource !== "config";
|
||||||
|
|
||||||
|
if (args.referenceImages.length > 0 || hasExplicitAspectRatio || args.size) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Replicate model ${model} is not in the baoyu-imagine compatibility list. Supported families: google/nano-banana*, bytedance/seedream-4.5, bytedance/seedream-5-lite, wan-video/wan-2.7-image, wan-video/wan-2.7-image-pro.`
|
`Replicate model ${model} is not in the baoyu-imagine compatibility list. Supported families: google/nano-banana*, bytedance/seedream-4.5, bytedance/seedream-5-lite, wan-video/wan-2.7-image, wan-video/wan-2.7-image-pro.`
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ export type CliArgs = {
|
||||||
provider: Provider | null;
|
provider: Provider | null;
|
||||||
model: string | null;
|
model: string | null;
|
||||||
aspectRatio: string | null;
|
aspectRatio: string | null;
|
||||||
|
aspectRatioSource?: "cli" | "task" | "config" | null;
|
||||||
size: string | null;
|
size: string | null;
|
||||||
quality: Quality | null;
|
quality: Quality | null;
|
||||||
imageSize: string | null;
|
imageSize: string | null;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue