@gobing-ai/knowledge-kit 0.0.13 → 0.0.14
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/.claude-plugin/marketplace.json +1 -1
- package/dist/index.js +20 -0
- package/package.json +1 -1
- package/plugins/generations/content-gen/dist/index.js +20 -0
- package/plugins/generations/core-facts-gen/dist/index.js +20 -0
- package/plugins/generations/daily-article-gen/dist/index.js +29 -1
- package/plugins/generations/daily-article-gen/src/index.ts +13 -1
- package/plugins/generations/dailynews-gen/dist/index.js +20 -0
- package/plugins/generations/episode-plan-gen/dist/index.js +24 -0
- package/plugins/generations/episode-plan-gen/src/index.ts +11 -0
- package/plugins/generations/image-gen/dist/index.js +2296 -53
- package/plugins/generations/image-gen/src/providers/agnes.ts +110 -0
- package/plugins/generations/image-gen/src/providers/azure.ts +153 -0
- package/plugins/generations/image-gen/src/providers/codex-cli.ts +170 -0
- package/plugins/generations/image-gen/src/providers/dashscope.ts +485 -0
- package/plugins/generations/image-gen/src/providers/google.ts +268 -0
- package/plugins/generations/image-gen/src/providers/huggingface.ts +59 -0
- package/plugins/generations/image-gen/src/providers/jimeng.ts +259 -0
- package/plugins/generations/image-gen/src/providers/minimax.ts +171 -0
- package/plugins/generations/image-gen/src/providers/openai.ts +319 -0
- package/plugins/generations/image-gen/src/providers/openrouter.ts +257 -0
- package/plugins/generations/image-gen/src/providers/refs.ts +24 -0
- package/plugins/generations/image-gen/src/providers/replicate.ts +279 -0
- package/plugins/generations/image-gen/src/providers/seedream.ts +1 -1
- package/plugins/generations/image-gen/src/providers/types.ts +52 -51
- package/plugins/generations/image-gen/src/providers/zai.ts +237 -0
- package/plugins/generations/news-report-gen/dist/index.js +22193 -0
- package/plugins/generations/news-report-gen/package.json +17 -0
- package/plugins/generations/news-report-gen/plugin.json +7 -0
- package/plugins/generations/news-report-gen/src/index.ts +308 -0
- package/plugins/generations/news-report-gen/tsconfig.json +4 -0
- package/plugins/generations/omni-voice-gen/Makefile +14 -0
- package/plugins/generations/omni-voice-gen/README.md +112 -0
- package/plugins/generations/omni-voice-gen/bin/omni-voice-gen +2 -0
- package/plugins/generations/omni-voice-gen/dist/omni-voice-gen-prr8skpb. +2 -0
- package/plugins/generations/omni-voice-gen/dist/omni-voice-gen.js +6 -0
- package/plugins/generations/omni-voice-gen/plugin.json +6 -0
- package/plugins/generations/omni-voice-gen/profiles.json +12 -0
- package/plugins/generations/omni-voice-gen/pyproject.toml +25 -0
- package/plugins/generations/omni-voice-gen/scripts/coverage_gate.py +74 -0
- package/plugins/generations/omni-voice-gen/src/omni_voice_gen/__init__.py +1 -0
- package/plugins/generations/omni-voice-gen/src/omni_voice_gen/__main__.py +39 -0
- package/plugins/generations/omni-voice-gen/src/omni_voice_gen/audio.py +190 -0
- package/plugins/generations/omni-voice-gen/src/omni_voice_gen/backend.py +150 -0
- package/plugins/generations/omni-voice-gen/src/omni_voice_gen/contract.py +76 -0
- package/plugins/generations/omni-voice-gen/src/omni_voice_gen/mp3.py +60 -0
- package/plugins/generations/omni-voice-gen/src/omni_voice_gen/pipeline.py +289 -0
- package/plugins/generations/omni-voice-gen/src/omni_voice_gen/profiles.py +100 -0
- package/plugins/generations/omni-voice-gen/src/omni_voice_gen/qc.py +234 -0
- package/plugins/generations/omni-voice-gen/src/omni_voice_gen/voicescript.py +352 -0
- package/plugins/generations/omni-voice-gen/uv.lock +3510 -0
- package/plugins/generations/voice-gen/dist/index.js +30 -2
- package/plugins/generations/voice-gen/src/index.ts +16 -1
- package/plugins/generations/voice-gen/src/voicebox-client.ts +3 -1
- package/plugins/ingestions/aihot-ingest/dist/index.js +20 -0
- package/plugins/ingestions/horizon-ingest/dist/index.js +20 -0
- package/plugins/ingestions/last30days-ingest/dist/index.js +20 -0
- package/plugins/ingestions/web-search/dist/index.js +20 -0
- package/plugins/kk/commands/image-extract.md +40 -0
- package/plugins/kk/commands/image-generate.md +18 -1
- package/plugins/kk/plugin.json +1 -1
- package/plugins/kk/skills/image-authoring/SKILL.md +10 -3
- package/plugins/kk/skills/image-authoring/references/format-drafting.md +57 -0
- package/plugins/kk/skills/image-authoring/references/style-extraction.md +14 -9
- package/plugins/kk/workflows/kk-daily-ai-voice.yaml +130 -30
- package/plugins/publishings/emdash-pub/dist/index.js +20 -0
- package/plugins/publishings/podcast-pub/dist/index.js +57 -3
- package/plugins/publishings/podcast-pub/src/index.ts +18 -2
- package/plugins/publishings/podcast-pub/src/show-notes.ts +56 -9
- package/plugins/publishings/qiita-pub/dist/index.js +20 -0
- package/plugins/publishings/surfdash-pub/dist/index.js +96 -6
- package/plugins/publishings/surfdash-pub/src/index.ts +109 -9
- package/plugins/publishings/zenn-pub/dist/index.js +20 -0
- package/plugins/sp/scripts/batch-preflight.mjs +346 -0
- package/plugins/sp/scripts/batch-preflight.ts +459 -0
- package/plugins/sp/scripts/daily-summary/daily-summary.mjs +615 -0
- package/plugins/sp/scripts/daily-summary/daily-summary.ts +846 -0
- package/plugins/sp/scripts/daily-summary/logger.ts +28 -0
- package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.mjs +223 -0
- package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.ts +367 -0
- package/plugins/sp/scripts/dogfood-testing/validate-report.mjs +132 -0
- package/plugins/sp/scripts/dogfood-testing/validate-report.ts +169 -0
- package/plugins/sp/scripts/feature-dev-precheck.mjs +171 -0
- package/plugins/sp/scripts/feature-dev-precheck.ts +238 -0
- package/plugins/sp/scripts/feature-sync-bounded.mjs +285 -0
- package/plugins/sp/scripts/feature-sync-bounded.ts +478 -0
- package/plugins/sp/scripts/history-anatomy-cache.mjs +902 -0
- package/plugins/sp/scripts/history-anatomy-cache.ts +1028 -0
- package/plugins/sp/scripts/idea-handoff.mjs +22 -0
- package/plugins/sp/scripts/idea-handoff.ts +44 -0
- package/plugins/sp/scripts/inline-pipeline-parity-check.ts +185 -0
- package/plugins/sp/scripts/inline-run-setup.ts +198 -0
- package/plugins/sp/scripts/pr-reviewing.mjs +769 -0
- package/plugins/sp/scripts/pr-reviewing.ts +925 -0
- package/plugins/sp/scripts/quality-gate.mjs +179 -0
- package/plugins/sp/scripts/quality-gate.ts +217 -0
- package/plugins/sp/scripts/script-contract-check.ts +319 -0
- package/plugins/sp/scripts/stage-registry-adapter.ts +1533 -0
- package/plugins/sp/scripts/surface-drift-inventory.ts +929 -0
- package/plugins/sp/scripts/task-evidence-precheck.ts +181 -0
- package/plugins/sp/scripts/task-size-precheck.ts +175 -0
- package/plugins/sp/scripts/transition-shim-check.ts +238 -0
- package/plugins/sp/scripts/validate-commands.ts +689 -0
- package/plugins/sp/scripts/validate-flag-contracts.ts +878 -0
- package/plugins/sp/scripts/verify-answer-lint.ts +530 -0
- package/plugins/sp/scripts/workflow-step-profile.mjs +316 -0
- package/plugins/sp/scripts/workflow-step-profile.ts +456 -0
- package/plugins/sp/scripts/wrapup-steps.mjs +373 -0
- package/plugins/sp/scripts/wrapup-steps.ts +466 -0
|
@@ -21848,6 +21848,26 @@ var ResultSchema = exports_external.object({
|
|
|
21848
21848
|
metadata: exports_external.record(exports_external.string(), exports_external.unknown()).optional()
|
|
21849
21849
|
});
|
|
21850
21850
|
var DocListSchema = exports_external.array(DocSchema);
|
|
21851
|
+
var omniVoiceGenContentSchema = ContentSchema.extend({
|
|
21852
|
+
format: exports_external.literal("audio"),
|
|
21853
|
+
metadata: exports_external.looseObject({
|
|
21854
|
+
generator: exports_external.literal("kk:omni-voice-gen"),
|
|
21855
|
+
audioPath: exports_external.string(),
|
|
21856
|
+
duration: exports_external.number(),
|
|
21857
|
+
segments: exports_external.array(exports_external.looseObject({
|
|
21858
|
+
generationId: exports_external.string(),
|
|
21859
|
+
profile: exports_external.string(),
|
|
21860
|
+
speaker: exports_external.string().optional(),
|
|
21861
|
+
emotion: exports_external.string().optional(),
|
|
21862
|
+
duration: exports_external.number(),
|
|
21863
|
+
verifyRetries: exports_external.number(),
|
|
21864
|
+
transcription: exports_external.string().optional(),
|
|
21865
|
+
loudnessDip: exports_external.boolean().optional()
|
|
21866
|
+
})),
|
|
21867
|
+
mp3Path: exports_external.string().optional(),
|
|
21868
|
+
qc: exports_external.record(exports_external.string(), exports_external.unknown()).optional()
|
|
21869
|
+
})
|
|
21870
|
+
});
|
|
21851
21871
|
|
|
21852
21872
|
// ../../packages/kk-core/src/kinds.ts
|
|
21853
21873
|
var PLUGIN_KINDS = ["ingestion", "generator", "publish"];
|
|
@@ -22115,8 +22135,32 @@ function loadPresets() {
|
|
|
22115
22135
|
return { formats, styles, diagnostics };
|
|
22116
22136
|
}
|
|
22117
22137
|
|
|
22138
|
+
// ../../plugins/generations/image-gen/src/providers/agnes.ts
|
|
22139
|
+
import { Buffer as Buffer3 } from "buffer";
|
|
22140
|
+
|
|
22141
|
+
// ../../plugins/generations/image-gen/src/providers/refs.ts
|
|
22142
|
+
import { extname as extname2 } from "path";
|
|
22143
|
+
function mimeForPath(path2) {
|
|
22144
|
+
const ext = extname2(path2).toLowerCase();
|
|
22145
|
+
if (ext === ".jpg" || ext === ".jpeg")
|
|
22146
|
+
return "image/jpeg";
|
|
22147
|
+
if (ext === ".webp")
|
|
22148
|
+
return "image/webp";
|
|
22149
|
+
if (ext === ".gif")
|
|
22150
|
+
return "image/gif";
|
|
22151
|
+
if (ext === ".bmp")
|
|
22152
|
+
return "image/bmp";
|
|
22153
|
+
return "image/png";
|
|
22154
|
+
}
|
|
22155
|
+
async function refToDataUri(path2) {
|
|
22156
|
+
if (/^https?:\/\//i.test(path2))
|
|
22157
|
+
return path2;
|
|
22158
|
+
const bytes = await readBytes(path2);
|
|
22159
|
+
return `data:${mimeForPath(path2)};base64,${Buffer.from(bytes).toString("base64")}`;
|
|
22160
|
+
}
|
|
22161
|
+
|
|
22118
22162
|
// ../../plugins/generations/image-gen/src/providers/seedream.ts
|
|
22119
|
-
import { Buffer } from "buffer";
|
|
22163
|
+
import { Buffer as Buffer2 } from "buffer";
|
|
22120
22164
|
var DEFAULT_BASE_URL = "https://ark.cn-beijing.volces.com/api/v3";
|
|
22121
22165
|
var DEFAULT_MODEL = "doubao-seedream-5-0-260128";
|
|
22122
22166
|
var REF_MODEL_FRAGMENTS = ["doubao-seedream-5-0", "doubao-seedream-4-5-", "doubao-seedream-4-0-"];
|
|
@@ -22164,7 +22208,7 @@ var createSeedream = (options) => {
|
|
|
22164
22208
|
if (req.referenceImages !== undefined && req.referenceImages.length > 0) {
|
|
22165
22209
|
const lifted = await Promise.all(req.referenceImages.map(async (path2) => {
|
|
22166
22210
|
const bytes = await readBytes(path2);
|
|
22167
|
-
return `data:image/png;base64,${
|
|
22211
|
+
return `data:image/png;base64,${Buffer2.from(bytes).toString("base64")}`;
|
|
22168
22212
|
}));
|
|
22169
22213
|
body.image = lifted.length === 1 ? lifted[0] : lifted;
|
|
22170
22214
|
body.response_format = "b64_json";
|
|
@@ -22193,7 +22237,7 @@ var createSeedream = (options) => {
|
|
|
22193
22237
|
}
|
|
22194
22238
|
if (image.b64_json !== undefined) {
|
|
22195
22239
|
return {
|
|
22196
|
-
bytes: Uint8Array.from(
|
|
22240
|
+
bytes: Uint8Array.from(Buffer2.from(image.b64_json, "base64")),
|
|
22197
22241
|
extension: "png",
|
|
22198
22242
|
provider: "seedream",
|
|
22199
22243
|
model: req.model
|
|
@@ -22216,52 +22260,2237 @@ var createSeedream = (options) => {
|
|
|
22216
22260
|
};
|
|
22217
22261
|
};
|
|
22218
22262
|
|
|
22219
|
-
// ../../plugins/generations/image-gen/src/providers/
|
|
22220
|
-
var
|
|
22221
|
-
var
|
|
22222
|
-
|
|
22223
|
-
|
|
22224
|
-
|
|
22225
|
-
|
|
22226
|
-
|
|
22227
|
-
|
|
22228
|
-
|
|
22229
|
-
|
|
22230
|
-
|
|
22263
|
+
// ../../plugins/generations/image-gen/src/providers/agnes.ts
|
|
22264
|
+
var DEFAULT_MODEL2 = "agnes-image-2.1-flash";
|
|
22265
|
+
var DEFAULT_BASE_URL2 = "https://apihub.agnes-ai.com/v1";
|
|
22266
|
+
var DEFAULT_SIZE = "1024x1024";
|
|
22267
|
+
function parseAspectRatio(ar) {
|
|
22268
|
+
const match = ar.match(/^(\d+(?:\.\d+)?):(\d+(?:\.\d+)?)$/);
|
|
22269
|
+
if (match === null)
|
|
22270
|
+
return null;
|
|
22271
|
+
const w = Number.parseFloat(match[1]);
|
|
22272
|
+
const h = Number.parseFloat(match[2]);
|
|
22273
|
+
if (w <= 0 || h <= 0)
|
|
22274
|
+
return null;
|
|
22275
|
+
return { width: w, height: h };
|
|
22276
|
+
}
|
|
22277
|
+
function snapDim(n) {
|
|
22278
|
+
return Math.max(32, Math.round(n / 32) * 32);
|
|
22279
|
+
}
|
|
22280
|
+
function resolveSize(args) {
|
|
22281
|
+
if (args.width !== undefined && args.height !== undefined)
|
|
22282
|
+
return `${args.width}x${args.height}`;
|
|
22283
|
+
if (args.aspectRatio !== undefined) {
|
|
22284
|
+
const parsed = parseAspectRatio(args.aspectRatio);
|
|
22285
|
+
if (parsed !== null) {
|
|
22286
|
+
if (parsed.width === 1 && parsed.height === 1)
|
|
22287
|
+
return "1024x1024";
|
|
22288
|
+
const maxEdge = 2048;
|
|
22289
|
+
const scale = Math.max(1, Math.floor(maxEdge / Math.max(parsed.width, parsed.height)));
|
|
22290
|
+
return `${snapDim(parsed.width * scale)}x${snapDim(parsed.height * scale)}`;
|
|
22291
|
+
}
|
|
22292
|
+
}
|
|
22293
|
+
return DEFAULT_SIZE;
|
|
22294
|
+
}
|
|
22295
|
+
function buildRequestBody(prompt, model, args) {
|
|
22296
|
+
const body = { model, prompt, size: resolveSize(args) };
|
|
22297
|
+
if (args.referenceImages.length > 0)
|
|
22298
|
+
body.image = args.referenceImages;
|
|
22299
|
+
body.extra_body = { response_format: "url" };
|
|
22300
|
+
return body;
|
|
22301
|
+
}
|
|
22302
|
+
async function extractImageFromResponse(result, fetchFn = fetch) {
|
|
22303
|
+
const img = result.data[0];
|
|
22304
|
+
if (img?.b64_json !== undefined && img.b64_json !== "") {
|
|
22305
|
+
return Uint8Array.from(Buffer3.from(img.b64_json, "base64"));
|
|
22306
|
+
}
|
|
22307
|
+
if (img?.url !== undefined) {
|
|
22308
|
+
const imgRes = await fetchFn(img.url);
|
|
22309
|
+
if (!imgRes.ok)
|
|
22310
|
+
throw new Error(`Failed to download image from Agnes: ${imgRes.status}`);
|
|
22311
|
+
return new Uint8Array(await imgRes.arrayBuffer());
|
|
22312
|
+
}
|
|
22313
|
+
throw new Error("No image in Agnes response");
|
|
22314
|
+
}
|
|
22315
|
+
var createAgnes = (options) => {
|
|
22316
|
+
const fetchFn = options?.fetch ?? fetch;
|
|
22317
|
+
const env = options?.env ?? process.env;
|
|
22318
|
+
const readEnv = (key) => env[key] ?? "";
|
|
22319
|
+
return {
|
|
22320
|
+
name: "agnes",
|
|
22321
|
+
defaultModel: () => DEFAULT_MODEL2,
|
|
22322
|
+
isAvailable: () => readEnv("AGNES_API_KEY") !== "",
|
|
22323
|
+
supportsReferenceImages: () => true,
|
|
22324
|
+
async generate(req) {
|
|
22325
|
+
const apiKey = readEnv("AGNES_API_KEY");
|
|
22326
|
+
if (apiKey === "")
|
|
22327
|
+
throw new Error("AGNES_API_KEY is required. Get one from https://apihub.agnes-ai.com.");
|
|
22328
|
+
const referenceImages = await Promise.all((req.referenceImages ?? []).map(refToDataUri));
|
|
22329
|
+
const body = buildRequestBody(req.prompt, req.model, { ...req, referenceImages });
|
|
22330
|
+
const base = (readEnv("AGNES_BASE_URL") || DEFAULT_BASE_URL2).replace(/\/+$/, "");
|
|
22331
|
+
const res = await fetchFn(`${base}/images/generations`, {
|
|
22332
|
+
method: "POST",
|
|
22333
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${apiKey}` },
|
|
22334
|
+
body: JSON.stringify(body)
|
|
22335
|
+
});
|
|
22336
|
+
if (!res.ok)
|
|
22337
|
+
throw new Error(`Agnes API error (${res.status}): ${await res.text()}`);
|
|
22338
|
+
const result = await res.json();
|
|
22339
|
+
const bytes = await extractImageFromResponse(result, fetchFn);
|
|
22340
|
+
const url2 = result.data[0]?.url;
|
|
22341
|
+
return {
|
|
22342
|
+
bytes,
|
|
22343
|
+
extension: url2 === undefined || url2.startsWith("data:") ? "png" : extensionFromUrl(url2),
|
|
22344
|
+
provider: "agnes",
|
|
22345
|
+
model: req.model
|
|
22346
|
+
};
|
|
22347
|
+
}
|
|
22348
|
+
};
|
|
22349
|
+
};
|
|
22350
|
+
|
|
22351
|
+
// ../../plugins/generations/image-gen/src/providers/azure.ts
|
|
22352
|
+
import { extname as extname3 } from "path";
|
|
22353
|
+
|
|
22354
|
+
// ../../plugins/generations/image-gen/src/providers/openai.ts
|
|
22355
|
+
import { Buffer as Buffer4 } from "buffer";
|
|
22356
|
+
import { basename as basename2 } from "path";
|
|
22357
|
+
var DEFAULT_MODEL3 = "gpt-image-2";
|
|
22358
|
+
function explicitSize(req) {
|
|
22359
|
+
return req.width !== undefined && req.height !== undefined ? `${req.width}x${req.height}` : undefined;
|
|
22360
|
+
}
|
|
22361
|
+
function parseAspectRatio2(ar) {
|
|
22362
|
+
const match = ar.match(/^(\d+(?:\.\d+)?):(\d+(?:\.\d+)?)$/);
|
|
22363
|
+
if (match === null)
|
|
22364
|
+
return null;
|
|
22365
|
+
const w = Number.parseFloat(match[1]);
|
|
22366
|
+
const h = Number.parseFloat(match[2]);
|
|
22367
|
+
if (w <= 0 || h <= 0)
|
|
22368
|
+
return null;
|
|
22369
|
+
return { width: w, height: h };
|
|
22370
|
+
}
|
|
22371
|
+
function roundToMultiple(value, multiple) {
|
|
22372
|
+
return Math.max(multiple, Math.round(value / multiple) * multiple);
|
|
22373
|
+
}
|
|
22374
|
+
function buildGptImage2SizeFromAspectRatio(ratio, quality) {
|
|
22375
|
+
if (Math.abs(ratio - 1) < 0.1) {
|
|
22376
|
+
const edge = quality === "2k" ? 2048 : 1024;
|
|
22377
|
+
return `${edge}x${edge}`;
|
|
22378
|
+
}
|
|
22379
|
+
const targetLongEdge = quality === "2k" ? 2048 : 1024;
|
|
22380
|
+
let width;
|
|
22381
|
+
let height;
|
|
22382
|
+
if (ratio > 1) {
|
|
22383
|
+
width = targetLongEdge;
|
|
22384
|
+
height = roundToMultiple(width / ratio, 16);
|
|
22385
|
+
} else {
|
|
22386
|
+
height = targetLongEdge;
|
|
22387
|
+
width = roundToMultiple(height * ratio, 16);
|
|
22388
|
+
}
|
|
22389
|
+
while (width * height < 655360) {
|
|
22390
|
+
if (ratio > 1) {
|
|
22391
|
+
width += 16;
|
|
22392
|
+
height = roundToMultiple(width / ratio, 16);
|
|
22393
|
+
} else {
|
|
22394
|
+
height += 16;
|
|
22395
|
+
width = roundToMultiple(height * ratio, 16);
|
|
22396
|
+
}
|
|
22397
|
+
}
|
|
22398
|
+
return `${width}x${height}`;
|
|
22399
|
+
}
|
|
22400
|
+
function getOpenAISize(model, ar, quality) {
|
|
22401
|
+
const isDalle2 = model.includes("dall-e-2");
|
|
22402
|
+
const isDalle3 = model.includes("dall-e-3");
|
|
22403
|
+
const isGptImage2 = model.includes("gpt-image-2");
|
|
22404
|
+
if (isDalle2)
|
|
22405
|
+
return "1024x1024";
|
|
22406
|
+
if (isGptImage2) {
|
|
22407
|
+
const parsed2 = ar === null ? null : parseAspectRatio2(ar);
|
|
22408
|
+
const ratio2 = parsed2 === null ? 1 : parsed2.width / parsed2.height;
|
|
22409
|
+
return buildGptImage2SizeFromAspectRatio(ratio2, quality);
|
|
22410
|
+
}
|
|
22411
|
+
const sizes = isDalle3 ? { square: "1024x1024", landscape: "1792x1024", portrait: "1024x1792" } : { square: "1024x1024", landscape: "1536x1024", portrait: "1024x1536" };
|
|
22412
|
+
if (ar === null)
|
|
22413
|
+
return sizes.square;
|
|
22414
|
+
const parsed = parseAspectRatio2(ar);
|
|
22415
|
+
if (parsed === null)
|
|
22416
|
+
return sizes.square;
|
|
22417
|
+
const ratio = parsed.width / parsed.height;
|
|
22418
|
+
if (Math.abs(ratio - 1) < 0.1)
|
|
22419
|
+
return sizes.square;
|
|
22420
|
+
if (ratio > 1.5)
|
|
22421
|
+
return sizes.landscape;
|
|
22422
|
+
if (ratio < 0.67)
|
|
22423
|
+
return sizes.portrait;
|
|
22424
|
+
return sizes.square;
|
|
22425
|
+
}
|
|
22426
|
+
function gcd(a, b) {
|
|
22427
|
+
let x = Math.abs(a);
|
|
22428
|
+
let y = Math.abs(b);
|
|
22429
|
+
while (y !== 0) {
|
|
22430
|
+
const next = x % y;
|
|
22431
|
+
x = y;
|
|
22432
|
+
y = next;
|
|
22433
|
+
}
|
|
22434
|
+
return x || 1;
|
|
22435
|
+
}
|
|
22436
|
+
function inferAspectRatioFromSize(size) {
|
|
22437
|
+
if (size === null)
|
|
22438
|
+
return null;
|
|
22439
|
+
const parsed = size.match(/^(\d+)\s*[xX]\s*(\d+)$/);
|
|
22440
|
+
if (parsed === null)
|
|
22441
|
+
return null;
|
|
22442
|
+
const width = Number.parseInt(parsed[1], 10);
|
|
22443
|
+
const height = Number.parseInt(parsed[2], 10);
|
|
22444
|
+
if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0)
|
|
22445
|
+
return null;
|
|
22446
|
+
const divisor = gcd(width, height);
|
|
22447
|
+
return `${width / divisor}:${height / divisor}`;
|
|
22448
|
+
}
|
|
22449
|
+
function inferResolutionFromSize(size) {
|
|
22450
|
+
if (size === null)
|
|
22451
|
+
return null;
|
|
22452
|
+
const parsed = size.match(/^(\d+)\s*[xX]\s*(\d+)$/);
|
|
22453
|
+
if (parsed === null)
|
|
22454
|
+
return null;
|
|
22455
|
+
const width = Number.parseInt(parsed[1], 10);
|
|
22456
|
+
const height = Number.parseInt(parsed[2], 10);
|
|
22457
|
+
if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0)
|
|
22458
|
+
return null;
|
|
22459
|
+
const longestEdge = Math.max(width, height);
|
|
22460
|
+
if (longestEdge <= 1024)
|
|
22461
|
+
return "1K";
|
|
22462
|
+
if (longestEdge <= 2048)
|
|
22463
|
+
return "2K";
|
|
22464
|
+
return "4K";
|
|
22465
|
+
}
|
|
22466
|
+
function getOpenAIAspectRatio(args) {
|
|
22467
|
+
return args.aspectRatio ?? inferAspectRatioFromSize(args.size ?? null) ?? "1:1";
|
|
22468
|
+
}
|
|
22469
|
+
function getOpenAIResolution(args) {
|
|
22470
|
+
const inferred = inferResolutionFromSize(args.size ?? null);
|
|
22471
|
+
if (inferred !== null)
|
|
22472
|
+
return inferred;
|
|
22473
|
+
return args.quality === "normal" ? "1K" : "2K";
|
|
22474
|
+
}
|
|
22475
|
+
function getOpenAIQuality(model, quality) {
|
|
22476
|
+
if (model.includes("dall-e-3"))
|
|
22477
|
+
return quality === "2k" ? "hd" : "standard";
|
|
22478
|
+
if (model.includes("gpt-image"))
|
|
22479
|
+
return quality === "2k" ? "high" : "medium";
|
|
22480
|
+
return null;
|
|
22481
|
+
}
|
|
22482
|
+
function getOrientationFromAspectRatio(ar) {
|
|
22483
|
+
const parsed = parseAspectRatio2(ar);
|
|
22484
|
+
if (parsed === null)
|
|
22485
|
+
return null;
|
|
22486
|
+
const ratio = parsed.width / parsed.height;
|
|
22487
|
+
if (Math.abs(ratio - 1) < 0.1)
|
|
22488
|
+
return null;
|
|
22489
|
+
return ratio > 1 ? "landscape" : "portrait";
|
|
22490
|
+
}
|
|
22491
|
+
function buildOpenAIGenerationsBody(prompt, model, args) {
|
|
22492
|
+
if ((args.imageApiDialect ?? "openai-native") === "ratio-metadata") {
|
|
22493
|
+
const aspectRatio = getOpenAIAspectRatio(args);
|
|
22494
|
+
const metadata = {
|
|
22495
|
+
resolution: getOpenAIResolution({ size: args.size, quality: args.quality })
|
|
22496
|
+
};
|
|
22497
|
+
const orientation = getOrientationFromAspectRatio(aspectRatio);
|
|
22498
|
+
if (orientation !== null)
|
|
22499
|
+
metadata.orientation = orientation;
|
|
22500
|
+
return { model, prompt, size: aspectRatio, metadata };
|
|
22501
|
+
}
|
|
22502
|
+
const body = {
|
|
22503
|
+
model,
|
|
22504
|
+
prompt,
|
|
22505
|
+
size: args.size ?? getOpenAISize(model, args.aspectRatio ?? null, args.quality)
|
|
22506
|
+
};
|
|
22507
|
+
const quality = getOpenAIQuality(model, args.quality);
|
|
22508
|
+
if (quality !== null)
|
|
22509
|
+
body.quality = quality;
|
|
22510
|
+
return body;
|
|
22511
|
+
}
|
|
22512
|
+
function validateGptImage2Size(width, height) {
|
|
22513
|
+
const ratio = Math.max(width, height) / Math.min(width, height);
|
|
22514
|
+
if (Math.max(width, height) > 3840)
|
|
22515
|
+
throw new Error("gpt-image-2 --size maximum edge length must be 3840px or less.");
|
|
22516
|
+
if (width % 16 !== 0 || height % 16 !== 0) {
|
|
22517
|
+
throw new Error("gpt-image-2 --size width and height must both be multiples of 16px.");
|
|
22518
|
+
}
|
|
22519
|
+
if (ratio > 3)
|
|
22520
|
+
throw new Error("gpt-image-2 --size long edge to short edge ratio must not exceed 3:1.");
|
|
22521
|
+
const totalPixels = width * height;
|
|
22522
|
+
if (totalPixels < 655360 || totalPixels > 8294400) {
|
|
22523
|
+
throw new Error("gpt-image-2 --size total pixels must be between 655,360 and 8,294,400.");
|
|
22524
|
+
}
|
|
22525
|
+
}
|
|
22526
|
+
function getMimeType(filename) {
|
|
22527
|
+
const ext = basename2(filename).toLowerCase();
|
|
22528
|
+
if (ext.includes(".jpg") || ext.includes(".jpeg"))
|
|
22529
|
+
return "image/jpeg";
|
|
22530
|
+
if (ext.includes(".webp"))
|
|
22531
|
+
return "image/webp";
|
|
22532
|
+
if (ext.includes(".gif"))
|
|
22533
|
+
return "image/gif";
|
|
22534
|
+
return "image/png";
|
|
22535
|
+
}
|
|
22536
|
+
async function extractImageFromResponse2(result, fetchFn = fetch) {
|
|
22537
|
+
const img = result.data[0];
|
|
22538
|
+
if (img?.b64_json !== undefined && img.b64_json !== "") {
|
|
22539
|
+
return Uint8Array.from(Buffer4.from(img.b64_json, "base64"));
|
|
22540
|
+
}
|
|
22541
|
+
if (img?.url !== undefined) {
|
|
22542
|
+
const imgRes = await fetchFn(img.url);
|
|
22543
|
+
if (!imgRes.ok)
|
|
22544
|
+
throw new Error("Failed to download image");
|
|
22545
|
+
return new Uint8Array(await imgRes.arrayBuffer());
|
|
22546
|
+
}
|
|
22547
|
+
throw new Error("No image in response");
|
|
22548
|
+
}
|
|
22549
|
+
var createOpenai = (options) => {
|
|
22550
|
+
const fetchFn = options?.fetch ?? fetch;
|
|
22551
|
+
const env = options?.env ?? process.env;
|
|
22552
|
+
const readEnv = (key) => env[key] ?? "";
|
|
22553
|
+
const supportsRef = (model) => model.includes("gpt-image");
|
|
22554
|
+
return {
|
|
22555
|
+
name: "openai",
|
|
22556
|
+
defaultModel: () => DEFAULT_MODEL3,
|
|
22557
|
+
isAvailable: () => readEnv("OPENAI_API_KEY") !== "",
|
|
22558
|
+
supportsReferenceImages: supportsRef,
|
|
22559
|
+
validate(req) {
|
|
22560
|
+
if ((req.referenceImages?.length ?? 0) > 0 && !supportsRef(req.model)) {
|
|
22561
|
+
throw new Error(`Reference images with OpenAI require GPT Image models. Use --model gpt-image-2 (or another gpt-image model); '${req.model}' is not one.`);
|
|
22562
|
+
}
|
|
22563
|
+
if (req.width !== undefined && req.height !== undefined && req.model.includes("gpt-image-2")) {
|
|
22564
|
+
validateGptImage2Size(req.width, req.height);
|
|
22565
|
+
}
|
|
22566
|
+
},
|
|
22567
|
+
async generate(req) {
|
|
22568
|
+
const baseURL = readEnv("OPENAI_BASE_URL") || "https://api.openai.com/v1";
|
|
22569
|
+
const apiKey = readEnv("OPENAI_API_KEY");
|
|
22570
|
+
if (apiKey === "") {
|
|
22571
|
+
throw new Error("OPENAI_API_KEY is required. Codex/ChatGPT desktop login does not automatically grant OpenAI Images API access.");
|
|
22572
|
+
}
|
|
22573
|
+
if (readEnv("OPENAI_IMAGE_USE_CHAT") === "true") {
|
|
22574
|
+
const chatRes = await fetchFn(`${baseURL}/chat/completions`, {
|
|
22575
|
+
method: "POST",
|
|
22576
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${apiKey}` },
|
|
22577
|
+
body: JSON.stringify({ model: req.model, messages: [{ role: "user", content: req.prompt }] })
|
|
22578
|
+
});
|
|
22579
|
+
if (!chatRes.ok)
|
|
22580
|
+
throw new Error(`OpenAI API error: ${await chatRes.text()}`);
|
|
22581
|
+
const content = (await chatRes.json()).choices?.[0]?.message?.content ?? "";
|
|
22582
|
+
const match = content.match(/data:image\/[^;]+;base64,([A-Za-z0-9+/=]+)/);
|
|
22583
|
+
if (match !== null && match[1] !== undefined) {
|
|
22584
|
+
return {
|
|
22585
|
+
bytes: Uint8Array.from(Buffer4.from(match[1], "base64")),
|
|
22586
|
+
extension: "png",
|
|
22587
|
+
provider: "openai",
|
|
22588
|
+
model: req.model
|
|
22589
|
+
};
|
|
22590
|
+
}
|
|
22591
|
+
throw new Error("No image found in chat completions response");
|
|
22592
|
+
}
|
|
22593
|
+
const refs = req.referenceImages ?? [];
|
|
22594
|
+
const args = {
|
|
22595
|
+
aspectRatio: req.aspectRatio,
|
|
22596
|
+
size: explicitSize(req),
|
|
22597
|
+
quality: req.quality
|
|
22598
|
+
};
|
|
22599
|
+
const bytes = await (async () => {
|
|
22600
|
+
if (refs.length > 0 && (req.model.includes("dall-e-2") || req.model.includes("dall-e-3"))) {
|
|
22601
|
+
throw new Error(`Reference images with OpenAI require GPT Image models. Use --model gpt-image-2 (or another gpt-image model).`);
|
|
22602
|
+
}
|
|
22603
|
+
if (refs.length > 0) {
|
|
22604
|
+
const size = args.size ?? getOpenAISize(req.model, args.aspectRatio ?? null, args.quality);
|
|
22605
|
+
const form = new FormData;
|
|
22606
|
+
form.append("model", req.model);
|
|
22607
|
+
form.append("prompt", req.prompt);
|
|
22608
|
+
form.append("size", size);
|
|
22609
|
+
const quality = getOpenAIQuality(req.model, args.quality);
|
|
22610
|
+
if (quality !== null && quality !== "standard" && quality !== "hd")
|
|
22611
|
+
form.append("quality", quality);
|
|
22612
|
+
for (const refPath of refs) {
|
|
22613
|
+
const bytes2 = await readBytes(refPath);
|
|
22614
|
+
const filename = basename2(refPath);
|
|
22615
|
+
form.append("image[]", new Blob([bytes2], { type: getMimeType(filename) }), filename);
|
|
22616
|
+
}
|
|
22617
|
+
const res2 = await fetchFn(`${baseURL}/images/edits`, {
|
|
22618
|
+
method: "POST",
|
|
22619
|
+
headers: { authorization: `Bearer ${apiKey}` },
|
|
22620
|
+
body: form
|
|
22621
|
+
});
|
|
22622
|
+
if (!res2.ok)
|
|
22623
|
+
throw new Error(`OpenAI edits API error: ${await res2.text()}`);
|
|
22624
|
+
return extractImageFromResponse2(await res2.json(), fetchFn);
|
|
22625
|
+
}
|
|
22626
|
+
const res = await fetchFn(`${baseURL}/images/generations`, {
|
|
22627
|
+
method: "POST",
|
|
22628
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${apiKey}` },
|
|
22629
|
+
body: JSON.stringify(buildOpenAIGenerationsBody(req.prompt, req.model, args))
|
|
22630
|
+
});
|
|
22631
|
+
if (!res.ok)
|
|
22632
|
+
throw new Error(`OpenAI API error: ${await res.text()}`);
|
|
22633
|
+
return extractImageFromResponse2(await res.json(), fetchFn);
|
|
22634
|
+
})();
|
|
22635
|
+
return { bytes, extension: "png", provider: "openai", model: req.model };
|
|
22636
|
+
}
|
|
22637
|
+
};
|
|
22638
|
+
};
|
|
22639
|
+
|
|
22640
|
+
// ../../plugins/generations/image-gen/src/providers/azure.ts
|
|
22641
|
+
var DEFAULT_AZURE_API_VERSION = "2025-04-01-preview";
|
|
22642
|
+
var AZURE_EDIT_IMAGE_EXTENSIONS = new Set([".png", ".jpg", ".jpeg"]);
|
|
22643
|
+
function parseAzureBaseURL(url2) {
|
|
22644
|
+
const parsed = new URL(url2);
|
|
22645
|
+
const trimmedPath = parsed.pathname.replace(/\/+$/, "");
|
|
22646
|
+
const deploymentMatch = trimmedPath.match(/^(.*?)(?:\/openai)?\/deployments\/([^/]+)$/);
|
|
22647
|
+
if (deploymentMatch !== null) {
|
|
22648
|
+
parsed.pathname = `${deploymentMatch[1] ?? ""}/openai`;
|
|
22649
|
+
return {
|
|
22650
|
+
resourceBaseURL: parsed.toString().replace(/\/+$/, ""),
|
|
22651
|
+
deployment: decodeURIComponent(deploymentMatch[2])
|
|
22652
|
+
};
|
|
22653
|
+
}
|
|
22654
|
+
parsed.pathname = trimmedPath.endsWith("/openai") ? trimmedPath : `${trimmedPath}/openai`;
|
|
22655
|
+
return { resourceBaseURL: parsed.toString().replace(/\/+$/, ""), deployment: null };
|
|
22656
|
+
}
|
|
22657
|
+
function azureEndpoint(env) {
|
|
22658
|
+
const url2 = env.AZURE_OPENAI_BASE_URL ?? "";
|
|
22659
|
+
if (url2 === "") {
|
|
22660
|
+
throw new Error("AZURE_OPENAI_BASE_URL is required. Set it to your Azure resource or deployment endpoint, e.g.: https://your-resource.openai.azure.com or https://your-resource.openai.azure.com/openai/deployments/your-deployment");
|
|
22661
|
+
}
|
|
22662
|
+
return parseAzureBaseURL(url2);
|
|
22663
|
+
}
|
|
22664
|
+
function getApiKey(env) {
|
|
22665
|
+
const key = env.AZURE_OPENAI_API_KEY ?? "";
|
|
22666
|
+
if (key === "") {
|
|
22667
|
+
throw new Error("AZURE_OPENAI_API_KEY is required. Get it from Azure Portal \u2192 your OpenAI resource \u2192 Keys and Endpoint.");
|
|
22668
|
+
}
|
|
22669
|
+
return key;
|
|
22670
|
+
}
|
|
22671
|
+
function buildURL(endpoint, deployment, pathSuffix, env) {
|
|
22672
|
+
const apiVersion = env.AZURE_API_VERSION ?? DEFAULT_AZURE_API_VERSION;
|
|
22673
|
+
return `${endpoint.resourceBaseURL}/deployments/${encodeURIComponent(deployment)}${pathSuffix}?api-version=${apiVersion}`;
|
|
22674
|
+
}
|
|
22675
|
+
function getAzureQuality(quality) {
|
|
22676
|
+
return quality === "2k" ? "high" : "medium";
|
|
22677
|
+
}
|
|
22678
|
+
function validateReferenceImages(referenceImages) {
|
|
22679
|
+
for (const refPath of referenceImages) {
|
|
22680
|
+
if (!AZURE_EDIT_IMAGE_EXTENSIONS.has(extname3(refPath).toLowerCase())) {
|
|
22681
|
+
throw new Error(`Azure OpenAI reference images must be PNG or JPG/JPEG. Unsupported file: ${refPath}`);
|
|
22682
|
+
}
|
|
22683
|
+
}
|
|
22684
|
+
}
|
|
22685
|
+
var createAzure = (options) => {
|
|
22686
|
+
const fetchFn = options?.fetch ?? fetch;
|
|
22687
|
+
const env = options?.env ?? process.env;
|
|
22688
|
+
function defaultModel() {
|
|
22689
|
+
const explicit = env.AZURE_OPENAI_DEPLOYMENT?.trim();
|
|
22690
|
+
if (explicit !== undefined && explicit !== "")
|
|
22691
|
+
return explicit;
|
|
22692
|
+
const baseURL = env.AZURE_OPENAI_BASE_URL;
|
|
22693
|
+
if (baseURL !== undefined) {
|
|
22694
|
+
try {
|
|
22695
|
+
const { deployment } = parseAzureBaseURL(baseURL);
|
|
22696
|
+
if (deployment !== null)
|
|
22697
|
+
return deployment;
|
|
22698
|
+
} catch {}
|
|
22699
|
+
}
|
|
22700
|
+
return "gpt-image-2";
|
|
22701
|
+
}
|
|
22702
|
+
const supportsRef = () => true;
|
|
22703
|
+
return {
|
|
22704
|
+
name: "azure",
|
|
22705
|
+
defaultModel,
|
|
22706
|
+
isAvailable: () => (env.AZURE_OPENAI_API_KEY ?? "") !== "",
|
|
22707
|
+
supportsReferenceImages: supportsRef,
|
|
22708
|
+
validate(req) {
|
|
22709
|
+
validateReferenceImages(req.referenceImages ?? []);
|
|
22231
22710
|
},
|
|
22232
|
-
|
|
22233
|
-
|
|
22234
|
-
|
|
22711
|
+
async generate(req) {
|
|
22712
|
+
const apiKey = getApiKey(env);
|
|
22713
|
+
const endpoint = azureEndpoint(env);
|
|
22714
|
+
const deployment = req.model.trim();
|
|
22715
|
+
if (deployment === "") {
|
|
22716
|
+
throw new Error("Azure deployment name is required. Use --model <deployment>, AZURE_OPENAI_DEPLOYMENT, AZURE_OPENAI_IMAGE_MODEL, or embed the deployment in AZURE_OPENAI_BASE_URL.");
|
|
22717
|
+
}
|
|
22718
|
+
const size = (req.width !== undefined && req.height !== undefined ? `${req.width}x${req.height}` : undefined) ?? getOpenAISize(req.model, req.aspectRatio ?? null, req.quality);
|
|
22719
|
+
const refs = req.referenceImages ?? [];
|
|
22720
|
+
const bytes = await (async () => {
|
|
22721
|
+
if (refs.length > 0) {
|
|
22722
|
+
const form = new FormData;
|
|
22723
|
+
form.append("prompt", req.prompt);
|
|
22724
|
+
form.append("size", size);
|
|
22725
|
+
form.append("n", "1");
|
|
22726
|
+
form.append("quality", getAzureQuality(req.quality));
|
|
22727
|
+
for (const refPath of refs) {
|
|
22728
|
+
const fileBytes = await readBytes(refPath);
|
|
22729
|
+
const filename = refPath.split("/").pop() ?? refPath;
|
|
22730
|
+
const mimeType = extname3(filename).toLowerCase() === ".png" ? "image/png" : "image/jpeg";
|
|
22731
|
+
form.append("image[]", new Blob([fileBytes], { type: mimeType }), filename);
|
|
22732
|
+
}
|
|
22733
|
+
const res2 = await fetchFn(buildURL(endpoint, deployment, "/images/edits", env), {
|
|
22734
|
+
method: "POST",
|
|
22735
|
+
headers: { "api-key": apiKey },
|
|
22736
|
+
body: form
|
|
22737
|
+
});
|
|
22738
|
+
if (!res2.ok)
|
|
22739
|
+
throw new Error(`Azure OpenAI edits API error: ${await res2.text()}`);
|
|
22740
|
+
return extractImageFromResponse2(await res2.json(), fetchFn);
|
|
22741
|
+
}
|
|
22742
|
+
const res = await fetchFn(buildURL(endpoint, deployment, "/images/generations", env), {
|
|
22743
|
+
method: "POST",
|
|
22744
|
+
headers: { "content-type": "application/json", "api-key": apiKey },
|
|
22745
|
+
body: JSON.stringify({ prompt: req.prompt, size, n: 1, quality: getAzureQuality(req.quality) })
|
|
22746
|
+
});
|
|
22747
|
+
if (!res.ok)
|
|
22748
|
+
throw new Error(`Azure OpenAI API error: ${await res.text()}`);
|
|
22749
|
+
return extractImageFromResponse2(await res.json(), fetchFn);
|
|
22750
|
+
})();
|
|
22751
|
+
return { bytes, extension: "png", provider: "azure", model: req.model };
|
|
22752
|
+
}
|
|
22753
|
+
};
|
|
22754
|
+
};
|
|
22755
|
+
|
|
22756
|
+
// ../../plugins/generations/image-gen/src/providers/codex-cli.ts
|
|
22757
|
+
import { spawn } from "child_process";
|
|
22758
|
+
import { randomBytes } from "crypto";
|
|
22759
|
+
import { access as access2, mkdir, rm, writeFile as writeFile2 } from "fs/promises";
|
|
22760
|
+
import { tmpdir } from "os";
|
|
22761
|
+
import { resolve as resolve2 } from "path";
|
|
22762
|
+
var DEFAULT_MODEL4 = "codex-image-gen";
|
|
22763
|
+
async function exists(filePath) {
|
|
22764
|
+
try {
|
|
22765
|
+
await access2(filePath);
|
|
22766
|
+
return true;
|
|
22767
|
+
} catch {
|
|
22768
|
+
return false;
|
|
22769
|
+
}
|
|
22770
|
+
}
|
|
22771
|
+
async function resolveWrapperPath(readEnv) {
|
|
22772
|
+
const override = readEnv("BAOYU_CODEX_IMAGEGEN_BIN");
|
|
22773
|
+
if (override.length > 0) {
|
|
22774
|
+
if (!await exists(override)) {
|
|
22775
|
+
throw new Error(`Invalid BAOYU_CODEX_IMAGEGEN_BIN: ${override} does not exist.`);
|
|
22235
22776
|
}
|
|
22777
|
+
return override;
|
|
22778
|
+
}
|
|
22779
|
+
throw new Error("codex-cli wrapper not found. Set BAOYU_CODEX_IMAGEGEN_BIN to a codex-imagegen main.ts (or .sh) path.");
|
|
22780
|
+
}
|
|
22781
|
+
function spawnWrapper(wrapperPath, cliArgs) {
|
|
22782
|
+
return new Promise((resolveSpawn, rejectSpawn) => {
|
|
22783
|
+
const isTs = wrapperPath.endsWith(".ts");
|
|
22784
|
+
const command = isTs ? "bun" : wrapperPath;
|
|
22785
|
+
const args = isTs ? [wrapperPath, ...cliArgs] : cliArgs;
|
|
22786
|
+
const child = spawn(command, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
22787
|
+
let stdout = "";
|
|
22788
|
+
let stderr = "";
|
|
22789
|
+
child.stdout.on("data", (chunk) => {
|
|
22790
|
+
stdout += chunk.toString("utf8");
|
|
22791
|
+
});
|
|
22792
|
+
child.stderr.on("data", (chunk) => {
|
|
22793
|
+
const text = chunk.toString("utf8");
|
|
22794
|
+
stderr += text;
|
|
22795
|
+
});
|
|
22796
|
+
child.on("error", (err) => rejectSpawn(err));
|
|
22797
|
+
child.on("close", (code) => resolveSpawn({ stdout, stderr, code: code ?? 1 }));
|
|
22236
22798
|
});
|
|
22237
22799
|
}
|
|
22238
|
-
function
|
|
22239
|
-
|
|
22800
|
+
function parseWrapperJson(stdout) {
|
|
22801
|
+
const trimmed = stdout.trim();
|
|
22802
|
+
if (trimmed === "")
|
|
22803
|
+
throw new Error("Invalid codex-cli response: empty stdout from wrapper.");
|
|
22804
|
+
const lastLine = trimmed.split(/\r?\n/).pop() ?? trimmed;
|
|
22805
|
+
try {
|
|
22806
|
+
return JSON.parse(lastLine);
|
|
22807
|
+
} catch (parseErr) {
|
|
22808
|
+
throw new Error(`Invalid codex-cli response: could not parse JSON from wrapper stdout (${parseErr instanceof Error ? parseErr.message : String(parseErr)}).`);
|
|
22809
|
+
}
|
|
22810
|
+
}
|
|
22811
|
+
function parsePositiveInt(value) {
|
|
22812
|
+
if (value === "")
|
|
22813
|
+
return null;
|
|
22814
|
+
const parsed = Number.parseInt(value, 10);
|
|
22815
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
|
|
22816
|
+
}
|
|
22817
|
+
var createCodexCli = (options) => {
|
|
22818
|
+
const env = options?.env ?? process.env;
|
|
22819
|
+
const readEnv = (key) => env[key] ?? "";
|
|
22820
|
+
return {
|
|
22821
|
+
name: "codex-cli",
|
|
22822
|
+
defaultModel: () => DEFAULT_MODEL4,
|
|
22823
|
+
isAvailable: () => false,
|
|
22824
|
+
supportsReferenceImages: () => true,
|
|
22825
|
+
validate(req) {
|
|
22826
|
+
if ((req.n ?? 1) > 1) {
|
|
22827
|
+
throw new Error("codex-cli provider supports only n=1 (Codex image_gen returns a single image per call).");
|
|
22828
|
+
}
|
|
22829
|
+
},
|
|
22830
|
+
async generate(req) {
|
|
22831
|
+
const wrapperPath = await resolveWrapperPath(readEnv);
|
|
22832
|
+
const sessionDir = resolve2(tmpdir(), "kk-image-gen-codex-cli");
|
|
22833
|
+
await mkdir(sessionDir, { recursive: true });
|
|
22834
|
+
const token = randomBytes(8).toString("hex");
|
|
22835
|
+
const tmpOutput = resolve2(sessionDir, `out-${token}.png`);
|
|
22836
|
+
const tmpPrompt = resolve2(sessionDir, `prompt-${token}.md`);
|
|
22837
|
+
await writeFile2(tmpPrompt, req.prompt, "utf8");
|
|
22838
|
+
const cliArgs = [
|
|
22839
|
+
"--image",
|
|
22840
|
+
tmpOutput,
|
|
22841
|
+
"--prompt-file",
|
|
22842
|
+
tmpPrompt,
|
|
22843
|
+
"--aspect",
|
|
22844
|
+
req.aspectRatio ?? "1:1"
|
|
22845
|
+
];
|
|
22846
|
+
for (const ref of req.referenceImages ?? []) {
|
|
22847
|
+
cliArgs.push("--ref", resolve2(ref));
|
|
22848
|
+
}
|
|
22849
|
+
const cacheDir = readEnv("BAOYU_CODEX_IMAGEGEN_CACHE_DIR");
|
|
22850
|
+
if (cacheDir !== "")
|
|
22851
|
+
cliArgs.push("--cache-dir", cacheDir);
|
|
22852
|
+
const timeoutMs = parsePositiveInt(readEnv("BAOYU_CODEX_IMAGEGEN_TIMEOUT_MS"));
|
|
22853
|
+
if (timeoutMs !== null)
|
|
22854
|
+
cliArgs.push("--timeout", String(timeoutMs));
|
|
22855
|
+
const retries = parsePositiveInt(readEnv("BAOYU_CODEX_IMAGEGEN_RETRIES"));
|
|
22856
|
+
if (retries !== null)
|
|
22857
|
+
cliArgs.push("--retries", String(retries));
|
|
22858
|
+
const logFile = readEnv("BAOYU_CODEX_IMAGEGEN_LOG_FILE");
|
|
22859
|
+
if (logFile !== "")
|
|
22860
|
+
cliArgs.push("--log-file", logFile);
|
|
22861
|
+
try {
|
|
22862
|
+
const spawnResult = await spawnWrapper(wrapperPath, cliArgs);
|
|
22863
|
+
const parsed = parseWrapperJson(spawnResult.stdout);
|
|
22864
|
+
if (parsed.status === "error") {
|
|
22865
|
+
throw new Error(`Invalid codex-cli result (${parsed.error_kind}): ${parsed.error}`);
|
|
22866
|
+
}
|
|
22867
|
+
if (spawnResult.code !== 0) {
|
|
22868
|
+
throw new Error(`Invalid codex-cli result: wrapper exited with code ${spawnResult.code} despite reporting status=ok.`);
|
|
22869
|
+
}
|
|
22870
|
+
const bytes = await readBytes(parsed.path ?? tmpOutput);
|
|
22871
|
+
return { bytes, extension: "png", provider: "codex-cli", model: req.model };
|
|
22872
|
+
} finally {
|
|
22873
|
+
await Promise.allSettled([rm(tmpOutput, { force: true }), rm(tmpPrompt, { force: true })]);
|
|
22874
|
+
}
|
|
22875
|
+
}
|
|
22876
|
+
};
|
|
22877
|
+
};
|
|
22878
|
+
|
|
22879
|
+
// ../../plugins/generations/image-gen/src/providers/dashscope.ts
|
|
22880
|
+
init_dist();
|
|
22881
|
+
import { Buffer as Buffer5 } from "buffer";
|
|
22882
|
+
var DEFAULT_MODEL5 = "qwen-image-2.0-pro";
|
|
22883
|
+
var MIN_QWEN_2_TOTAL_PIXELS = 512 * 512;
|
|
22884
|
+
var MAX_QWEN_2_TOTAL_PIXELS = 2048 * 2048;
|
|
22885
|
+
var SIZE_STEP = 16;
|
|
22886
|
+
var QWEN_NEGATIVE_PROMPT = "\u4F4E\u5206\u8FA8\u7387\uFF0C\u4F4E\u753B\u8D28\uFF0C\u80A2\u4F53\u7578\u5F62\uFF0C\u624B\u6307\u7578\u5F62\uFF0C\u753B\u9762\u8FC7\u9971\u548C\uFF0C\u8721\u50CF\u611F\uFF0C\u4EBA\u8138\u65E0\u7EC6\u8282\uFF0C\u8FC7\u5EA6\u5149\u6ED1\uFF0C\u753B\u9762\u5177\u6709AI\u611F\uFF0C\u6784\u56FE\u6DF7\u4E71\uFF0C\u6587\u5B57\u6A21\u7CCA\uFF0C\u626D\u66F2";
|
|
22887
|
+
var QWEN_2_TARGET_PIXELS = { normal: 1024 * 1024, "2k": 1536 * 1536 };
|
|
22888
|
+
var MIN_WAN27_TOTAL_PIXELS = 768 * 768;
|
|
22889
|
+
var MAX_WAN27_PRO_T2I_PIXELS = 4096 * 4096;
|
|
22890
|
+
var MAX_WAN27_GENERAL_PIXELS = 2048 * 2048;
|
|
22891
|
+
var WAN27_MAX_REFERENCE_IMAGES = 9;
|
|
22892
|
+
var WAN27_TARGET_PIXELS = { normal: 1024 * 1024, "2k": 2048 * 2048 };
|
|
22893
|
+
var QWEN_2_RECOMMENDED = {
|
|
22894
|
+
"1:1": { normal: "1024*1024", "2k": "1536*1536" },
|
|
22895
|
+
"2:3": { normal: "768*1152", "2k": "1024*1536" },
|
|
22896
|
+
"3:2": { normal: "1152*768", "2k": "1536*1024" },
|
|
22897
|
+
"3:4": { normal: "960*1280", "2k": "1080*1440" },
|
|
22898
|
+
"4:3": { normal: "1280*960", "2k": "1440*1080" },
|
|
22899
|
+
"9:16": { normal: "720*1280", "2k": "1080*1920" },
|
|
22900
|
+
"16:9": { normal: "1280*720", "2k": "1920*1080" },
|
|
22901
|
+
"21:9": { normal: "1344*576", "2k": "2048*872" }
|
|
22902
|
+
};
|
|
22903
|
+
var QWEN_FIXED_SIZES_BY_RATIO = {
|
|
22904
|
+
"16:9": "1664*928",
|
|
22905
|
+
"4:3": "1472*1104",
|
|
22906
|
+
"1:1": "1328*1328",
|
|
22907
|
+
"3:4": "1104*1472",
|
|
22908
|
+
"9:16": "928*1664"
|
|
22909
|
+
};
|
|
22910
|
+
var QWEN_FIXED_SPEC_DEFAULT = QWEN_FIXED_SIZES_BY_RATIO["16:9"];
|
|
22911
|
+
var LEGACY_STANDARD_SIZES = [
|
|
22912
|
+
[1024, 1024],
|
|
22913
|
+
[1280, 720],
|
|
22914
|
+
[720, 1280],
|
|
22915
|
+
[1024, 768],
|
|
22916
|
+
[768, 1024],
|
|
22917
|
+
[1536, 1024],
|
|
22918
|
+
[1024, 1536],
|
|
22919
|
+
[1536, 864],
|
|
22920
|
+
[864, 1536]
|
|
22921
|
+
];
|
|
22922
|
+
var LEGACY_STANDARD_SIZES_2K = [
|
|
22923
|
+
[1536, 1536],
|
|
22924
|
+
[2048, 1152],
|
|
22925
|
+
[1152, 2048],
|
|
22926
|
+
[1536, 1024],
|
|
22927
|
+
[1024, 1536],
|
|
22928
|
+
[1536, 864],
|
|
22929
|
+
[864, 1536],
|
|
22930
|
+
[2048, 2048]
|
|
22931
|
+
];
|
|
22932
|
+
var MODEL_SPEC_ALIASES = {
|
|
22933
|
+
"qwen-image-2.0-pro": { family: "qwen2", defaultSize: "1024*1024" },
|
|
22934
|
+
"qwen-image-2.0-pro-2026-03-03": { family: "qwen2", defaultSize: "1024*1024" },
|
|
22935
|
+
"qwen-image-2.0": { family: "qwen2", defaultSize: "1024*1024" },
|
|
22936
|
+
"qwen-image-2.0-2026-03-03": { family: "qwen2", defaultSize: "1024*1024" },
|
|
22937
|
+
"qwen-image-max": { family: "qwenFixed", defaultSize: QWEN_FIXED_SPEC_DEFAULT },
|
|
22938
|
+
"qwen-image-max-2025-12-30": { family: "qwenFixed", defaultSize: QWEN_FIXED_SPEC_DEFAULT },
|
|
22939
|
+
"qwen-image-plus": { family: "qwenFixed", defaultSize: QWEN_FIXED_SPEC_DEFAULT },
|
|
22940
|
+
"qwen-image-plus-2026-01-09": { family: "qwenFixed", defaultSize: QWEN_FIXED_SPEC_DEFAULT },
|
|
22941
|
+
"qwen-image": { family: "qwenFixed", defaultSize: QWEN_FIXED_SPEC_DEFAULT },
|
|
22942
|
+
"wan2.7-image-pro": { family: "wan27", defaultSize: "2048*2048" },
|
|
22943
|
+
"wan2.7-image": { family: "wan27", defaultSize: "2048*2048" }
|
|
22944
|
+
};
|
|
22945
|
+
function getModelSpec(model) {
|
|
22946
|
+
return MODEL_SPEC_ALIASES[model.trim().toLowerCase()] ?? { family: "legacy", defaultSize: "1536*1536" };
|
|
22947
|
+
}
|
|
22948
|
+
function resolveSizeForModel(model, args) {
|
|
22949
|
+
const spec = getModelSpec(model);
|
|
22950
|
+
const referenceCount = args.referenceImages?.length ?? 0;
|
|
22951
|
+
const quality = args.quality === "normal" ? "normal" : "2k";
|
|
22952
|
+
if (spec.family === "wan27") {
|
|
22953
|
+
const maxPixels = isWan27ProModel(model) && referenceCount === 0 ? MAX_WAN27_PRO_T2I_PIXELS : MAX_WAN27_GENERAL_PIXELS;
|
|
22954
|
+
if (args.size !== undefined)
|
|
22955
|
+
return validateWan27Size(args.size, maxPixels, model);
|
|
22956
|
+
return getWan27SizeFromAspectRatio(args.aspectRatio, quality, maxPixels);
|
|
22957
|
+
}
|
|
22958
|
+
if (args.size !== undefined) {
|
|
22959
|
+
if (spec.family === "qwen2")
|
|
22960
|
+
return validateQwen2Size(args.size);
|
|
22961
|
+
if (spec.family === "qwenFixed")
|
|
22962
|
+
return validateQwenFixedSize(args.size);
|
|
22963
|
+
validateSizeFormat(args.size);
|
|
22964
|
+
return normalizeSize(args.size);
|
|
22965
|
+
}
|
|
22966
|
+
if (spec.family === "qwen2")
|
|
22967
|
+
return getQwen2SizeFromAspectRatio(args.aspectRatio, quality);
|
|
22968
|
+
if (spec.family === "qwenFixed")
|
|
22969
|
+
return getQwenFixedSizeFromAspectRatio(args.aspectRatio, quality);
|
|
22970
|
+
return getSizeFromAspectRatio(args.aspectRatio, quality);
|
|
22971
|
+
}
|
|
22972
|
+
function getSizeFromAspectRatio(ar, quality) {
|
|
22973
|
+
const sizes = quality === "2k" ? LEGACY_STANDARD_SIZES_2K : LEGACY_STANDARD_SIZES;
|
|
22974
|
+
const defaultSize = quality === "2k" ? "1536*1536" : "1024*1024";
|
|
22975
|
+
const parsed = ar === undefined ? null : parseAspectRatio3(ar);
|
|
22976
|
+
if (parsed === null)
|
|
22977
|
+
return defaultSize;
|
|
22978
|
+
const targetRatio = parsed.width / parsed.height;
|
|
22979
|
+
let best = defaultSize;
|
|
22980
|
+
let bestDiff = Number.POSITIVE_INFINITY;
|
|
22981
|
+
for (const [width, height] of sizes) {
|
|
22982
|
+
const diff = Math.abs(width / height - targetRatio);
|
|
22983
|
+
if (diff < bestDiff) {
|
|
22984
|
+
bestDiff = diff;
|
|
22985
|
+
best = `${width}*${height}`;
|
|
22986
|
+
}
|
|
22987
|
+
}
|
|
22988
|
+
return best;
|
|
22989
|
+
}
|
|
22990
|
+
function getQwen2SizeFromAspectRatio(ar, quality) {
|
|
22991
|
+
if (ar === undefined) {
|
|
22992
|
+
return QWEN_2_RECOMMENDED["1:1"]?.[quality];
|
|
22993
|
+
}
|
|
22994
|
+
const recommendedRatio = findKnownRatioKey(ar, Object.keys(QWEN_2_RECOMMENDED));
|
|
22995
|
+
if (recommendedRatio !== null) {
|
|
22996
|
+
return QWEN_2_RECOMMENDED[recommendedRatio]?.[quality];
|
|
22997
|
+
}
|
|
22998
|
+
const parsed = parseAspectRatio3(ar);
|
|
22999
|
+
if (parsed === null) {
|
|
23000
|
+
return QWEN_2_RECOMMENDED["1:1"]?.[quality];
|
|
23001
|
+
}
|
|
23002
|
+
const targetRatio = parsed.width / parsed.height;
|
|
23003
|
+
const targetPixels = QWEN_2_TARGET_PIXELS[quality];
|
|
23004
|
+
const rawWidth = Math.sqrt(targetPixels * targetRatio);
|
|
23005
|
+
const rawHeight = Math.sqrt(targetPixels / targetRatio);
|
|
23006
|
+
const fitted = fitToPixelBudget(rawWidth, rawHeight, MIN_QWEN_2_TOTAL_PIXELS, MAX_QWEN_2_TOTAL_PIXELS);
|
|
23007
|
+
return `${fitted.width}*${fitted.height}`;
|
|
23008
|
+
}
|
|
23009
|
+
function findKnownRatioKey(ar, candidates, tolerance = 0.02) {
|
|
23010
|
+
const parsed = parseAspectRatio3(ar);
|
|
23011
|
+
if (parsed === null)
|
|
23012
|
+
return null;
|
|
23013
|
+
const targetRatio = parsed.width / parsed.height;
|
|
23014
|
+
let bestKey = null;
|
|
23015
|
+
let bestDiff = Number.POSITIVE_INFINITY;
|
|
23016
|
+
for (const candidate of candidates) {
|
|
23017
|
+
const candidateParsed = parseAspectRatio3(candidate);
|
|
23018
|
+
if (candidateParsed === null)
|
|
23019
|
+
continue;
|
|
23020
|
+
const diff = Math.abs(candidateParsed.width / candidateParsed.height - targetRatio);
|
|
23021
|
+
if (diff < bestDiff) {
|
|
23022
|
+
bestDiff = diff;
|
|
23023
|
+
bestKey = candidate;
|
|
23024
|
+
}
|
|
23025
|
+
}
|
|
23026
|
+
return bestDiff <= tolerance ? bestKey : null;
|
|
23027
|
+
}
|
|
23028
|
+
function fitToPixelBudget(width, height, minPixels, maxPixels) {
|
|
23029
|
+
let nextWidth = width;
|
|
23030
|
+
let nextHeight = height;
|
|
23031
|
+
let pixels = nextWidth * nextHeight;
|
|
23032
|
+
if (pixels > maxPixels) {
|
|
23033
|
+
const scale = Math.sqrt(maxPixels / pixels);
|
|
23034
|
+
nextWidth *= scale;
|
|
23035
|
+
nextHeight *= scale;
|
|
23036
|
+
} else if (pixels < minPixels) {
|
|
23037
|
+
const scale = Math.sqrt(minPixels / pixels);
|
|
23038
|
+
nextWidth *= scale;
|
|
23039
|
+
nextHeight *= scale;
|
|
23040
|
+
}
|
|
23041
|
+
let roundedWidth = roundToStep(nextWidth);
|
|
23042
|
+
let roundedHeight = roundToStep(nextHeight);
|
|
23043
|
+
pixels = roundedWidth * roundedHeight;
|
|
23044
|
+
while (pixels > maxPixels && (roundedWidth > SIZE_STEP || roundedHeight > SIZE_STEP)) {
|
|
23045
|
+
if (roundedWidth >= roundedHeight && roundedWidth > SIZE_STEP) {
|
|
23046
|
+
roundedWidth -= SIZE_STEP;
|
|
23047
|
+
} else if (roundedHeight > SIZE_STEP) {
|
|
23048
|
+
roundedHeight -= SIZE_STEP;
|
|
23049
|
+
} else {
|
|
23050
|
+
break;
|
|
23051
|
+
}
|
|
23052
|
+
pixels = roundedWidth * roundedHeight;
|
|
23053
|
+
}
|
|
23054
|
+
while (pixels < minPixels) {
|
|
23055
|
+
if (roundedWidth <= roundedHeight) {
|
|
23056
|
+
roundedWidth += SIZE_STEP;
|
|
23057
|
+
} else {
|
|
23058
|
+
roundedHeight += SIZE_STEP;
|
|
23059
|
+
}
|
|
23060
|
+
pixels = roundedWidth * roundedHeight;
|
|
23061
|
+
}
|
|
23062
|
+
return { width: roundedWidth, height: roundedHeight };
|
|
23063
|
+
}
|
|
23064
|
+
function roundToStep(value) {
|
|
23065
|
+
return Math.max(SIZE_STEP, Math.round(value / SIZE_STEP) * SIZE_STEP);
|
|
23066
|
+
}
|
|
23067
|
+
function floorToStep(value) {
|
|
23068
|
+
return Math.max(SIZE_STEP, Math.floor(value / SIZE_STEP) * SIZE_STEP);
|
|
23069
|
+
}
|
|
23070
|
+
function getWan27SizeFromAspectRatio(ar, quality, maxPixels) {
|
|
23071
|
+
const targetPixels = Math.min(WAN27_TARGET_PIXELS[quality], maxPixels);
|
|
23072
|
+
if (ar === undefined) {
|
|
23073
|
+
const side = roundToStep(Math.sqrt(targetPixels));
|
|
23074
|
+
return `${side}*${side}`;
|
|
23075
|
+
}
|
|
23076
|
+
const parsed = parseAspectRatio3(ar);
|
|
23077
|
+
if (parsed === null) {
|
|
23078
|
+
const side = roundToStep(Math.sqrt(targetPixels));
|
|
23079
|
+
return `${side}*${side}`;
|
|
23080
|
+
}
|
|
23081
|
+
const ratio = parsed.width / parsed.height;
|
|
23082
|
+
if (ratio < 1 / 8 || ratio > 8) {
|
|
23083
|
+
throw new Error(`DashScope wan2.7 image models support aspect ratios in [1:8, 8:1]. Received "${ar}".`);
|
|
23084
|
+
}
|
|
23085
|
+
const rawWidth = Math.sqrt(targetPixels * ratio);
|
|
23086
|
+
const rawHeight = Math.sqrt(targetPixels / ratio);
|
|
23087
|
+
const fitted = fitToPixelBudget(rawWidth, rawHeight, MIN_WAN27_TOTAL_PIXELS, maxPixels);
|
|
23088
|
+
const bounded = clampWan27DerivedSizeToRatioBounds(fitted);
|
|
23089
|
+
return `${bounded.width}*${bounded.height}`;
|
|
23090
|
+
}
|
|
23091
|
+
function clampWan27DerivedSizeToRatioBounds(size) {
|
|
23092
|
+
let { width, height } = size;
|
|
23093
|
+
const ratio = width / height;
|
|
23094
|
+
if (ratio > 8) {
|
|
23095
|
+
width = floorToStep(height * 8);
|
|
23096
|
+
} else if (ratio < 1 / 8) {
|
|
23097
|
+
height = floorToStep(width * 8);
|
|
23098
|
+
}
|
|
23099
|
+
return { width, height };
|
|
23100
|
+
}
|
|
23101
|
+
function isWan27ProModel(model) {
|
|
23102
|
+
return model.trim().toLowerCase() === "wan2.7-image-pro";
|
|
23103
|
+
}
|
|
23104
|
+
function normalizeSize(size) {
|
|
23105
|
+
return size.replace("x", "*");
|
|
23106
|
+
}
|
|
23107
|
+
function parseAspectRatio3(ar) {
|
|
23108
|
+
const match = ar.match(/^(\d+(?:\.\d+)?):(\d+(?:\.\d+)?)$/);
|
|
23109
|
+
if (match === null)
|
|
23110
|
+
return null;
|
|
23111
|
+
const w = Number.parseFloat(match[1]);
|
|
23112
|
+
const h = Number.parseFloat(match[2]);
|
|
23113
|
+
if (w <= 0 || h <= 0)
|
|
23114
|
+
return null;
|
|
23115
|
+
return { width: w, height: h };
|
|
23116
|
+
}
|
|
23117
|
+
function parseSize(size) {
|
|
23118
|
+
const match = normalizeSize(size).match(/^(\d+)\*(\d+)$/);
|
|
23119
|
+
if (match === null)
|
|
23120
|
+
return null;
|
|
23121
|
+
const width = Number(match[1]);
|
|
23122
|
+
const height = Number(match[2]);
|
|
23123
|
+
if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0)
|
|
23124
|
+
return null;
|
|
23125
|
+
return { width, height };
|
|
23126
|
+
}
|
|
23127
|
+
function validateSizeFormat(size) {
|
|
23128
|
+
const parsed = parseSize(size);
|
|
23129
|
+
if (parsed === null) {
|
|
23130
|
+
throw new Error(`Invalid DashScope size "${size}". Expected <width>x<height> or <width>*<height>.`);
|
|
23131
|
+
}
|
|
23132
|
+
return parsed;
|
|
23133
|
+
}
|
|
23134
|
+
function validateQwen2Size(size) {
|
|
23135
|
+
const normalized = normalizeSize(size);
|
|
23136
|
+
const parsed = validateSizeFormat(normalized);
|
|
23137
|
+
const totalPixels = parsed.width * parsed.height;
|
|
23138
|
+
if (totalPixels < MIN_QWEN_2_TOTAL_PIXELS || totalPixels > MAX_QWEN_2_TOTAL_PIXELS) {
|
|
23139
|
+
throw new Error(`DashScope qwen-image-2.0* models require total pixels between ${MIN_QWEN_2_TOTAL_PIXELS} ` + `and ${MAX_QWEN_2_TOTAL_PIXELS}. Received ${normalized} (${totalPixels} pixels).`);
|
|
23140
|
+
}
|
|
23141
|
+
return normalized;
|
|
23142
|
+
}
|
|
23143
|
+
function validateQwenFixedSize(size) {
|
|
23144
|
+
const normalized = normalizeSize(size);
|
|
23145
|
+
validateSizeFormat(normalized);
|
|
23146
|
+
if (!Object.values(QWEN_FIXED_SIZES_BY_RATIO).includes(normalized)) {
|
|
23147
|
+
throw new Error(`DashScope qwen-image-max/plus/image models support only these sizes: ` + `${Object.values(QWEN_FIXED_SIZES_BY_RATIO).join(", ")}. Received ${normalized}.`);
|
|
23148
|
+
}
|
|
23149
|
+
return normalized;
|
|
23150
|
+
}
|
|
23151
|
+
function validateWan27Size(size, maxPixels, model) {
|
|
23152
|
+
const normalized = normalizeSize(size);
|
|
23153
|
+
const parsed = validateSizeFormat(normalized);
|
|
23154
|
+
const totalPixels = parsed.width * parsed.height;
|
|
23155
|
+
if (totalPixels < MIN_WAN27_TOTAL_PIXELS || totalPixels > maxPixels) {
|
|
23156
|
+
throw new Error(`DashScope ${model} requires total pixels between 768*768 and ` + `${maxPixels === MAX_WAN27_PRO_T2I_PIXELS ? "4096*4096" : "2048*2048"} for the current request. ` + `Received ${normalized} (${totalPixels} pixels).`);
|
|
23157
|
+
}
|
|
23158
|
+
const ratio = parsed.width / parsed.height;
|
|
23159
|
+
if (ratio < 1 / 8 || ratio > 8) {
|
|
23160
|
+
throw new Error(`DashScope wan2.7 image models support aspect ratios in [1:8, 8:1]. Received ${normalized} (ratio ${ratio.toFixed(3)}).`);
|
|
23161
|
+
}
|
|
23162
|
+
return normalized;
|
|
23163
|
+
}
|
|
23164
|
+
function getQwenFixedSizeFromAspectRatio(ar, quality) {
|
|
23165
|
+
if (quality === "normal") {
|
|
23166
|
+
echoError("DashScope qwen-image-max/plus/image models use fixed output sizes; quality does not change the resolution.");
|
|
23167
|
+
}
|
|
23168
|
+
if (ar === undefined)
|
|
23169
|
+
return QWEN_FIXED_SPEC_DEFAULT;
|
|
23170
|
+
const ratioKey = findKnownRatioKey(ar, Object.keys(QWEN_FIXED_SIZES_BY_RATIO));
|
|
23171
|
+
if (ratioKey === null) {
|
|
23172
|
+
throw new Error(`DashScope model supports only fixed ratios ${Object.keys(QWEN_FIXED_SIZES_BY_RATIO).join(", ")}. ` + `For custom ratios like "${ar}", use --model qwen-image-2.0-pro.`);
|
|
23173
|
+
}
|
|
23174
|
+
return QWEN_FIXED_SIZES_BY_RATIO[ratioKey];
|
|
23175
|
+
}
|
|
23176
|
+
function buildParameters(family, size) {
|
|
23177
|
+
if (family === "wan27") {
|
|
23178
|
+
return { size, n: 1, watermark: false };
|
|
23179
|
+
}
|
|
23180
|
+
const parameters = { prompt_extend: false, size };
|
|
23181
|
+
if (family === "qwen2" || family === "qwenFixed") {
|
|
23182
|
+
parameters.watermark = false;
|
|
23183
|
+
parameters.negative_prompt = QWEN_NEGATIVE_PROMPT;
|
|
23184
|
+
}
|
|
23185
|
+
return parameters;
|
|
23186
|
+
}
|
|
23187
|
+
async function extractImageFromResponse3(result, fetchFn) {
|
|
23188
|
+
let imageData = null;
|
|
23189
|
+
if (result.output?.result_image !== undefined) {
|
|
23190
|
+
imageData = result.output.result_image;
|
|
23191
|
+
} else if (result.output?.choices?.[0]?.message?.content !== undefined) {
|
|
23192
|
+
for (const item of result.output.choices[0].message.content) {
|
|
23193
|
+
if (item.image !== undefined) {
|
|
23194
|
+
imageData = item.image;
|
|
23195
|
+
break;
|
|
23196
|
+
}
|
|
23197
|
+
}
|
|
23198
|
+
}
|
|
23199
|
+
if (imageData === null)
|
|
23200
|
+
throw new Error("No image in response");
|
|
23201
|
+
if (imageData.startsWith("http://") || imageData.startsWith("https://")) {
|
|
23202
|
+
const imgRes = await fetchFn(imageData);
|
|
23203
|
+
if (!imgRes.ok)
|
|
23204
|
+
throw new Error("Failed to download image");
|
|
23205
|
+
return new Uint8Array(await imgRes.arrayBuffer());
|
|
23206
|
+
}
|
|
23207
|
+
return Uint8Array.from(Buffer5.from(imageData, "base64"));
|
|
23208
|
+
}
|
|
23209
|
+
var createDashscope = (options) => {
|
|
23210
|
+
const fetchFn = options?.fetch ?? fetch;
|
|
23211
|
+
const env = options?.env ?? process.env;
|
|
23212
|
+
const readEnv = (key) => env[key] ?? "";
|
|
23213
|
+
const supportsRef = (model) => model.includes("wan2.7");
|
|
23214
|
+
return {
|
|
23215
|
+
name: "dashscope",
|
|
23216
|
+
defaultModel: () => DEFAULT_MODEL5,
|
|
23217
|
+
isAvailable: () => readEnv("DASHSCOPE_API_KEY") !== "",
|
|
23218
|
+
supportsReferenceImages: supportsRef,
|
|
23219
|
+
validate(req) {
|
|
23220
|
+
const spec = getModelSpec(req.model);
|
|
23221
|
+
if ((req.referenceImages?.length ?? 0) > 0 && spec.family !== "wan27") {
|
|
23222
|
+
throw new Error("Reference images are not supported with this DashScope model. Use a wan2.7 image model (--model wan2.7-image-pro or wan2.7-image), or switch to --provider google with a Gemini multimodal model.");
|
|
23223
|
+
}
|
|
23224
|
+
},
|
|
23225
|
+
async generate(req) {
|
|
23226
|
+
const apiKey = readEnv("DASHSCOPE_API_KEY");
|
|
23227
|
+
if (apiKey === "")
|
|
23228
|
+
throw new Error("DASHSCOPE_API_KEY is required");
|
|
23229
|
+
const spec = getModelSpec(req.model);
|
|
23230
|
+
const refs = req.referenceImages ?? [];
|
|
23231
|
+
if (refs.length > 0 && spec.family !== "wan27") {
|
|
23232
|
+
throw new Error("Reference images are not supported with this DashScope model. Use a wan2.7 image model (--model wan2.7-image-pro or wan2.7-image), or switch to --provider google with a Gemini multimodal model.");
|
|
23233
|
+
}
|
|
23234
|
+
if (refs.length > WAN27_MAX_REFERENCE_IMAGES) {
|
|
23235
|
+
throw new Error(`DashScope wan2.7 image models accept at most ${WAN27_MAX_REFERENCE_IMAGES} reference images. Received ${refs.length}.`);
|
|
23236
|
+
}
|
|
23237
|
+
if (spec.family === "wan27" && (req.n ?? 1) !== 1) {
|
|
23238
|
+
throw new Error("DashScope wan2.7 image models in kk image-gen support exactly one output image per request (extra images would be billed but discarded). Remove --n or use --n 1.");
|
|
23239
|
+
}
|
|
23240
|
+
const size = resolveSizeForModel(req.model, {
|
|
23241
|
+
...req.width !== undefined && req.height !== undefined ? { size: `${req.width}x${req.height}` } : {},
|
|
23242
|
+
aspectRatio: req.aspectRatio,
|
|
23243
|
+
quality: req.quality,
|
|
23244
|
+
referenceImages: refs
|
|
23245
|
+
});
|
|
23246
|
+
const base = (readEnv("DASHSCOPE_BASE_URL") || "https://dashscope.aliyuncs.com").replace(/\/+$/g, "");
|
|
23247
|
+
const url2 = `${base}/api/v1/services/aigc/multimodal-generation/generation`;
|
|
23248
|
+
const content = [];
|
|
23249
|
+
if (spec.family === "wan27" && refs.length > 0) {
|
|
23250
|
+
for (const refPath of refs) {
|
|
23251
|
+
content.push({ image: await refToDataUri(refPath) });
|
|
23252
|
+
}
|
|
23253
|
+
}
|
|
23254
|
+
content.push({ text: req.prompt });
|
|
23255
|
+
const body = {
|
|
23256
|
+
model: req.model,
|
|
23257
|
+
input: { messages: [{ role: "user", content }] },
|
|
23258
|
+
parameters: buildParameters(spec.family, size)
|
|
23259
|
+
};
|
|
23260
|
+
const res = await fetchFn(url2, {
|
|
23261
|
+
method: "POST",
|
|
23262
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${apiKey}` },
|
|
23263
|
+
body: JSON.stringify(body)
|
|
23264
|
+
});
|
|
23265
|
+
if (!res.ok)
|
|
23266
|
+
throw new Error(`DashScope API error (${res.status}): ${await res.text()}`);
|
|
23267
|
+
const bytes = await extractImageFromResponse3(await res.json(), fetchFn);
|
|
23268
|
+
return { bytes, extension: "png", provider: "dashscope", model: req.model };
|
|
23269
|
+
}
|
|
23270
|
+
};
|
|
23271
|
+
};
|
|
23272
|
+
|
|
23273
|
+
// ../../plugins/generations/image-gen/src/providers/google.ts
|
|
23274
|
+
import { Buffer as Buffer6 } from "buffer";
|
|
23275
|
+
import { execFileSync } from "child_process";
|
|
23276
|
+
var GOOGLE_MULTIMODAL_MODELS = [
|
|
23277
|
+
"gemini-3-pro-image",
|
|
23278
|
+
"gemini-3.1-flash-image",
|
|
23279
|
+
"gemini-3-pro-image-preview",
|
|
23280
|
+
"gemini-3-flash-preview",
|
|
23281
|
+
"gemini-3.1-flash-image-preview"
|
|
23282
|
+
];
|
|
23283
|
+
var GOOGLE_IMAGEN_MODELS = ["imagen-3.0-generate-002", "imagen-3.0-generate-001"];
|
|
23284
|
+
var DEFAULT_MODEL6 = "gemini-3-pro-image";
|
|
23285
|
+
function normalizeGoogleModelId(model) {
|
|
23286
|
+
return model.startsWith("models/") ? model.slice("models/".length) : model;
|
|
23287
|
+
}
|
|
23288
|
+
function isGoogleMultimodal(model) {
|
|
23289
|
+
const normalized = normalizeGoogleModelId(model);
|
|
23290
|
+
return GOOGLE_MULTIMODAL_MODELS.some((fragment) => normalized.includes(fragment));
|
|
23291
|
+
}
|
|
23292
|
+
function isGoogleImagen(model) {
|
|
23293
|
+
const normalized = normalizeGoogleModelId(model);
|
|
23294
|
+
return GOOGLE_IMAGEN_MODELS.some((fragment) => normalized.includes(fragment));
|
|
23295
|
+
}
|
|
23296
|
+
function buildGoogleUrl(base, pathname) {
|
|
23297
|
+
const cleaned = base.replace(/\/+$/g, "").replace(/^\/+/g, "");
|
|
23298
|
+
const cleanedPath = pathname.replace(/^\/+/g, "");
|
|
23299
|
+
if (cleaned.endsWith("/v1beta"))
|
|
23300
|
+
return `${cleaned}/${cleanedPath}`;
|
|
23301
|
+
return `${cleaned}/v1beta/${cleanedPath}`;
|
|
23302
|
+
}
|
|
23303
|
+
function buildPromptWithAspect(prompt, aspectRatio, quality) {
|
|
23304
|
+
let result = prompt;
|
|
23305
|
+
if (aspectRatio !== undefined)
|
|
23306
|
+
result += ` Aspect ratio: ${aspectRatio}.`;
|
|
23307
|
+
if (quality === "2k")
|
|
23308
|
+
result += " High resolution 2048px.";
|
|
23309
|
+
return result;
|
|
23310
|
+
}
|
|
23311
|
+
function extractInlineImageData(response) {
|
|
23312
|
+
for (const candidate of response.candidates ?? []) {
|
|
23313
|
+
for (const part of candidate.content?.parts ?? []) {
|
|
23314
|
+
const data = part.inlineData?.data;
|
|
23315
|
+
if (typeof data === "string" && data.length > 0)
|
|
23316
|
+
return data;
|
|
23317
|
+
}
|
|
23318
|
+
}
|
|
23319
|
+
return null;
|
|
23320
|
+
}
|
|
23321
|
+
function extractPredictedImageData(response) {
|
|
23322
|
+
for (const candidate of [...response.predictions ?? [], ...response.generatedImages ?? []]) {
|
|
23323
|
+
if (candidate === null || typeof candidate !== "object")
|
|
23324
|
+
continue;
|
|
23325
|
+
const fields = [candidate.imageBytes, candidate.bytesBase64Encoded, candidate.data].flatMap((value) => typeof value === "string" && value.length > 0 ? [value] : []);
|
|
23326
|
+
if (fields.length > 0)
|
|
23327
|
+
return fields[0] ?? null;
|
|
23328
|
+
const image = candidate.image;
|
|
23329
|
+
if (image !== null && typeof image === "object") {
|
|
23330
|
+
const record2 = image;
|
|
23331
|
+
for (const value of [record2.imageBytes, record2.bytesBase64Encoded, record2.data]) {
|
|
23332
|
+
if (typeof value === "string" && value.length > 0)
|
|
23333
|
+
return value;
|
|
23334
|
+
}
|
|
23335
|
+
}
|
|
23336
|
+
}
|
|
23337
|
+
return null;
|
|
23338
|
+
}
|
|
23339
|
+
function resolveSize2(req) {
|
|
23340
|
+
if (req.width !== undefined && req.height !== undefined) {
|
|
23341
|
+
return Math.max(req.width, req.height) <= 1024 ? "1K" : Math.max(req.width, req.height) <= 2048 ? "2K" : "4K";
|
|
23342
|
+
}
|
|
23343
|
+
return;
|
|
23344
|
+
}
|
|
23345
|
+
function getHttpProxy(readEnv) {
|
|
23346
|
+
return readEnv("https_proxy") || readEnv("HTTPS_PROXY") || readEnv("http_proxy") || readEnv("HTTP_PROXY") || readEnv("ALL_PROXY") || null;
|
|
23347
|
+
}
|
|
23348
|
+
var createGoogle = (options) => {
|
|
23349
|
+
const fetchFn = options?.fetch ?? fetch;
|
|
23350
|
+
const env = options?.env ?? process.env;
|
|
23351
|
+
const readEnv = (key) => env[key] ?? "";
|
|
23352
|
+
const refModels = [
|
|
23353
|
+
"gemini-3-pro-image",
|
|
23354
|
+
"gemini-3.1-flash-image",
|
|
23355
|
+
"gemini-3-pro-image-preview",
|
|
23356
|
+
"gemini-3-flash-preview",
|
|
23357
|
+
"gemini-3.1-flash-image-preview"
|
|
23358
|
+
];
|
|
23359
|
+
const supportsRef = (model) => refModels.some((fragment) => normalizeGoogleModelId(model).includes(fragment));
|
|
23360
|
+
async function postGoogleJson(pathname, body) {
|
|
23361
|
+
const apiKey = readEnv("GOOGLE_API_KEY") || readEnv("GEMINI_API_KEY");
|
|
23362
|
+
if (apiKey === "")
|
|
23363
|
+
throw new Error("GOOGLE_API_KEY or GEMINI_API_KEY is required");
|
|
23364
|
+
const base = readEnv("GOOGLE_BASE_URL") || "https://generativelanguage.googleapis.com";
|
|
23365
|
+
const url2 = buildGoogleUrl(base, pathname);
|
|
23366
|
+
const proxy = getHttpProxy(readEnv);
|
|
23367
|
+
if (proxy !== null && options?.fetch === undefined) {
|
|
23368
|
+
const result = (() => {
|
|
23369
|
+
try {
|
|
23370
|
+
return execFileSync("curl", [
|
|
23371
|
+
"-s",
|
|
23372
|
+
"--connect-timeout",
|
|
23373
|
+
"30",
|
|
23374
|
+
"--max-time",
|
|
23375
|
+
"300",
|
|
23376
|
+
"-x",
|
|
23377
|
+
proxy,
|
|
23378
|
+
url2,
|
|
23379
|
+
"-H",
|
|
23380
|
+
"Content-Type: application/json",
|
|
23381
|
+
"-H",
|
|
23382
|
+
`x-goog-api-key: ${apiKey}`,
|
|
23383
|
+
"-d",
|
|
23384
|
+
"@-"
|
|
23385
|
+
], { input: JSON.stringify(body), encoding: "utf8", timeout: 310000 });
|
|
23386
|
+
} catch (error51) {
|
|
23387
|
+
const e = error51;
|
|
23388
|
+
const details = (typeof e.stderr === "string" ? e.stderr.trim() : "") || e.message || "curl request failed";
|
|
23389
|
+
throw new Error(`Google API request failed via curl: ${details}`);
|
|
23390
|
+
}
|
|
23391
|
+
})();
|
|
23392
|
+
const parsed = JSON.parse(result);
|
|
23393
|
+
if (parsed.error !== undefined) {
|
|
23394
|
+
throw new Error(`Google API error (${parsed.error.code}): ${parsed.error.message}`);
|
|
23395
|
+
}
|
|
23396
|
+
return parsed;
|
|
23397
|
+
}
|
|
23398
|
+
const res = await fetchFn(url2, {
|
|
23399
|
+
method: "POST",
|
|
23400
|
+
headers: { "content-type": "application/json", "x-goog-api-key": apiKey },
|
|
23401
|
+
body: JSON.stringify(body)
|
|
23402
|
+
});
|
|
23403
|
+
if (!res.ok) {
|
|
23404
|
+
const err = await res.text();
|
|
23405
|
+
throw new Error(`Google API error (${res.status}): ${err}`);
|
|
23406
|
+
}
|
|
23407
|
+
return await res.json();
|
|
23408
|
+
}
|
|
23409
|
+
async function generateWithGemini(req) {
|
|
23410
|
+
const promptWithAspect = buildPromptWithAspect(req.prompt, req.aspectRatio, req.quality);
|
|
23411
|
+
const parts = [];
|
|
23412
|
+
for (const refPath of req.referenceImages ?? []) {
|
|
23413
|
+
parts.push({
|
|
23414
|
+
inlineData: {
|
|
23415
|
+
data: Buffer6.from(await readBytes(refPath)).toString("base64"),
|
|
23416
|
+
mimeType: mimeForPath(refPath)
|
|
23417
|
+
}
|
|
23418
|
+
});
|
|
23419
|
+
}
|
|
23420
|
+
parts.push({ text: promptWithAspect });
|
|
23421
|
+
const response = await postGoogleJson(`models/${normalizeGoogleModelId(req.model)}:generateContent`, {
|
|
23422
|
+
contents: [{ role: "user", parts }],
|
|
23423
|
+
generationConfig: {
|
|
23424
|
+
responseModalities: ["IMAGE"],
|
|
23425
|
+
imageConfig: { imageSize: resolveSize2(req) ?? (req.quality === "2k" ? "2K" : "1K") }
|
|
23426
|
+
}
|
|
23427
|
+
});
|
|
23428
|
+
const imageData = extractInlineImageData(response);
|
|
23429
|
+
if (imageData !== null)
|
|
23430
|
+
return Uint8Array.from(Buffer6.from(imageData, "base64"));
|
|
23431
|
+
throw new Error("No image in response");
|
|
23432
|
+
}
|
|
23433
|
+
async function generateWithImagen(req) {
|
|
23434
|
+
const fullPrompt = buildPromptWithAspect(req.prompt, req.aspectRatio, req.quality);
|
|
23435
|
+
const imageSize = resolveSize2(req) ?? (req.quality === "2k" ? "2K" : "1K");
|
|
23436
|
+
const parameters = { sampleCount: req.n ?? 1 };
|
|
23437
|
+
if (req.aspectRatio !== undefined)
|
|
23438
|
+
parameters.aspectRatio = req.aspectRatio;
|
|
23439
|
+
parameters.imageSize = imageSize === "4K" ? "2K" : imageSize;
|
|
23440
|
+
const response = await postGoogleJson(`models/${normalizeGoogleModelId(req.model)}:predict`, {
|
|
23441
|
+
instances: [{ prompt: fullPrompt }],
|
|
23442
|
+
parameters
|
|
23443
|
+
});
|
|
23444
|
+
const imageData = extractPredictedImageData(response);
|
|
23445
|
+
if (imageData !== null)
|
|
23446
|
+
return Uint8Array.from(Buffer6.from(imageData, "base64"));
|
|
23447
|
+
throw new Error("No image in response");
|
|
23448
|
+
}
|
|
23449
|
+
async function generate(req) {
|
|
23450
|
+
const refs = req.referenceImages;
|
|
23451
|
+
if (isGoogleImagen(req.model)) {
|
|
23452
|
+
if (refs !== undefined && refs.length > 0) {
|
|
23453
|
+
throw new Error("Reference images are not supported with Imagen models. Use a Gemini multimodal model such as gemini-3-pro-image, gemini-3.1-flash-image, gemini-3-pro-image-preview, gemini-3-flash-preview, or gemini-3.1-flash-image-preview.");
|
|
23454
|
+
}
|
|
23455
|
+
return { bytes: await generateWithImagen(req), extension: "png", provider: "google", model: req.model };
|
|
23456
|
+
}
|
|
23457
|
+
if (!isGoogleMultimodal(req.model) && refs !== undefined && refs.length > 0) {
|
|
23458
|
+
throw new Error("Reference images are only supported with Gemini multimodal models such as gemini-3-pro-image, gemini-3.1-flash-image, gemini-3-pro-image-preview, gemini-3-flash-preview, or gemini-3.1-flash-image-preview.");
|
|
23459
|
+
}
|
|
23460
|
+
const bytes = await generateWithGemini(req);
|
|
23461
|
+
return { bytes, extension: "png", provider: "google", model: req.model };
|
|
23462
|
+
}
|
|
23463
|
+
return {
|
|
23464
|
+
name: "google",
|
|
23465
|
+
defaultModel: () => DEFAULT_MODEL6,
|
|
23466
|
+
isAvailable: () => (readEnv("GOOGLE_API_KEY") || readEnv("GEMINI_API_KEY")) !== "",
|
|
23467
|
+
supportsReferenceImages: supportsRef,
|
|
23468
|
+
validate(req) {
|
|
23469
|
+
if ((req.referenceImages?.length ?? 0) > 0 && !supportsRef(req.model)) {
|
|
23470
|
+
throw new Error(`google model '${req.model}' does not accept reference images; use a Gemini multimodal model`);
|
|
23471
|
+
}
|
|
23472
|
+
},
|
|
23473
|
+
generate
|
|
23474
|
+
};
|
|
23475
|
+
};
|
|
23476
|
+
|
|
23477
|
+
// ../../plugins/generations/image-gen/src/providers/huggingface.ts
|
|
23478
|
+
var DEFAULT_MODEL7 = "stabilityai/stable-diffusion-xl-base-1.0";
|
|
23479
|
+
var BASE_URL = "https://api-inference.huggingface.co/models";
|
|
23480
|
+
var buildPayload = (req) => ({
|
|
23481
|
+
inputs: req.prompt,
|
|
23482
|
+
parameters: {
|
|
23483
|
+
width: req.width ?? 1024,
|
|
23484
|
+
height: req.height ?? 1024,
|
|
23485
|
+
num_inference_steps: req.quality === "normal" ? 30 : 50
|
|
23486
|
+
}
|
|
23487
|
+
});
|
|
23488
|
+
var createHuggingface = (options) => {
|
|
23489
|
+
const fetchFn = options?.fetch ?? fetch;
|
|
23490
|
+
const env = options?.env ?? process.env;
|
|
23491
|
+
const readEnv = (key) => env[key] ?? "";
|
|
23492
|
+
return {
|
|
23493
|
+
name: "huggingface",
|
|
23494
|
+
defaultModel: () => readEnv("HUGGINGFACE_MODEL") || DEFAULT_MODEL7,
|
|
23495
|
+
isAvailable: () => readEnv("HUGGINGFACE_API_TOKEN") !== "",
|
|
23496
|
+
supportsReferenceImages: () => false,
|
|
23497
|
+
async generate(req) {
|
|
23498
|
+
const token = readEnv("HUGGINGFACE_API_TOKEN");
|
|
23499
|
+
if (token === "") {
|
|
23500
|
+
throw new Error("HUGGINGFACE_API_TOKEN is required. Get one at https://huggingface.co/settings/tokens");
|
|
23501
|
+
}
|
|
23502
|
+
const model = req.model.startsWith("hf:") ? req.model.slice(3) : req.model;
|
|
23503
|
+
const res = await fetchFn(`${BASE_URL}/${model}`, {
|
|
23504
|
+
method: "POST",
|
|
23505
|
+
headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
|
|
23506
|
+
body: JSON.stringify(buildPayload(req))
|
|
23507
|
+
});
|
|
23508
|
+
if (!res.ok) {
|
|
23509
|
+
const text = (await res.text()).slice(0, 200);
|
|
23510
|
+
if (res.status === 503)
|
|
23511
|
+
throw new Error(`HuggingFace model loading (HTTP 503): ${text}`);
|
|
23512
|
+
throw new Error(`HuggingFace HTTP ${res.status}: ${text}`);
|
|
23513
|
+
}
|
|
23514
|
+
const bytes = new Uint8Array(await res.arrayBuffer());
|
|
23515
|
+
const ct = res.headers.get("content-type") ?? "";
|
|
23516
|
+
const extension = ct.includes("jpeg") ? "jpg" : ct.includes("webp") ? "webp" : "png";
|
|
23517
|
+
return { bytes, extension, provider: "huggingface", model };
|
|
23518
|
+
}
|
|
23519
|
+
};
|
|
23520
|
+
};
|
|
23521
|
+
|
|
23522
|
+
// ../../plugins/generations/image-gen/src/providers/jimeng.ts
|
|
23523
|
+
import * as crypto from "crypto";
|
|
23524
|
+
var DEFAULT_MODEL8 = "jimeng_t2i_v40";
|
|
23525
|
+
var SIZE_PRESETS = {
|
|
23526
|
+
normal: {
|
|
23527
|
+
"1:1": "1024x1024",
|
|
23528
|
+
"4:3": "1360x1020",
|
|
23529
|
+
"16:9": "1536x864",
|
|
23530
|
+
"3:2": "1440x960",
|
|
23531
|
+
"21:9": "1920x824"
|
|
23532
|
+
},
|
|
23533
|
+
"2k": {
|
|
23534
|
+
"1:1": "2048x2048",
|
|
23535
|
+
"4:3": "2304x1728",
|
|
23536
|
+
"16:9": "2560x1440",
|
|
23537
|
+
"3:2": "2496x1664",
|
|
23538
|
+
"21:9": "3024x1296"
|
|
23539
|
+
},
|
|
23540
|
+
"4k": {
|
|
23541
|
+
"1:1": "4096x4096",
|
|
23542
|
+
"4:3": "4694x3520",
|
|
23543
|
+
"16:9": "5404x3040",
|
|
23544
|
+
"3:2": "4992x3328",
|
|
23545
|
+
"21:9": "6198x2656"
|
|
23546
|
+
}
|
|
23547
|
+
};
|
|
23548
|
+
function generateSignature(method, query, headers, body, accessKey, secretKey, region, service, canonicalUri) {
|
|
23549
|
+
const sortedQuery = Object.entries(query).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join("&");
|
|
23550
|
+
const sortedHeaders = Object.entries(headers).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => `${k.toLowerCase()}:${v.trim()}
|
|
23551
|
+
`).join("");
|
|
23552
|
+
const signedHeaders = Object.keys(headers).sort().map((k) => k.toLowerCase()).join(";");
|
|
23553
|
+
const hashedPayload = crypto.createHash("sha256").update(body, "utf8").digest("hex");
|
|
23554
|
+
const canonicalRequest = [method, canonicalUri, sortedQuery, sortedHeaders, signedHeaders, hashedPayload].join(`
|
|
23555
|
+
`);
|
|
23556
|
+
const hashedCanonicalRequest = crypto.createHash("sha256").update(canonicalRequest, "utf8").digest("hex");
|
|
23557
|
+
const algorithm = "HMAC-SHA256";
|
|
23558
|
+
const timestamp = headers["X-Date"] ?? headers["x-date"];
|
|
23559
|
+
if (timestamp === undefined)
|
|
23560
|
+
throw new Error("Jimeng signature generation requires an X-Date header.");
|
|
23561
|
+
const dateStamp = timestamp.slice(0, 8);
|
|
23562
|
+
const credentialScope = `${dateStamp}/${region}/${service}/request`;
|
|
23563
|
+
const stringToSign = [algorithm, timestamp, credentialScope, hashedCanonicalRequest].join(`
|
|
23564
|
+
`);
|
|
23565
|
+
const kDate = crypto.createHmac("sha256", secretKey).update(dateStamp).digest();
|
|
23566
|
+
const kRegion = crypto.createHmac("sha256", kDate).update(region).digest();
|
|
23567
|
+
const kService = crypto.createHmac("sha256", kRegion).update(service).digest();
|
|
23568
|
+
const kSigning = crypto.createHmac("sha256", kService).update("request").digest();
|
|
23569
|
+
const signature = crypto.createHmac("sha256", kSigning).update(stringToSign).digest("hex");
|
|
23570
|
+
return `${algorithm} Credential=${accessKey}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
|
|
23571
|
+
}
|
|
23572
|
+
function resolveEndpoint(baseUrl, query) {
|
|
23573
|
+
let parsed;
|
|
23574
|
+
try {
|
|
23575
|
+
parsed = new URL(baseUrl);
|
|
23576
|
+
} catch {
|
|
23577
|
+
throw new Error(`Invalid JIMENG_BASE_URL: ${baseUrl}`);
|
|
23578
|
+
}
|
|
23579
|
+
parsed.search = "";
|
|
23580
|
+
for (const [key, value] of Object.entries(query).sort(([a], [b]) => a.localeCompare(b))) {
|
|
23581
|
+
parsed.searchParams.set(key, value);
|
|
23582
|
+
}
|
|
23583
|
+
return { url: parsed.toString(), host: parsed.host, canonicalUri: parsed.pathname || "/" };
|
|
23584
|
+
}
|
|
23585
|
+
function getClosestPresetSize(ar, qualityLevel) {
|
|
23586
|
+
const presets = SIZE_PRESETS[qualityLevel];
|
|
23587
|
+
const defaultSize = presets["1:1"];
|
|
23588
|
+
if (ar === undefined)
|
|
23589
|
+
return defaultSize;
|
|
23590
|
+
const match = ar.match(/^(\d+(?:\.\d+)?):(\d+(?:\.\d+)?)$/);
|
|
23591
|
+
if (match === null)
|
|
23592
|
+
return defaultSize;
|
|
23593
|
+
const targetRatio = Number(match[1]) / Number(match[2]);
|
|
23594
|
+
let bestMatch = defaultSize;
|
|
23595
|
+
let bestDiff = Number.POSITIVE_INFINITY;
|
|
23596
|
+
for (const [ratio, size] of Object.entries(presets)) {
|
|
23597
|
+
const [w, h] = ratio.split(":").map(Number);
|
|
23598
|
+
const diff = Math.abs(w / h - targetRatio);
|
|
23599
|
+
if (diff < bestDiff) {
|
|
23600
|
+
bestDiff = diff;
|
|
23601
|
+
bestMatch = size;
|
|
23602
|
+
}
|
|
23603
|
+
}
|
|
23604
|
+
return bestMatch;
|
|
23605
|
+
}
|
|
23606
|
+
function getImageSize(ar, quality, width, height) {
|
|
23607
|
+
if (width !== undefined && height !== undefined)
|
|
23608
|
+
return `${width}x${height}`;
|
|
23609
|
+
const qualityLevel = quality === "normal" ? "normal" : "2k";
|
|
23610
|
+
return getClosestPresetSize(ar, qualityLevel);
|
|
23611
|
+
}
|
|
23612
|
+
function timestampHeader() {
|
|
23613
|
+
return new Date().toISOString().replace(/[:-]|\.\d{3}/g, "");
|
|
23614
|
+
}
|
|
23615
|
+
var createJimeng = (options) => {
|
|
23616
|
+
const fetchFn = options?.fetch ?? fetch;
|
|
23617
|
+
const env = options?.env ?? process.env;
|
|
23618
|
+
const readEnv = (key) => env[key] ?? "";
|
|
23619
|
+
async function signedPost(query, body, accessKey, secretKey, region) {
|
|
23620
|
+
const base = readEnv("JIMENG_BASE_URL") || "https://visual.volcengineapi.com";
|
|
23621
|
+
const endpoint = resolveEndpoint(base, query);
|
|
23622
|
+
const headers = {
|
|
23623
|
+
"content-type": "application/json",
|
|
23624
|
+
"X-Date": timestampHeader(),
|
|
23625
|
+
Host: endpoint.host
|
|
23626
|
+
};
|
|
23627
|
+
const authorization = generateSignature("POST", query, headers, body, accessKey, secretKey, region, "cv", endpoint.canonicalUri);
|
|
23628
|
+
return fetchFn(endpoint.url, { method: "POST", headers: { ...headers, authorization }, body });
|
|
23629
|
+
}
|
|
23630
|
+
async function submitTask(req, size, accessKey, secretKey, region) {
|
|
23631
|
+
const query = { Action: "CVSync2AsyncSubmitTask", Version: "2022-08-31" };
|
|
23632
|
+
const [width, height] = size.split("x").map(Number);
|
|
23633
|
+
const body = JSON.stringify({ req_key: req.model, prompt: req.prompt, width, height });
|
|
23634
|
+
const res = await signedPost(query, body, accessKey, secretKey, region);
|
|
23635
|
+
if (!res.ok)
|
|
23636
|
+
throw new Error(`Jimeng API submit error (${res.status}): ${await res.text()}`);
|
|
23637
|
+
const result = await res.json();
|
|
23638
|
+
if (result.code !== 1e4 || result.data?.task_id === undefined) {
|
|
23639
|
+
throw new Error(`Failed to submit task: ${result.message ?? "Unknown error"}`);
|
|
23640
|
+
}
|
|
23641
|
+
return result.data.task_id;
|
|
23642
|
+
}
|
|
23643
|
+
async function pollForResult(taskId, req, accessKey, secretKey, region) {
|
|
23644
|
+
const maxAttempts = 60;
|
|
23645
|
+
const pollIntervalMs = 2000;
|
|
23646
|
+
for (let attempt = 0;attempt < maxAttempts; attempt++) {
|
|
23647
|
+
const query = { Action: "CVSync2AsyncGetResult", Version: "2022-08-31" };
|
|
23648
|
+
const body = JSON.stringify({ req_key: req.model, task_id: taskId });
|
|
23649
|
+
const res = await signedPost(query, body, accessKey, secretKey, region);
|
|
23650
|
+
if (!res.ok)
|
|
23651
|
+
throw new Error(`Jimeng API poll error (${res.status}): ${await res.text()}`);
|
|
23652
|
+
const result = await res.json();
|
|
23653
|
+
if (result.code === 1e4 && result.data !== undefined) {
|
|
23654
|
+
const { status, image_urls, binary_data_base64 } = result.data;
|
|
23655
|
+
if (binary_data_base64 !== undefined && binary_data_base64.length > 0) {
|
|
23656
|
+
return new Uint8Array(Buffer.from(binary_data_base64[0], "base64"));
|
|
23657
|
+
}
|
|
23658
|
+
if (status === "done" && image_urls !== undefined && image_urls.length > 0) {
|
|
23659
|
+
const url2 = image_urls[0];
|
|
23660
|
+
const imgRes = await fetchFn(url2);
|
|
23661
|
+
if (!imgRes.ok)
|
|
23662
|
+
throw new Error(`Failed to download image from ${url2}`);
|
|
23663
|
+
return new Uint8Array(await imgRes.arrayBuffer());
|
|
23664
|
+
}
|
|
23665
|
+
if (status === "in_queue" || status === "generating") {
|
|
23666
|
+
await new Promise((resolve3) => setTimeout(resolve3, pollIntervalMs));
|
|
23667
|
+
continue;
|
|
23668
|
+
}
|
|
23669
|
+
if (status === "fail")
|
|
23670
|
+
throw new Error(`Jimeng task failed: ${result.message ?? "Generation failed"}`);
|
|
23671
|
+
}
|
|
23672
|
+
throw new Error(`Unexpected response during polling: ${result.message ?? "Unknown error"}`);
|
|
23673
|
+
}
|
|
23674
|
+
throw new Error("Task timeout: image generation took too long");
|
|
23675
|
+
}
|
|
23676
|
+
async function generate(req) {
|
|
23677
|
+
if ((req.referenceImages?.length ?? 0) > 0) {
|
|
23678
|
+
throw new Error("Jimeng does not support reference images. Use --provider google, openai, openrouter, or replicate.");
|
|
23679
|
+
}
|
|
23680
|
+
const accessKey = readEnv("JIMENG_ACCESS_KEY_ID");
|
|
23681
|
+
const secretKey = readEnv("JIMENG_SECRET_ACCESS_KEY");
|
|
23682
|
+
if (accessKey === "" || secretKey === "") {
|
|
23683
|
+
throw new Error("JIMENG_ACCESS_KEY_ID and JIMENG_SECRET_ACCESS_KEY are required. Get your credentials from https://console.volcengine.com/iam/keymanage");
|
|
23684
|
+
}
|
|
23685
|
+
const region = readEnv("JIMENG_REGION") || "cn-north-1";
|
|
23686
|
+
const size = getImageSize(req.aspectRatio, req.quality, req.width, req.height);
|
|
23687
|
+
const taskId = await submitTask(req, size, accessKey, secretKey, region);
|
|
23688
|
+
const bytes = await pollForResult(taskId, req, accessKey, secretKey, region);
|
|
23689
|
+
return { bytes, extension: "png", provider: "jimeng", model: req.model };
|
|
23690
|
+
}
|
|
23691
|
+
return {
|
|
23692
|
+
name: "jimeng",
|
|
23693
|
+
defaultModel: () => DEFAULT_MODEL8,
|
|
23694
|
+
isAvailable: () => readEnv("JIMENG_ACCESS_KEY_ID") !== "" && readEnv("JIMENG_SECRET_ACCESS_KEY") !== "",
|
|
23695
|
+
supportsReferenceImages: () => true,
|
|
23696
|
+
generate
|
|
23697
|
+
};
|
|
23698
|
+
};
|
|
23699
|
+
|
|
23700
|
+
// ../../plugins/generations/image-gen/src/providers/minimax.ts
|
|
23701
|
+
import { Buffer as Buffer7 } from "buffer";
|
|
23702
|
+
var DEFAULT_MODEL9 = "image-01";
|
|
23703
|
+
var MAX_REFERENCE_IMAGE_BYTES = 10 * 1024 * 1024;
|
|
23704
|
+
var SUPPORTED_ASPECT_RATIOS = new Set(["1:1", "16:9", "4:3", "3:2", "2:3", "3:4", "9:16", "21:9"]);
|
|
23705
|
+
function validatePixelSize(width, height) {
|
|
23706
|
+
if (width < 512 || width > 2048 || height < 512 || height > 2048) {
|
|
23707
|
+
throw new Error("MiniMax custom size must keep width and height between 512 and 2048.");
|
|
23708
|
+
}
|
|
23709
|
+
if (width % 8 !== 0 || height % 8 !== 0) {
|
|
23710
|
+
throw new Error("MiniMax custom size requires width and height divisible by 8.");
|
|
23711
|
+
}
|
|
23712
|
+
}
|
|
23713
|
+
function requestBody(prompt, model, req) {
|
|
23714
|
+
if ((req.n ?? 1) > 9)
|
|
23715
|
+
throw new Error("MiniMax supports at most 9 images per request.");
|
|
23716
|
+
if (req.aspectRatio !== undefined && !SUPPORTED_ASPECT_RATIOS.has(req.aspectRatio)) {
|
|
23717
|
+
throw new Error(`MiniMax aspect_ratio must be one of: ${Array.from(SUPPORTED_ASPECT_RATIOS).join(", ")}.`);
|
|
23718
|
+
}
|
|
23719
|
+
if (req.width !== undefined && req.height !== undefined && req.aspectRatio === undefined) {
|
|
23720
|
+
if (model !== "image-01") {
|
|
23721
|
+
throw new Error("MiniMax custom --size is only supported with model image-01. Use --model image-01 or pass --ar instead.");
|
|
23722
|
+
}
|
|
23723
|
+
validatePixelSize(req.width, req.height);
|
|
23724
|
+
}
|
|
23725
|
+
if (req.referenceImages !== undefined && req.referenceImages.length > 0) {
|
|
23726
|
+
for (const refPath of req.referenceImages) {
|
|
23727
|
+
const ext = refPath.toLowerCase();
|
|
23728
|
+
if (!ext.includes(".jpg") && !ext.includes(".jpeg") && !ext.includes(".png")) {
|
|
23729
|
+
throw new Error(`MiniMax subject_reference only supports JPG, JPEG, or PNG files: ${refPath}`);
|
|
23730
|
+
}
|
|
23731
|
+
}
|
|
23732
|
+
}
|
|
23733
|
+
const body = { model, prompt, response_format: "base64" };
|
|
23734
|
+
if (req.aspectRatio !== undefined) {
|
|
23735
|
+
body.aspect_ratio = req.aspectRatio;
|
|
23736
|
+
} else if (req.width !== undefined && req.height !== undefined) {
|
|
23737
|
+
body.width = req.width;
|
|
23738
|
+
body.height = req.height;
|
|
23739
|
+
}
|
|
23740
|
+
if ((req.n ?? 1) > 1)
|
|
23741
|
+
body.n = req.n;
|
|
23742
|
+
return body;
|
|
23743
|
+
}
|
|
23744
|
+
async function buildSubjectReference(referenceImages) {
|
|
23745
|
+
if (referenceImages.length === 0)
|
|
23746
|
+
return;
|
|
23747
|
+
const subjectReference = [];
|
|
23748
|
+
for (const refPath of referenceImages) {
|
|
23749
|
+
const ext = refPath.toLowerCase();
|
|
23750
|
+
if (!ext.includes(".jpg") && !ext.includes(".jpeg") && !ext.includes(".png")) {
|
|
23751
|
+
throw new Error(`MiniMax subject_reference only supports JPG, JPEG, or PNG files: ${refPath}`);
|
|
23752
|
+
}
|
|
23753
|
+
const bytes = Buffer7.from(await readBytes(refPath));
|
|
23754
|
+
if (bytes.length > MAX_REFERENCE_IMAGE_BYTES) {
|
|
23755
|
+
throw new Error(`MiniMax subject_reference images must be smaller than 10MB: ${refPath}`);
|
|
23756
|
+
}
|
|
23757
|
+
subjectReference.push({
|
|
23758
|
+
type: "character",
|
|
23759
|
+
image_file: `data:${mimeForPath(refPath)};base64,${bytes.toString("base64")}`
|
|
23760
|
+
});
|
|
23761
|
+
}
|
|
23762
|
+
return subjectReference;
|
|
23763
|
+
}
|
|
23764
|
+
async function extractImageFromResponse4(result, fetchFn = fetch) {
|
|
23765
|
+
const baseResp = result.base_resp;
|
|
23766
|
+
if (baseResp?.status_code !== undefined && baseResp.status_code !== 0) {
|
|
23767
|
+
throw new Error(baseResp.status_msg ?? `MiniMax API returned status_code=${baseResp.status_code}`);
|
|
23768
|
+
}
|
|
23769
|
+
const base64Image = result.data?.image_base64?.[0];
|
|
23770
|
+
if (base64Image !== undefined)
|
|
23771
|
+
return Uint8Array.from(Buffer7.from(base64Image, "base64"));
|
|
23772
|
+
const url2 = result.data?.image_urls?.[0];
|
|
23773
|
+
if (url2 !== undefined) {
|
|
23774
|
+
const response = await fetchFn(url2);
|
|
23775
|
+
if (!response.ok)
|
|
23776
|
+
throw new Error(`Failed to download image from MiniMax: ${response.status}`);
|
|
23777
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
23778
|
+
}
|
|
23779
|
+
throw new Error("No image data in MiniMax response");
|
|
23780
|
+
}
|
|
23781
|
+
var createMinimax = (options) => {
|
|
23782
|
+
const fetchFn = options?.fetch ?? fetch;
|
|
23783
|
+
const env = options?.env ?? process.env;
|
|
23784
|
+
const readEnv = (key) => env[key] ?? "";
|
|
23785
|
+
return {
|
|
23786
|
+
name: "minimax",
|
|
23787
|
+
defaultModel: () => DEFAULT_MODEL9,
|
|
23788
|
+
isAvailable: () => readEnv("MINIMAX_API_KEY") !== "",
|
|
23789
|
+
supportsReferenceImages: () => true,
|
|
23790
|
+
validate(req) {
|
|
23791
|
+
requestBody(req.prompt, req.model, req);
|
|
23792
|
+
},
|
|
23793
|
+
async generate(req) {
|
|
23794
|
+
const apiKey = readEnv("MINIMAX_API_KEY");
|
|
23795
|
+
if (apiKey === "")
|
|
23796
|
+
throw new Error("MINIMAX_API_KEY is required. Get one from https://platform.minimaxi.com/");
|
|
23797
|
+
const body = requestBody(req.prompt, req.model, req);
|
|
23798
|
+
const subjectReference = await buildSubjectReference(req.referenceImages ?? []);
|
|
23799
|
+
if (subjectReference !== undefined)
|
|
23800
|
+
body.subject_reference = subjectReference;
|
|
23801
|
+
const base = (readEnv("MINIMAX_BASE_URL") || "https://api.minimaxi.com").replace(/\/+$/g, "");
|
|
23802
|
+
const url2 = base.endsWith("/v1") ? `${base}/image_generation` : `${base}/v1/image_generation`;
|
|
23803
|
+
const response = await fetchFn(url2, {
|
|
23804
|
+
method: "POST",
|
|
23805
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${apiKey}` },
|
|
23806
|
+
body: JSON.stringify(body)
|
|
23807
|
+
});
|
|
23808
|
+
if (!response.ok) {
|
|
23809
|
+
throw new Error(`MiniMax API error (${response.status}): ${await response.text()}`);
|
|
23810
|
+
}
|
|
23811
|
+
const bytes = await extractImageFromResponse4(await response.json(), fetchFn);
|
|
23812
|
+
return { bytes, extension: "jpg", provider: "minimax", model: req.model };
|
|
23813
|
+
}
|
|
23814
|
+
};
|
|
23815
|
+
};
|
|
23816
|
+
|
|
23817
|
+
// ../../plugins/generations/image-gen/src/providers/openrouter.ts
|
|
23818
|
+
import { Buffer as Buffer8 } from "buffer";
|
|
23819
|
+
var DEFAULT_MODEL10 = "google/gemini-3.1-flash-image";
|
|
23820
|
+
var COMMON_ASPECT_RATIOS = ["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9"];
|
|
23821
|
+
var GEMINI_EXTENDED_ASPECT_RATIOS = ["1:4", "4:1", "1:8", "8:1"];
|
|
23822
|
+
function normalizeModelId(model) {
|
|
23823
|
+
return model.trim().toLowerCase().split(":")[0];
|
|
23824
|
+
}
|
|
23825
|
+
function isTextAndImageModel(model) {
|
|
23826
|
+
const normalized = normalizeModelId(model);
|
|
23827
|
+
if (normalized === "openrouter/auto")
|
|
23828
|
+
return true;
|
|
23829
|
+
if (normalized.startsWith("google/gemini-") && normalized.includes("image"))
|
|
23830
|
+
return true;
|
|
23831
|
+
if (normalized.startsWith("openai/gpt-") && normalized.includes("image"))
|
|
23832
|
+
return true;
|
|
23833
|
+
return false;
|
|
23834
|
+
}
|
|
23835
|
+
function getSupportedAspectRatios(model) {
|
|
23836
|
+
const normalized = normalizeModelId(model);
|
|
23837
|
+
if (normalized !== "google/gemini-3.1-flash-image" && normalized !== "google/gemini-3.1-flash-image-preview") {
|
|
23838
|
+
return new Set(COMMON_ASPECT_RATIOS);
|
|
23839
|
+
}
|
|
23840
|
+
return new Set([...COMMON_ASPECT_RATIOS, ...GEMINI_EXTENDED_ASPECT_RATIOS]);
|
|
23841
|
+
}
|
|
23842
|
+
function gcd2(a, b) {
|
|
23843
|
+
let x = Math.abs(a);
|
|
23844
|
+
let y = Math.abs(b);
|
|
23845
|
+
while (y !== 0) {
|
|
23846
|
+
const next = x % y;
|
|
23847
|
+
x = y;
|
|
23848
|
+
y = next;
|
|
23849
|
+
}
|
|
23850
|
+
return x || 1;
|
|
23851
|
+
}
|
|
23852
|
+
function inferAspectRatio(size) {
|
|
23853
|
+
if (size === null)
|
|
23854
|
+
return null;
|
|
23855
|
+
const parsed = size.match(/^(\d+)\s*[xX]\s*(\d+)$/);
|
|
23856
|
+
if (parsed === null)
|
|
23857
|
+
return null;
|
|
23858
|
+
const width = Number.parseInt(parsed[1], 10);
|
|
23859
|
+
const height = Number.parseInt(parsed[2], 10);
|
|
23860
|
+
if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0)
|
|
23861
|
+
return null;
|
|
23862
|
+
const divisor = gcd2(width, height);
|
|
23863
|
+
return `${width / divisor}:${height / divisor}`;
|
|
22240
23864
|
}
|
|
23865
|
+
function inferImageSize(size) {
|
|
23866
|
+
if (size === null)
|
|
23867
|
+
return null;
|
|
23868
|
+
const parsed = size.match(/^(\d+)\s*[xX]\s*(\d+)$/);
|
|
23869
|
+
if (parsed === null)
|
|
23870
|
+
return null;
|
|
23871
|
+
const width = Number.parseInt(parsed[1], 10);
|
|
23872
|
+
const height = Number.parseInt(parsed[2], 10);
|
|
23873
|
+
if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0)
|
|
23874
|
+
return null;
|
|
23875
|
+
const longestEdge = Math.max(width, height);
|
|
23876
|
+
if (longestEdge <= 1024)
|
|
23877
|
+
return "1K";
|
|
23878
|
+
if (longestEdge <= 2048)
|
|
23879
|
+
return "2K";
|
|
23880
|
+
return "4K";
|
|
23881
|
+
}
|
|
23882
|
+
function getImageSize2(req) {
|
|
23883
|
+
if (req.width !== undefined && req.height !== undefined)
|
|
23884
|
+
return inferImageSize(`${req.width}x${req.height}`);
|
|
23885
|
+
if (req.quality === "normal")
|
|
23886
|
+
return "1K";
|
|
23887
|
+
if (req.quality === "2k")
|
|
23888
|
+
return "2K";
|
|
23889
|
+
return null;
|
|
23890
|
+
}
|
|
23891
|
+
function getAspectRatio(preferred, size, model) {
|
|
23892
|
+
if (preferred !== null)
|
|
23893
|
+
return preferred;
|
|
23894
|
+
const inferred = inferAspectRatio(size);
|
|
23895
|
+
if (inferred === null || !getSupportedAspectRatios(model).has(inferred))
|
|
23896
|
+
return null;
|
|
23897
|
+
return inferred;
|
|
23898
|
+
}
|
|
23899
|
+
function validateAspectRatio(model, aspectRatio, size) {
|
|
23900
|
+
const requested = aspectRatio ?? inferAspectRatio(size);
|
|
23901
|
+
if (requested === null)
|
|
23902
|
+
return;
|
|
23903
|
+
const supported = getSupportedAspectRatios(model);
|
|
23904
|
+
if (supported.has(requested))
|
|
23905
|
+
return;
|
|
23906
|
+
const requestedValue = aspectRatio !== null ? `aspect ratio ${requested}` : `size ${size} (aspect ratio ${requested})`;
|
|
23907
|
+
throw new Error(`OpenRouter model ${model} does not support ${requestedValue}. Supported values: ${Array.from(supported).join(", ")}`);
|
|
23908
|
+
}
|
|
23909
|
+
function buildContent(prompt, referenceImages) {
|
|
23910
|
+
if (referenceImages.length === 0)
|
|
23911
|
+
return prompt;
|
|
23912
|
+
const content = [{ type: "text", text: prompt }];
|
|
23913
|
+
for (const imageUrl of referenceImages) {
|
|
23914
|
+
content.push({ type: "image_url", image_url: { url: imageUrl } });
|
|
23915
|
+
}
|
|
23916
|
+
return content;
|
|
23917
|
+
}
|
|
23918
|
+
function extractImageUrl(entry) {
|
|
23919
|
+
const value = entry.image_url ?? entry.imageUrl;
|
|
23920
|
+
if (value === null || value === undefined)
|
|
23921
|
+
return null;
|
|
23922
|
+
if (typeof value === "string")
|
|
23923
|
+
return value;
|
|
23924
|
+
return value.url ?? null;
|
|
23925
|
+
}
|
|
23926
|
+
function decodeDataUrl(value) {
|
|
23927
|
+
const match = value.match(/^data:image\/[^;]+;base64,([A-Za-z0-9+/=]+)$/);
|
|
23928
|
+
if (match === null)
|
|
23929
|
+
return null;
|
|
23930
|
+
return Uint8Array.from(Buffer8.from(match[1], "base64"));
|
|
23931
|
+
}
|
|
23932
|
+
async function downloadImage(value, fetchFn) {
|
|
23933
|
+
const inline = decodeDataUrl(value);
|
|
23934
|
+
if (inline !== null)
|
|
23935
|
+
return inline;
|
|
23936
|
+
if (value.startsWith("http://") || value.startsWith("https://")) {
|
|
23937
|
+
const response = await fetchFn(value);
|
|
23938
|
+
if (!response.ok)
|
|
23939
|
+
throw new Error(`Failed to download OpenRouter image: ${response.status}`);
|
|
23940
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
23941
|
+
}
|
|
23942
|
+
return Uint8Array.from(Buffer8.from(value, "base64"));
|
|
23943
|
+
}
|
|
23944
|
+
async function extractImageFromResponse5(result, fetchFn = fetch) {
|
|
23945
|
+
const choice = result.choices?.[0];
|
|
23946
|
+
const message = choice?.message;
|
|
23947
|
+
for (const image of message?.images ?? []) {
|
|
23948
|
+
const imageUrl = extractImageUrl(image);
|
|
23949
|
+
if (imageUrl !== null)
|
|
23950
|
+
return downloadImage(imageUrl, fetchFn);
|
|
23951
|
+
}
|
|
23952
|
+
if (Array.isArray(message?.content)) {
|
|
23953
|
+
for (const item of message.content) {
|
|
23954
|
+
const imageUrl = extractImageUrl(item);
|
|
23955
|
+
if (imageUrl !== null)
|
|
23956
|
+
return downloadImage(imageUrl, fetchFn);
|
|
23957
|
+
if (item.type === "text" && item.text !== undefined) {
|
|
23958
|
+
const inline = decodeDataUrl(item.text);
|
|
23959
|
+
if (inline !== null)
|
|
23960
|
+
return inline;
|
|
23961
|
+
}
|
|
23962
|
+
}
|
|
23963
|
+
} else if (typeof message?.content === "string") {
|
|
23964
|
+
const inline = decodeDataUrl(message.content);
|
|
23965
|
+
if (inline !== null)
|
|
23966
|
+
return inline;
|
|
23967
|
+
}
|
|
23968
|
+
const finishReason = choice?.native_finish_reason || choice?.finish_reason || "unknown";
|
|
23969
|
+
throw new Error(`No image in OpenRouter response (finish_reason=${finishReason})`);
|
|
23970
|
+
}
|
|
23971
|
+
function buildRequestBody2(prompt, model, req, referenceImages) {
|
|
23972
|
+
validateAspectRatio(model, req.aspectRatio ?? null, req.width !== undefined && req.height !== undefined ? `${req.width}x${req.height}` : null);
|
|
23973
|
+
const imageConfig = {};
|
|
23974
|
+
const imageSize = getImageSize2(req);
|
|
23975
|
+
if (imageSize !== null)
|
|
23976
|
+
imageConfig.image_size = imageSize;
|
|
23977
|
+
const aspectRatio = getAspectRatio(req.aspectRatio ?? null, req.width !== undefined && req.height !== undefined ? `${req.width}x${req.height}` : null, model);
|
|
23978
|
+
if (aspectRatio !== null)
|
|
23979
|
+
imageConfig.aspect_ratio = aspectRatio;
|
|
23980
|
+
const body = {
|
|
23981
|
+
messages: [{ role: "user", content: buildContent(prompt, referenceImages) }],
|
|
23982
|
+
modalities: isTextAndImageModel(model) ? ["image", "text"] : ["image"],
|
|
23983
|
+
stream: false
|
|
23984
|
+
};
|
|
23985
|
+
if (Object.keys(imageConfig).length > 0) {
|
|
23986
|
+
body.image_config = imageConfig;
|
|
23987
|
+
body.provider = { require_parameters: true };
|
|
23988
|
+
}
|
|
23989
|
+
return body;
|
|
23990
|
+
}
|
|
23991
|
+
var createOpenrouter = (options) => {
|
|
23992
|
+
const fetchFn = options?.fetch ?? fetch;
|
|
23993
|
+
const env = options?.env ?? process.env;
|
|
23994
|
+
const readEnv = (key) => env[key] ?? "";
|
|
23995
|
+
return {
|
|
23996
|
+
name: "openrouter",
|
|
23997
|
+
defaultModel: () => DEFAULT_MODEL10,
|
|
23998
|
+
isAvailable: () => readEnv("OPENROUTER_API_KEY") !== "",
|
|
23999
|
+
supportsReferenceImages: () => true,
|
|
24000
|
+
async generate(req) {
|
|
24001
|
+
const apiKey = readEnv("OPENROUTER_API_KEY");
|
|
24002
|
+
if (apiKey === "")
|
|
24003
|
+
throw new Error("OPENROUTER_API_KEY is required. Get one at https://openrouter.ai/settings/keys");
|
|
24004
|
+
const headers = {
|
|
24005
|
+
"content-type": "application/json",
|
|
24006
|
+
authorization: `Bearer ${apiKey}`
|
|
24007
|
+
};
|
|
24008
|
+
const referer = readEnv("OPENROUTER_HTTP_REFERER").trim();
|
|
24009
|
+
if (referer !== "")
|
|
24010
|
+
headers["HTTP-Referer"] = referer;
|
|
24011
|
+
const title = readEnv("OPENROUTER_TITLE").trim();
|
|
24012
|
+
if (title !== "") {
|
|
24013
|
+
headers["X-OpenRouter-Title"] = title;
|
|
24014
|
+
headers["X-Title"] = title;
|
|
24015
|
+
}
|
|
24016
|
+
const referenceImages = [];
|
|
24017
|
+
for (const refPath of req.referenceImages ?? []) {
|
|
24018
|
+
referenceImages.push(await refToDataUri(refPath));
|
|
24019
|
+
}
|
|
24020
|
+
const body = { model: req.model, ...buildRequestBody2(req.prompt, req.model, req, referenceImages) };
|
|
24021
|
+
const base = (readEnv("OPENROUTER_BASE_URL") || "https://openrouter.ai/api/v1").replace(/\/+$/g, "");
|
|
24022
|
+
const response = await fetchFn(`${base}/chat/completions`, {
|
|
24023
|
+
method: "POST",
|
|
24024
|
+
headers,
|
|
24025
|
+
body: JSON.stringify(body)
|
|
24026
|
+
});
|
|
24027
|
+
if (!response.ok) {
|
|
24028
|
+
throw new Error(`OpenRouter API error (${response.status}): ${await response.text()}`);
|
|
24029
|
+
}
|
|
24030
|
+
const bytes = await extractImageFromResponse5(await response.json(), fetchFn);
|
|
24031
|
+
return { bytes, extension: "png", provider: "openrouter", model: req.model };
|
|
24032
|
+
}
|
|
24033
|
+
};
|
|
24034
|
+
};
|
|
24035
|
+
|
|
24036
|
+
// ../../plugins/generations/image-gen/src/providers/replicate.ts
|
|
24037
|
+
var DEFAULT_MODEL11 = "google/nano-banana-2";
|
|
24038
|
+
var SYNC_WAIT_SECONDS = 60;
|
|
24039
|
+
var POLL_INTERVAL_MS = 2000;
|
|
24040
|
+
var MAX_POLL_MS = 300000;
|
|
24041
|
+
var DOCUMENTED_REPLICATE_ASPECT_RATIOS = new Set([
|
|
24042
|
+
"1:1",
|
|
24043
|
+
"2:3",
|
|
24044
|
+
"3:2",
|
|
24045
|
+
"3:4",
|
|
24046
|
+
"4:3",
|
|
24047
|
+
"5:4",
|
|
24048
|
+
"4:5",
|
|
24049
|
+
"9:16",
|
|
24050
|
+
"16:9",
|
|
24051
|
+
"21:9"
|
|
24052
|
+
]);
|
|
24053
|
+
function parseModelId(model) {
|
|
24054
|
+
const [ownerName, version2] = model.split(":");
|
|
24055
|
+
const parts = (ownerName ?? "").split("/");
|
|
24056
|
+
if (parts.length !== 2 || !parts[0] || !parts[1]) {
|
|
24057
|
+
throw new Error(`Invalid Replicate model format: "${model}". Expected "owner/name" or "owner/name:version".`);
|
|
24058
|
+
}
|
|
24059
|
+
return { owner: parts[0], name: parts[1], version: version2 ?? null };
|
|
24060
|
+
}
|
|
24061
|
+
function getModelFamily(model) {
|
|
24062
|
+
const normalized = model.trim().toLowerCase().split(":")[0];
|
|
24063
|
+
if (["google/nano-banana", "google/nano-banana-pro", "google/nano-banana-2"].includes(normalized))
|
|
24064
|
+
return "nano-banana";
|
|
24065
|
+
if (normalized === "bytedance/seedream-4.5")
|
|
24066
|
+
return "seedream45";
|
|
24067
|
+
if (normalized === "bytedance/seedream-5-lite")
|
|
24068
|
+
return "seedream5lite";
|
|
24069
|
+
if (normalized === "wan-video/wan-2.7-image")
|
|
24070
|
+
return "wan27image";
|
|
24071
|
+
if (normalized === "wan-video/wan-2.7-image-pro")
|
|
24072
|
+
return "wan27imagepro";
|
|
24073
|
+
return "unknown";
|
|
24074
|
+
}
|
|
24075
|
+
function validateDocumentedAspectRatio(model, aspectRatio) {
|
|
24076
|
+
if (aspectRatio === "match_input_image" || DOCUMENTED_REPLICATE_ASPECT_RATIOS.has(aspectRatio))
|
|
24077
|
+
return;
|
|
24078
|
+
throw new Error(`Replicate model ${model} does not support aspect ratio ${aspectRatio}. Supported values: ${Array.from(DOCUMENTED_REPLICATE_ASPECT_RATIOS).join(", ")}`);
|
|
24079
|
+
}
|
|
24080
|
+
function getNanoBananaResolution(req) {
|
|
24081
|
+
if (req.width !== undefined && req.height !== undefined) {
|
|
24082
|
+
const longestEdge = Math.max(req.width, req.height);
|
|
24083
|
+
if (longestEdge <= 1024)
|
|
24084
|
+
return "1K";
|
|
24085
|
+
if (longestEdge <= 2048)
|
|
24086
|
+
return "2K";
|
|
24087
|
+
throw new Error("Replicate nano-banana only supports sizes that map to 1K or 2K output.");
|
|
24088
|
+
}
|
|
24089
|
+
return req.quality === "normal" ? "1K" : "2K";
|
|
24090
|
+
}
|
|
24091
|
+
function resolveSeedream45Size(req) {
|
|
24092
|
+
if (req.width !== undefined && req.height !== undefined) {
|
|
24093
|
+
if (req.width < 1024 || req.width > 4096 || req.height < 1024 || req.height > 4096) {
|
|
24094
|
+
throw new Error("Replicate Seedream 4.5 custom --size must keep width and height between 1024 and 4096.");
|
|
24095
|
+
}
|
|
24096
|
+
return { width: req.width, height: req.height };
|
|
24097
|
+
}
|
|
24098
|
+
return req.quality === "normal" ? "2K" : "4K";
|
|
24099
|
+
}
|
|
24100
|
+
function resolveSeedream5LiteSize(req) {
|
|
24101
|
+
return req.quality === "normal" ? "2K" : "3K";
|
|
24102
|
+
}
|
|
24103
|
+
function resolveWanSizeFromAspectRatio(aspectRatio, maxDimension) {
|
|
24104
|
+
const match = aspectRatio.trim().match(/^(\d+)\s*:\s*(\d+)$/);
|
|
24105
|
+
if (match === null)
|
|
24106
|
+
throw new Error(`Replicate Wan aspect ratio must be in W:H format, got ${aspectRatio}.`);
|
|
24107
|
+
const ratioWidth = Number.parseInt(match[1], 10);
|
|
24108
|
+
const ratioHeight = Number.parseInt(match[2], 10);
|
|
24109
|
+
if (!Number.isFinite(ratioWidth) || !Number.isFinite(ratioHeight) || ratioWidth <= 0 || ratioHeight <= 0) {
|
|
24110
|
+
throw new Error(`Replicate Wan aspect ratio must be in W:H format, got ${aspectRatio}.`);
|
|
24111
|
+
}
|
|
24112
|
+
const scale = Math.min(maxDimension / ratioWidth, maxDimension / ratioHeight);
|
|
24113
|
+
const width = Math.max(1, Math.floor(ratioWidth * scale));
|
|
24114
|
+
const height = Math.max(1, Math.floor(ratioHeight * scale));
|
|
24115
|
+
return `${width}*${height}`;
|
|
24116
|
+
}
|
|
24117
|
+
function resolveWanSize(family, req) {
|
|
24118
|
+
const referenceMode = (req.referenceImages?.length ?? 0) > 0;
|
|
24119
|
+
const maxDimension = family === "wan27imagepro" && !referenceMode ? 4096 : 2048;
|
|
24120
|
+
if (req.width !== undefined && req.height !== undefined) {
|
|
24121
|
+
const parsed = { width: req.width, height: req.height };
|
|
24122
|
+
if (req.width === undefined || req.height === undefined || parsed.width > maxDimension || parsed.height > maxDimension) {
|
|
24123
|
+
throw new Error(`Replicate ${family === "wan27imagepro" ? "Wan 2.7 Image Pro" : "Wan 2.7 Image"} custom --size must keep width and height at or below ${maxDimension}px in the current mode.`);
|
|
24124
|
+
}
|
|
24125
|
+
return `${parsed.width}*${parsed.height}`;
|
|
24126
|
+
}
|
|
24127
|
+
if (req.aspectRatio !== undefined) {
|
|
24128
|
+
return resolveWanSizeFromAspectRatio(req.aspectRatio, req.quality === "normal" ? 1024 : 2048);
|
|
24129
|
+
}
|
|
24130
|
+
return req.quality === "normal" ? "1K" : "2K";
|
|
24131
|
+
}
|
|
24132
|
+
function buildInput(req) {
|
|
24133
|
+
const family = getModelFamily(req.model);
|
|
24134
|
+
const referenceImages = req.referenceImages ?? [];
|
|
24135
|
+
if (family === "nano-banana") {
|
|
24136
|
+
const input = {
|
|
24137
|
+
prompt: req.prompt,
|
|
24138
|
+
resolution: getNanoBananaResolution(req),
|
|
24139
|
+
output_format: "png"
|
|
24140
|
+
};
|
|
24141
|
+
if (req.aspectRatio !== undefined) {
|
|
24142
|
+
validateDocumentedAspectRatio(req.model, req.aspectRatio);
|
|
24143
|
+
input.aspect_ratio = req.aspectRatio;
|
|
24144
|
+
} else if (referenceImages.length > 0) {
|
|
24145
|
+
input.aspect_ratio = "match_input_image";
|
|
24146
|
+
}
|
|
24147
|
+
if (referenceImages.length > 0)
|
|
24148
|
+
input.image_input = referenceImages;
|
|
24149
|
+
return input;
|
|
24150
|
+
}
|
|
24151
|
+
if (family === "seedream45" || family === "seedream5lite") {
|
|
24152
|
+
const size = family === "seedream45" ? resolveSeedream45Size(req) : resolveSeedream5LiteSize(req);
|
|
24153
|
+
const input = { prompt: req.prompt };
|
|
24154
|
+
if (family === "seedream45" && typeof size === "object") {
|
|
24155
|
+
input.size = "custom";
|
|
24156
|
+
input.width = size.width;
|
|
24157
|
+
input.height = size.height;
|
|
24158
|
+
} else {
|
|
24159
|
+
input.size = size;
|
|
24160
|
+
}
|
|
24161
|
+
if (referenceImages.length > 0)
|
|
24162
|
+
input.image_input = referenceImages;
|
|
24163
|
+
if (req.aspectRatio !== undefined) {
|
|
24164
|
+
validateDocumentedAspectRatio(req.model, req.aspectRatio);
|
|
24165
|
+
input.aspect_ratio = req.aspectRatio;
|
|
24166
|
+
} else if (referenceImages.length > 0 && family === "seedream45") {
|
|
24167
|
+
input.aspect_ratio = "match_input_image";
|
|
24168
|
+
}
|
|
24169
|
+
return input;
|
|
24170
|
+
}
|
|
24171
|
+
if (family === "wan27image" || family === "wan27imagepro") {
|
|
24172
|
+
const input = { prompt: req.prompt, size: resolveWanSize(family, req) };
|
|
24173
|
+
if (referenceImages.length > 0)
|
|
24174
|
+
input.images = referenceImages;
|
|
24175
|
+
return input;
|
|
24176
|
+
}
|
|
24177
|
+
return { prompt: req.prompt };
|
|
24178
|
+
}
|
|
24179
|
+
function extractOutputUrl(prediction) {
|
|
24180
|
+
const output2 = prediction.output;
|
|
24181
|
+
if (typeof output2 === "string")
|
|
24182
|
+
return output2;
|
|
24183
|
+
if (Array.isArray(output2)) {
|
|
24184
|
+
if (output2.length !== 1) {
|
|
24185
|
+
throw new Error(`Replicate returned ${output2.length} outputs, but kk image-gen currently supports saving exactly one image per request.`);
|
|
24186
|
+
}
|
|
24187
|
+
const first = output2[0];
|
|
24188
|
+
if (typeof first === "string")
|
|
24189
|
+
return first;
|
|
24190
|
+
}
|
|
24191
|
+
if (output2 !== null && typeof output2 === "object" && "url" in output2) {
|
|
24192
|
+
const url2 = output2.url;
|
|
24193
|
+
if (typeof url2 === "string")
|
|
24194
|
+
return url2;
|
|
24195
|
+
}
|
|
24196
|
+
throw new Error(`Unexpected Replicate output format: ${JSON.stringify(output2)}`);
|
|
24197
|
+
}
|
|
24198
|
+
var createReplicate = (options) => {
|
|
24199
|
+
const fetchFn = options?.fetch ?? fetch;
|
|
24200
|
+
const env = options?.env ?? process.env;
|
|
24201
|
+
const readEnv = (key) => env[key] ?? "";
|
|
24202
|
+
async function downloadImage2(url2) {
|
|
24203
|
+
const res = await fetchFn(url2);
|
|
24204
|
+
if (!res.ok)
|
|
24205
|
+
throw new Error(`Failed to download image from Replicate: ${res.status}`);
|
|
24206
|
+
return new Uint8Array(await res.arrayBuffer());
|
|
24207
|
+
}
|
|
24208
|
+
async function pollPrediction(apiToken, getUrl) {
|
|
24209
|
+
const start = Date.now();
|
|
24210
|
+
while (Date.now() - start < MAX_POLL_MS) {
|
|
24211
|
+
const res = await fetchFn(getUrl, { headers: { authorization: `Bearer ${apiToken}` } });
|
|
24212
|
+
if (!res.ok)
|
|
24213
|
+
throw new Error(`Replicate poll error (${res.status}): ${await res.text()}`);
|
|
24214
|
+
const prediction = await res.json();
|
|
24215
|
+
if (prediction.status === "succeeded")
|
|
24216
|
+
return prediction;
|
|
24217
|
+
if (prediction.status === "failed" || prediction.status === "canceled") {
|
|
24218
|
+
throw new Error(`Replicate prediction ${prediction.status}: ${prediction.error ?? "unknown error"}`);
|
|
24219
|
+
}
|
|
24220
|
+
await new Promise((resolve3) => setTimeout(resolve3, POLL_INTERVAL_MS));
|
|
24221
|
+
}
|
|
24222
|
+
throw new Error(`Replicate prediction timed out after ${MAX_POLL_MS / 1000}s`);
|
|
24223
|
+
}
|
|
24224
|
+
return {
|
|
24225
|
+
name: "replicate",
|
|
24226
|
+
defaultModel: () => DEFAULT_MODEL11,
|
|
24227
|
+
isAvailable: () => readEnv("REPLICATE_API_TOKEN") !== "",
|
|
24228
|
+
supportsReferenceImages: () => true,
|
|
24229
|
+
async generate(req) {
|
|
24230
|
+
const apiToken = readEnv("REPLICATE_API_TOKEN");
|
|
24231
|
+
if (apiToken === "") {
|
|
24232
|
+
throw new Error("REPLICATE_API_TOKEN is required. Get one at https://replicate.com/account/api-tokens");
|
|
24233
|
+
}
|
|
24234
|
+
const model = parseModelId(req.model);
|
|
24235
|
+
const refDataUrls = [];
|
|
24236
|
+
for (const refPath of req.referenceImages ?? []) {
|
|
24237
|
+
refDataUrls.push(await refToDataUri(refPath));
|
|
24238
|
+
}
|
|
24239
|
+
const input = buildInput({ ...req, referenceImages: refDataUrls });
|
|
24240
|
+
const baseUrl = (readEnv("REPLICATE_BASE_URL") || "https://api.replicate.com").replace(/\/+$/g, "");
|
|
24241
|
+
const url2 = model.version !== null ? `${baseUrl}/v1/predictions` : `${baseUrl}/v1/models/${model.owner}/${model.name}/predictions`;
|
|
24242
|
+
const body = { input };
|
|
24243
|
+
if (model.version !== null)
|
|
24244
|
+
body.version = model.version;
|
|
24245
|
+
const headers = {
|
|
24246
|
+
authorization: `Bearer ${apiToken}`,
|
|
24247
|
+
"content-type": "application/json",
|
|
24248
|
+
Prefer: `wait=${SYNC_WAIT_SECONDS}`
|
|
24249
|
+
};
|
|
24250
|
+
let prediction = await (await fetchFn(url2, { method: "POST", headers, body: JSON.stringify(body) })).json();
|
|
24251
|
+
if (prediction.status !== "succeeded") {
|
|
24252
|
+
if (prediction.urls?.get === undefined)
|
|
24253
|
+
throw new Error("Replicate prediction did not return a poll URL");
|
|
24254
|
+
prediction = await pollPrediction(apiToken, prediction.urls.get);
|
|
24255
|
+
}
|
|
24256
|
+
const outputUrl = extractOutputUrl(prediction);
|
|
24257
|
+
return {
|
|
24258
|
+
bytes: await downloadImage2(outputUrl),
|
|
24259
|
+
extension: extensionFromUrl(outputUrl),
|
|
24260
|
+
provider: "replicate",
|
|
24261
|
+
model: req.model
|
|
24262
|
+
};
|
|
24263
|
+
}
|
|
24264
|
+
};
|
|
24265
|
+
};
|
|
24266
|
+
|
|
24267
|
+
// ../../plugins/generations/image-gen/src/providers/zai.ts
|
|
24268
|
+
var GLM_MAX_PIXELS = 2 ** 22;
|
|
24269
|
+
var LEGACY_MAX_PIXELS = 2 ** 21;
|
|
24270
|
+
var GLM_SIZE_STEP = 32;
|
|
24271
|
+
var LEGACY_SIZE_STEP = 16;
|
|
24272
|
+
var GLM_RECOMMENDED_SIZES = {
|
|
24273
|
+
"1:1": "1280x1280",
|
|
24274
|
+
"3:2": "1568x1056",
|
|
24275
|
+
"2:3": "1056x1568",
|
|
24276
|
+
"4:3": "1472x1088",
|
|
24277
|
+
"3:4": "1088x1472",
|
|
24278
|
+
"16:9": "1728x960",
|
|
24279
|
+
"9:16": "960x1728"
|
|
24280
|
+
};
|
|
24281
|
+
var LEGACY_RECOMMENDED_SIZES = {
|
|
24282
|
+
"1:1": "1024x1024",
|
|
24283
|
+
"9:16": "768x1344",
|
|
24284
|
+
"3:4": "864x1152",
|
|
24285
|
+
"16:9": "1344x768",
|
|
24286
|
+
"4:3": "1152x864",
|
|
24287
|
+
"2:1": "1440x720",
|
|
24288
|
+
"1:2": "720x1440"
|
|
24289
|
+
};
|
|
24290
|
+
function buildZaiUrl(base) {
|
|
24291
|
+
const cleaned = base.replace(/\/+$/g, "");
|
|
24292
|
+
if (cleaned.endsWith("/images/generations"))
|
|
24293
|
+
return cleaned;
|
|
24294
|
+
if (cleaned.endsWith("/api/paas/v4") || cleaned.endsWith("/v4"))
|
|
24295
|
+
return `${cleaned}/images/generations`;
|
|
24296
|
+
return `${cleaned}/api/paas/v4/images/generations`;
|
|
24297
|
+
}
|
|
24298
|
+
function getModelFamily2(model) {
|
|
24299
|
+
return model.trim().toLowerCase() === "glm-image" ? "glm" : "legacy";
|
|
24300
|
+
}
|
|
24301
|
+
function parseAspectRatio4(ar) {
|
|
24302
|
+
const match = ar.match(/^(\d+(?:\.\d+)?):(\d+(?:\.\d+)?)$/);
|
|
24303
|
+
if (match === null)
|
|
24304
|
+
return null;
|
|
24305
|
+
const width = Number(match[1]);
|
|
24306
|
+
const height = Number(match[2]);
|
|
24307
|
+
if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0)
|
|
24308
|
+
return null;
|
|
24309
|
+
return { width, height };
|
|
24310
|
+
}
|
|
24311
|
+
function parseSize2(size) {
|
|
24312
|
+
const match = size.trim().match(/^(\d+)\s*[xX*]\s*(\d+)$/);
|
|
24313
|
+
if (match === null)
|
|
24314
|
+
return null;
|
|
24315
|
+
const width = Number.parseInt(match[1], 10);
|
|
24316
|
+
const height = Number.parseInt(match[2], 10);
|
|
24317
|
+
if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0)
|
|
24318
|
+
return null;
|
|
24319
|
+
return { width, height };
|
|
24320
|
+
}
|
|
24321
|
+
function roundToStep2(value, step) {
|
|
24322
|
+
return Math.max(step, Math.round(value / step) * step);
|
|
24323
|
+
}
|
|
24324
|
+
var DEFAULT_MODEL12 = "glm-image";
|
|
24325
|
+
function getRatioValue(ar) {
|
|
24326
|
+
const parsed = parseAspectRatio4(ar);
|
|
24327
|
+
return parsed === null ? null : parsed.width / parsed.height;
|
|
24328
|
+
}
|
|
24329
|
+
function findClosestRatioKey(ar, candidates) {
|
|
24330
|
+
const targetRatio = getRatioValue(ar);
|
|
24331
|
+
if (targetRatio === null)
|
|
24332
|
+
return null;
|
|
24333
|
+
let bestKey = null;
|
|
24334
|
+
let bestDiff = Number.POSITIVE_INFINITY;
|
|
24335
|
+
for (const candidate of candidates) {
|
|
24336
|
+
const candidateRatio = getRatioValue(candidate);
|
|
24337
|
+
if (candidateRatio === null)
|
|
24338
|
+
continue;
|
|
24339
|
+
const diff = Math.abs(candidateRatio - targetRatio);
|
|
24340
|
+
if (diff < bestDiff) {
|
|
24341
|
+
bestDiff = diff;
|
|
24342
|
+
bestKey = candidate;
|
|
24343
|
+
}
|
|
24344
|
+
}
|
|
24345
|
+
return bestDiff <= 0.05 ? bestKey : null;
|
|
24346
|
+
}
|
|
24347
|
+
function fitToPixelBudget2(width, height, targetPixels, maxPixels, step) {
|
|
24348
|
+
let nextWidth = width;
|
|
24349
|
+
let nextHeight = height;
|
|
24350
|
+
const pixels = nextWidth * nextHeight;
|
|
24351
|
+
const scaleTo = pixels > maxPixels ? maxPixels : targetPixels;
|
|
24352
|
+
const scale = Math.sqrt(scaleTo / pixels);
|
|
24353
|
+
nextWidth *= scale;
|
|
24354
|
+
nextHeight *= scale;
|
|
24355
|
+
let roundedWidth = roundToStep2(nextWidth, step);
|
|
24356
|
+
let roundedHeight = roundToStep2(nextHeight, step);
|
|
24357
|
+
let roundedPixels = roundedWidth * roundedHeight;
|
|
24358
|
+
while (roundedPixels > maxPixels && (roundedWidth > step || roundedHeight > step)) {
|
|
24359
|
+
if (roundedWidth >= roundedHeight && roundedWidth > step) {
|
|
24360
|
+
roundedWidth -= step;
|
|
24361
|
+
} else if (roundedHeight > step) {
|
|
24362
|
+
roundedHeight -= step;
|
|
24363
|
+
} else {
|
|
24364
|
+
break;
|
|
24365
|
+
}
|
|
24366
|
+
roundedPixels = roundedWidth * roundedHeight;
|
|
24367
|
+
}
|
|
24368
|
+
return { width: roundedWidth, height: roundedHeight };
|
|
24369
|
+
}
|
|
24370
|
+
function resolveSizeForModel2(model, args) {
|
|
24371
|
+
const family = getModelFamily2(model);
|
|
24372
|
+
if (args.size !== undefined) {
|
|
24373
|
+
const parsed = parseSize2(args.size);
|
|
24374
|
+
if (parsed === null)
|
|
24375
|
+
throw new Error("Z.AI --size must be in WxH format, for example 1280x1280.");
|
|
24376
|
+
const widthStep = family === "glm" ? GLM_SIZE_STEP : LEGACY_SIZE_STEP;
|
|
24377
|
+
const minEdge = family === "glm" ? 1024 : 512;
|
|
24378
|
+
const maxPixels2 = family === "glm" ? GLM_MAX_PIXELS : LEGACY_MAX_PIXELS;
|
|
24379
|
+
if (parsed.width < minEdge || parsed.width > 2048 || parsed.height < minEdge || parsed.height > 2048) {
|
|
24380
|
+
throw new Error(family === "glm" ? "GLM-image custom size requires width and height between 1024 and 2048." : "Z.AI legacy image models require width and height between 512 and 2048.");
|
|
24381
|
+
}
|
|
24382
|
+
if (parsed.width % widthStep !== 0 || parsed.height % widthStep !== 0) {
|
|
24383
|
+
throw new Error(family === "glm" ? "GLM-image custom size requires width and height divisible by 32." : "Z.AI legacy image models require width and height divisible by 16.");
|
|
24384
|
+
}
|
|
24385
|
+
if (parsed.width * parsed.height > maxPixels2) {
|
|
24386
|
+
throw new Error(family === "glm" ? "GLM-image custom size must not exceed 2^22 total pixels." : "Z.AI legacy image size must not exceed 2^21 total pixels.");
|
|
24387
|
+
}
|
|
24388
|
+
return `${parsed.width}x${parsed.height}`;
|
|
24389
|
+
}
|
|
24390
|
+
const recommended = family === "glm" ? GLM_RECOMMENDED_SIZES : LEGACY_RECOMMENDED_SIZES;
|
|
24391
|
+
const defaultSize = family === "glm" ? "1280x1280" : "1024x1024";
|
|
24392
|
+
if (args.aspectRatio === undefined)
|
|
24393
|
+
return defaultSize;
|
|
24394
|
+
const recommendedRatio = findClosestRatioKey(args.aspectRatio, Object.keys(recommended));
|
|
24395
|
+
if (recommendedRatio !== null) {
|
|
24396
|
+
return recommended[recommendedRatio];
|
|
24397
|
+
}
|
|
24398
|
+
const parsedRatio = parseAspectRatio4(args.aspectRatio);
|
|
24399
|
+
if (parsedRatio === null)
|
|
24400
|
+
return defaultSize;
|
|
24401
|
+
const targetPixels = args.quality === "normal" ? 1024 * 1024 : 1536 * 1536;
|
|
24402
|
+
const maxPixels = family === "glm" ? GLM_MAX_PIXELS : LEGACY_MAX_PIXELS;
|
|
24403
|
+
const step = family === "glm" ? GLM_SIZE_STEP : LEGACY_SIZE_STEP;
|
|
24404
|
+
const fit = fitToPixelBudget2(parsedRatio.width, parsedRatio.height, targetPixels, maxPixels, step);
|
|
24405
|
+
return `${fit.width}x${fit.height}`;
|
|
24406
|
+
}
|
|
24407
|
+
var createZai = (options) => {
|
|
24408
|
+
const fetchFn = options?.fetch ?? fetch;
|
|
24409
|
+
const env = options?.env ?? process.env;
|
|
24410
|
+
const readEnv = (key) => env[key] ?? "";
|
|
24411
|
+
return {
|
|
24412
|
+
name: "zai",
|
|
24413
|
+
defaultModel: () => DEFAULT_MODEL12,
|
|
24414
|
+
isAvailable: () => (readEnv("ZAI_API_KEY") || readEnv("BIGMODEL_API_KEY")) !== "",
|
|
24415
|
+
supportsReferenceImages: () => false,
|
|
24416
|
+
validate(req) {
|
|
24417
|
+
if ((req.referenceImages?.length ?? 0) > 0) {
|
|
24418
|
+
throw new Error("Z.AI GLM-image currently supports text-to-image only in kk image-gen. Remove --ref or choose another provider.");
|
|
24419
|
+
}
|
|
24420
|
+
},
|
|
24421
|
+
async generate(req) {
|
|
24422
|
+
const refs = req.referenceImages ?? [];
|
|
24423
|
+
if (refs.length > 0) {
|
|
24424
|
+
throw new Error("Z.AI GLM-image currently supports text-to-image only in kk image-gen. Remove --ref or choose another provider.");
|
|
24425
|
+
}
|
|
24426
|
+
const apiKey = readEnv("ZAI_API_KEY") || readEnv("BIGMODEL_API_KEY");
|
|
24427
|
+
if (apiKey === "")
|
|
24428
|
+
throw new Error("ZAI_API_KEY is required. Get one from https://docs.z.ai/.");
|
|
24429
|
+
const size = resolveSizeForModel2(req.model, {
|
|
24430
|
+
size: req.width !== undefined && req.height !== undefined ? `${req.width}x${req.height}` : undefined,
|
|
24431
|
+
aspectRatio: req.aspectRatio,
|
|
24432
|
+
quality: req.quality
|
|
24433
|
+
});
|
|
24434
|
+
const base = readEnv("ZAI_BASE_URL") || readEnv("BIGMODEL_BASE_URL") || "https://api.z.ai/api/paas/v4";
|
|
24435
|
+
const response = await fetchFn(buildZaiUrl(base), {
|
|
24436
|
+
method: "POST",
|
|
24437
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${apiKey}` },
|
|
24438
|
+
body: JSON.stringify({
|
|
24439
|
+
model: req.model,
|
|
24440
|
+
prompt: req.prompt,
|
|
24441
|
+
quality: req.quality === "normal" ? "standard" : "hd",
|
|
24442
|
+
size
|
|
24443
|
+
})
|
|
24444
|
+
});
|
|
24445
|
+
if (!response.ok)
|
|
24446
|
+
throw new Error(`Z.AI API error (${response.status}): ${await response.text()}`);
|
|
24447
|
+
const url2 = (await response.json()).data?.[0]?.url;
|
|
24448
|
+
if (url2 === undefined)
|
|
24449
|
+
throw new Error("No image URL in Z.AI response");
|
|
24450
|
+
const imageResponse = await fetchFn(url2);
|
|
24451
|
+
if (!imageResponse.ok)
|
|
24452
|
+
throw new Error(`Failed to download image from Z.AI: ${imageResponse.status}`);
|
|
24453
|
+
return {
|
|
24454
|
+
bytes: new Uint8Array(await imageResponse.arrayBuffer()),
|
|
24455
|
+
extension: "png",
|
|
24456
|
+
provider: "zai",
|
|
24457
|
+
model: req.model
|
|
24458
|
+
};
|
|
24459
|
+
}
|
|
24460
|
+
};
|
|
24461
|
+
};
|
|
24462
|
+
|
|
24463
|
+
// ../../plugins/generations/image-gen/src/providers/types.ts
|
|
24464
|
+
var IMAGE_GEN_PROVIDER_ENV = "IMAGE_GEN_PROVIDER";
|
|
24465
|
+
var NEVER_AUTO = new Set(["codex-cli"]);
|
|
22241
24466
|
var PROVIDERS = {
|
|
22242
|
-
agnes:
|
|
24467
|
+
agnes: {
|
|
22243
24468
|
name: "agnes",
|
|
22244
24469
|
env: ["AGNES_API_KEY"],
|
|
22245
|
-
defaultModel: "agnes-image-2.1-flash"
|
|
22246
|
-
|
|
22247
|
-
|
|
24470
|
+
defaultModel: "agnes-image-2.1-flash",
|
|
24471
|
+
create: createAgnes
|
|
24472
|
+
},
|
|
24473
|
+
azure: {
|
|
22248
24474
|
name: "azure",
|
|
22249
24475
|
env: ["AZURE_OPENAI_API_KEY"],
|
|
22250
|
-
defaultModel: "gpt-image-2"
|
|
22251
|
-
|
|
22252
|
-
|
|
24476
|
+
defaultModel: "gpt-image-2",
|
|
24477
|
+
create: createAzure
|
|
24478
|
+
},
|
|
24479
|
+
"codex-cli": {
|
|
22253
24480
|
name: "codex-cli",
|
|
22254
24481
|
env: [],
|
|
22255
24482
|
defaultModel: "codex-image-gen",
|
|
22256
|
-
note: "requires the 'codex' CLI on PATH with an active login (never auto-selected)"
|
|
22257
|
-
|
|
22258
|
-
|
|
24483
|
+
note: "requires the 'codex' CLI on PATH with an active login (never auto-selected)",
|
|
24484
|
+
create: createCodexCli
|
|
24485
|
+
},
|
|
24486
|
+
dashscope: {
|
|
22259
24487
|
name: "dashscope",
|
|
22260
24488
|
env: ["DASHSCOPE_API_KEY"],
|
|
22261
24489
|
defaultModel: "qwen-image-2.0-pro",
|
|
22262
|
-
refModels: ["wan2.7"]
|
|
22263
|
-
|
|
22264
|
-
|
|
24490
|
+
refModels: ["wan2.7"],
|
|
24491
|
+
create: createDashscope
|
|
24492
|
+
},
|
|
24493
|
+
google: {
|
|
22265
24494
|
name: "google",
|
|
22266
24495
|
env: ["GOOGLE_API_KEY", "GEMINI_API_KEY"],
|
|
22267
24496
|
defaultModel: "gemini-3-pro-image",
|
|
@@ -22271,35 +24500,47 @@ var PROVIDERS = {
|
|
|
22271
24500
|
"gemini-3-pro-image-preview",
|
|
22272
24501
|
"gemini-3-flash-preview",
|
|
22273
24502
|
"gemini-3.1-flash-image-preview"
|
|
22274
|
-
]
|
|
22275
|
-
|
|
22276
|
-
|
|
24503
|
+
],
|
|
24504
|
+
create: createGoogle
|
|
24505
|
+
},
|
|
24506
|
+
huggingface: {
|
|
24507
|
+
name: "huggingface",
|
|
24508
|
+
env: ["HUGGINGFACE_API_TOKEN"],
|
|
24509
|
+
defaultModel: "stabilityai/stable-diffusion-xl-base-1.0",
|
|
24510
|
+
create: createHuggingface
|
|
24511
|
+
},
|
|
24512
|
+
jimeng: {
|
|
22277
24513
|
name: "jimeng",
|
|
22278
24514
|
env: [],
|
|
22279
24515
|
envAll: ["JIMENG_ACCESS_KEY_ID", "JIMENG_SECRET_ACCESS_KEY"],
|
|
22280
|
-
defaultModel: "jimeng_t2i_v40"
|
|
22281
|
-
|
|
22282
|
-
|
|
24516
|
+
defaultModel: "jimeng_t2i_v40",
|
|
24517
|
+
create: createJimeng
|
|
24518
|
+
},
|
|
24519
|
+
minimax: {
|
|
22283
24520
|
name: "minimax",
|
|
22284
24521
|
env: ["MINIMAX_API_KEY"],
|
|
22285
|
-
defaultModel: "image-01"
|
|
22286
|
-
|
|
22287
|
-
|
|
24522
|
+
defaultModel: "image-01",
|
|
24523
|
+
create: createMinimax
|
|
24524
|
+
},
|
|
24525
|
+
openai: {
|
|
22288
24526
|
name: "openai",
|
|
22289
24527
|
env: ["OPENAI_API_KEY"],
|
|
22290
24528
|
defaultModel: "gpt-image-2",
|
|
22291
|
-
refModels: ["gpt-image"]
|
|
22292
|
-
|
|
22293
|
-
|
|
24529
|
+
refModels: ["gpt-image"],
|
|
24530
|
+
create: createOpenai
|
|
24531
|
+
},
|
|
24532
|
+
openrouter: {
|
|
22294
24533
|
name: "openrouter",
|
|
22295
24534
|
env: ["OPENROUTER_API_KEY"],
|
|
22296
|
-
defaultModel: "google/gemini-3.1-flash-image"
|
|
22297
|
-
|
|
22298
|
-
|
|
24535
|
+
defaultModel: "google/gemini-3.1-flash-image",
|
|
24536
|
+
create: createOpenrouter
|
|
24537
|
+
},
|
|
24538
|
+
replicate: {
|
|
22299
24539
|
name: "replicate",
|
|
22300
24540
|
env: ["REPLICATE_API_TOKEN"],
|
|
22301
|
-
defaultModel: "google/nano-banana-2"
|
|
22302
|
-
|
|
24541
|
+
defaultModel: "google/nano-banana-2",
|
|
24542
|
+
create: createReplicate
|
|
24543
|
+
},
|
|
22303
24544
|
seedream: {
|
|
22304
24545
|
name: "seedream",
|
|
22305
24546
|
env: ["ARK_API_KEY"],
|
|
@@ -22307,14 +24548,16 @@ var PROVIDERS = {
|
|
|
22307
24548
|
refModels: ["doubao-seedream-5-0", "doubao-seedream-4-5-", "doubao-seedream-4-0-"],
|
|
22308
24549
|
create: createSeedream
|
|
22309
24550
|
},
|
|
22310
|
-
zai:
|
|
24551
|
+
zai: {
|
|
22311
24552
|
name: "zai",
|
|
22312
24553
|
env: ["ZAI_API_KEY", "BIGMODEL_API_KEY"],
|
|
22313
24554
|
defaultModel: "glm-image",
|
|
22314
|
-
refModels: []
|
|
22315
|
-
|
|
24555
|
+
refModels: [],
|
|
24556
|
+
create: createZai
|
|
24557
|
+
}
|
|
22316
24558
|
};
|
|
22317
24559
|
var AUTO_ORDER = [
|
|
24560
|
+
"huggingface",
|
|
22318
24561
|
"google",
|
|
22319
24562
|
"openai",
|
|
22320
24563
|
"replicate",
|