dsh-image-gen 0.9.1 → 0.9.3
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/README.en.md +454 -442
- package/README.md +454 -442
- package/lib/client.js +349 -46
- package/lib/index.js +323 -165
- package/lib/types/client/index.d.ts +3 -1
- package/lib/types/client/studio-style.d.ts +1 -1
- package/lib/types/client/tl/conversation-landings.d.ts +16 -1
- package/lib/types/client/tl/gallery-canvas-button.d.ts +10 -0
- package/lib/types/client/tl/gallery-image-transfer.d.ts +12 -0
- package/lib/types/client/tl/studio-tl-canvas.d.ts +3 -0
- package/lib/types/client/tl/tl-canvas-bridge.d.ts +6 -0
- package/lib/types/config.d.ts +7 -2
- package/lib/types/google.d.ts +4 -1
- package/lib/types/image-tool-context.d.ts +3 -0
- package/lib/types/openai-compatible.d.ts +1 -1
- package/lib/types/shared.d.ts +4 -0
- package/package.json +139 -139
- package/skills/install-dsh-image-gen/SKILL.md +22 -22
package/lib/index.js
CHANGED
|
@@ -5604,7 +5604,7 @@ function configuredField(extra) {
|
|
|
5604
5604
|
}
|
|
5605
5605
|
/** Model names alone do not establish the protocol of an unknown relay. */
|
|
5606
5606
|
function officialContract(config, mode) {
|
|
5607
|
-
const model = (mode === "generate" ? config.openaiCompatExtraBody : config.openaiCompatEditExtra)?.model ?? config.openaiCompatModel;
|
|
5607
|
+
const model = (mode === "generate" ? config.openaiCompatExtraBody : config.openaiCompatEditExtra)?.model ?? (mode === "edit" ? config.openaiCompatEditModel?.trim() || config.openaiCompatModel : config.openaiCompatModel);
|
|
5608
5608
|
if (typeof model !== "string") return void 0;
|
|
5609
5609
|
try {
|
|
5610
5610
|
const url = new URL(config.openaiCompatBaseURL ?? "");
|
|
@@ -5616,6 +5616,10 @@ function officialContract(config, mode) {
|
|
|
5616
5616
|
}
|
|
5617
5617
|
/** Known official edit contracts take precedence over relay-specific format settings. */
|
|
5618
5618
|
function compatEditFormat(config) {
|
|
5619
|
+
try {
|
|
5620
|
+
const url = new URL(config.openaiCompatBaseURL ?? "");
|
|
5621
|
+
if (url.protocol === "https:" && !url.port && !url.search && !url.hash && (url.hostname === "api.siliconflow.cn" || url.hostname === "api.siliconflow.com") && url.pathname.replace(/\/+$/, "") === "/v1") return "siliconflowJson";
|
|
5622
|
+
} catch {}
|
|
5619
5623
|
const contract = officialContract(config, "edit");
|
|
5620
5624
|
if (contract === "sensenova") return "jsonImageUrlArray";
|
|
5621
5625
|
if (contract === "qwen") return "qwenJson";
|
|
@@ -5827,6 +5831,10 @@ const STUDIO_WATERMARK_VALUES = [
|
|
|
5827
5831
|
"off",
|
|
5828
5832
|
"on"
|
|
5829
5833
|
];
|
|
5834
|
+
/** A model picker/request must use the same operation-specific default. */
|
|
5835
|
+
function studioModelForMode(profile, mode) {
|
|
5836
|
+
return mode === "edit" ? profile.editModel ?? profile.model : profile.model;
|
|
5837
|
+
}
|
|
5830
5838
|
function studioWatermarkCapability(profile, mode) {
|
|
5831
5839
|
return profile?.watermarkCapabilities?.[mode] ?? (mode === "generate" && profile?.watermarkOptions?.length ? { status: "supported" } : {
|
|
5832
5840
|
status: "unknown",
|
|
@@ -5955,11 +5963,13 @@ const Config = z.object({
|
|
|
5955
5963
|
provider: volatile(z.union(IMAGE_PROVIDERS).default("google")),
|
|
5956
5964
|
googleModel: volatile(z.string().default(DEFAULT_GOOGLE_MODEL)),
|
|
5957
5965
|
googleEndpoint: volatile(z.string().default(DEFAULT_GOOGLE_ENDPOINT)),
|
|
5966
|
+
googleNative: volatile(z.boolean().default(false)),
|
|
5958
5967
|
openaiBaseURL: volatile(z.string().default(DEFAULT_OPENAI_BASE_URL)),
|
|
5959
5968
|
openaiModel: volatile(z.string().default(DEFAULT_OPENAI_MODEL)),
|
|
5960
5969
|
openaiSizes: volatile(z.dict(z.dict(z.string())).default({})),
|
|
5961
5970
|
openaiCompatBaseURL: volatile(z.string().default("")),
|
|
5962
5971
|
openaiCompatModel: volatile(z.string().default("")),
|
|
5972
|
+
openaiCompatEditModel: volatile(z.string().default("")),
|
|
5963
5973
|
openaiCompatQuality: volatile(z.string().default("")),
|
|
5964
5974
|
openaiCompatQualityOptions: volatile(z.array(z.string()).default([
|
|
5965
5975
|
"auto",
|
|
@@ -5972,7 +5982,8 @@ const Config = z.object({
|
|
|
5972
5982
|
openaiCompatEditFormat: volatile(z.union([
|
|
5973
5983
|
z.const("multipart"),
|
|
5974
5984
|
z.const("jsonImageUrlArray"),
|
|
5975
|
-
z.const("formReferenceImages")
|
|
5985
|
+
z.const("formReferenceImages"),
|
|
5986
|
+
z.const("siliconflowJson")
|
|
5976
5987
|
]).default("multipart")),
|
|
5977
5988
|
openaiCompatEditExtra: volatile(z.dict(z.any()).default({})),
|
|
5978
5989
|
openaiCompatSizes: volatile(z.dict(z.dict(z.string())).default({})),
|
|
@@ -6090,6 +6101,7 @@ function resolveProvider(config) {
|
|
|
6090
6101
|
apiKeyEnv: GOOGLE_API_KEY_ENV,
|
|
6091
6102
|
model: config.googleModel ?? "gemini-3.1-flash-image",
|
|
6092
6103
|
endpoint: config.googleEndpoint ?? "https://generativelanguage.googleapis.com/v1beta/interactions",
|
|
6104
|
+
native: config.googleNative === true,
|
|
6093
6105
|
aspectRatio: "1:1",
|
|
6094
6106
|
imageSize: "1K"
|
|
6095
6107
|
};
|
|
@@ -6099,13 +6111,12 @@ function resolveProvider(config) {
|
|
|
6099
6111
|
* Apply a per-call provider and/or model override on top of the saved config.
|
|
6100
6112
|
* `model` is ignored for ComfyUI, whose per-call equivalent is `workflow`.
|
|
6101
6113
|
*/
|
|
6102
|
-
function withProviderOverrides(config, provider, model) {
|
|
6114
|
+
function withProviderOverrides(config, provider, model, mode = "generate") {
|
|
6103
6115
|
const base = provider === void 0 ? { ...config } : {
|
|
6104
6116
|
...config,
|
|
6105
6117
|
provider
|
|
6106
6118
|
};
|
|
6107
|
-
|
|
6108
|
-
const trimmed = model.trim();
|
|
6119
|
+
const trimmed = model?.trim() || (mode === "edit" && base.provider === "openai-compat" ? base.openaiCompatEditModel?.trim() : "") || "";
|
|
6109
6120
|
if (trimmed.length === 0) return base;
|
|
6110
6121
|
switch (base.provider ?? "google") {
|
|
6111
6122
|
case "google": return {
|
|
@@ -6118,7 +6129,8 @@ function withProviderOverrides(config, provider, model) {
|
|
|
6118
6129
|
};
|
|
6119
6130
|
case "openai-compat": return {
|
|
6120
6131
|
...base,
|
|
6121
|
-
openaiCompatModel: trimmed
|
|
6132
|
+
openaiCompatModel: trimmed,
|
|
6133
|
+
...mode === "edit" ? { openaiCompatEditModel: trimmed } : {}
|
|
6122
6134
|
};
|
|
6123
6135
|
case "seedream": return {
|
|
6124
6136
|
...base,
|
|
@@ -7563,12 +7575,13 @@ function generateGoogleImage(input) {
|
|
|
7563
7575
|
return requestGoogleImage({
|
|
7564
7576
|
...input,
|
|
7565
7577
|
operation: "generation",
|
|
7566
|
-
interactionInput: input.prompt
|
|
7578
|
+
interactionInput: input.prompt,
|
|
7579
|
+
nativeParts: [{ text: input.prompt }]
|
|
7567
7580
|
});
|
|
7568
7581
|
}
|
|
7569
7582
|
/** Send one native Google image-editing request using already-resolved bytes. */
|
|
7570
7583
|
function editGoogleImage(input) {
|
|
7571
|
-
const interactionInput = input.sourceImages.length === 1 ? [{
|
|
7584
|
+
const interactionInput = input.native ? [] : input.sourceImages.length === 1 ? [{
|
|
7572
7585
|
type: "image",
|
|
7573
7586
|
mime_type: input.sourceImages[0].mediaType,
|
|
7574
7587
|
data: Buffer.from(input.sourceImages[0].data).toString("base64")
|
|
@@ -7589,13 +7602,41 @@ function editGoogleImage(input) {
|
|
|
7589
7602
|
return requestGoogleImage({
|
|
7590
7603
|
...input,
|
|
7591
7604
|
operation: "editing",
|
|
7592
|
-
interactionInput
|
|
7605
|
+
interactionInput,
|
|
7606
|
+
nativeParts: input.native ? [...input.sourceImages.map((image) => ({ inlineData: {
|
|
7607
|
+
mimeType: image.mediaType,
|
|
7608
|
+
data: Buffer.from(image.data).toString("base64")
|
|
7609
|
+
} })), { text: input.prompt }] : []
|
|
7593
7610
|
});
|
|
7594
7611
|
}
|
|
7595
7612
|
/** Shared Google request, response parsing, decoding, and size enforcement. */
|
|
7596
7613
|
async function requestGoogleImage(input) {
|
|
7597
7614
|
const label = `Google image ${input.operation}`;
|
|
7598
|
-
const
|
|
7615
|
+
const endpoint = input.native ? nativeGoogleUrl(input.endpoint, input.model).toString() : input.endpoint;
|
|
7616
|
+
const body = input.native ? {
|
|
7617
|
+
contents: [{
|
|
7618
|
+
role: "user",
|
|
7619
|
+
parts: input.nativeParts
|
|
7620
|
+
}],
|
|
7621
|
+
generationConfig: {
|
|
7622
|
+
candidateCount: 1,
|
|
7623
|
+
responseModalities: ["TEXT", "IMAGE"],
|
|
7624
|
+
imageConfig: {
|
|
7625
|
+
aspectRatio: input.aspectRatio,
|
|
7626
|
+
imageSize: input.imageSize
|
|
7627
|
+
}
|
|
7628
|
+
}
|
|
7629
|
+
} : {
|
|
7630
|
+
model: input.model,
|
|
7631
|
+
input: input.interactionInput,
|
|
7632
|
+
response_format: {
|
|
7633
|
+
type: "image",
|
|
7634
|
+
mime_type: REQUESTED_MEDIA_TYPE,
|
|
7635
|
+
aspect_ratio: input.aspectRatio,
|
|
7636
|
+
image_size: input.imageSize
|
|
7637
|
+
}
|
|
7638
|
+
};
|
|
7639
|
+
const response = await guardedFetch(`${label} request`, input.signal, () => fetch(endpoint, {
|
|
7599
7640
|
method: "POST",
|
|
7600
7641
|
redirect: "error",
|
|
7601
7642
|
signal: input.signal,
|
|
@@ -7603,16 +7644,7 @@ async function requestGoogleImage(input) {
|
|
|
7603
7644
|
"content-type": "application/json",
|
|
7604
7645
|
"x-goog-api-key": input.apiKey
|
|
7605
7646
|
},
|
|
7606
|
-
body: JSON.stringify(
|
|
7607
|
-
model: input.model,
|
|
7608
|
-
input: input.interactionInput,
|
|
7609
|
-
response_format: {
|
|
7610
|
-
type: "image",
|
|
7611
|
-
mime_type: REQUESTED_MEDIA_TYPE,
|
|
7612
|
-
aspect_ratio: input.aspectRatio,
|
|
7613
|
-
image_size: input.imageSize
|
|
7614
|
-
}
|
|
7615
|
-
})
|
|
7647
|
+
body: JSON.stringify(body)
|
|
7616
7648
|
}), input.apiKey);
|
|
7617
7649
|
const text = await readBoundedText$2(response, Math.ceil(input.maxBytes * 1.4) + ERROR_LIMIT$2, label);
|
|
7618
7650
|
if (!response.ok) throw new Error(`${label} failed (${response.status}): ${redactSecrets(text, input.apiKey).slice(0, ERROR_LIMIT$2)}`);
|
|
@@ -7622,7 +7654,7 @@ async function requestGoogleImage(input) {
|
|
|
7622
7654
|
} catch {
|
|
7623
7655
|
throw new Error(`${label} returned invalid JSON`);
|
|
7624
7656
|
}
|
|
7625
|
-
const image = outputImage(payload);
|
|
7657
|
+
const image = input.native ? nativeOutputImage(payload) : outputImage(payload);
|
|
7626
7658
|
if (image === void 0) throw new Error(`${label} returned no image: ${redactSecrets(text, input.apiKey).slice(0, ERROR_LIMIT$2)}`);
|
|
7627
7659
|
const data = decodeBase64$2(image.data, label);
|
|
7628
7660
|
if (data.byteLength > input.maxBytes) throw new Error(`${label} exceeded the ${String(input.maxBytes)} byte image limit`);
|
|
@@ -7633,6 +7665,49 @@ async function requestGoogleImage(input) {
|
|
|
7633
7665
|
mediaType
|
|
7634
7666
|
};
|
|
7635
7667
|
}
|
|
7668
|
+
/** Accept a versioned base, the existing interactions URL, or a full native URL. */
|
|
7669
|
+
function nativeGoogleUrl(endpoint, model) {
|
|
7670
|
+
let url;
|
|
7671
|
+
try {
|
|
7672
|
+
url = new URL(endpoint);
|
|
7673
|
+
} catch {
|
|
7674
|
+
throw new Error("Google endpoint must be an absolute URL");
|
|
7675
|
+
}
|
|
7676
|
+
const base = url.pathname.replace(/\/+$/, "").replace(/\/(?:interactions|models(?:\/[^/]+:generateContent)?)$/, "");
|
|
7677
|
+
url.pathname = `${base}/models${model === void 0 ? "" : `/${encodeURIComponent(model.replace(/^models\//, ""))}:generateContent`}`;
|
|
7678
|
+
return url;
|
|
7679
|
+
}
|
|
7680
|
+
/** The settings probe and model picker use the same base as image requests. */
|
|
7681
|
+
function googleModelsEndpoint(endpoint, native) {
|
|
7682
|
+
if (native) return nativeGoogleUrl(endpoint).toString();
|
|
7683
|
+
let url;
|
|
7684
|
+
try {
|
|
7685
|
+
url = new URL(endpoint);
|
|
7686
|
+
} catch {
|
|
7687
|
+
throw new Error("Google endpoint must be an absolute URL");
|
|
7688
|
+
}
|
|
7689
|
+
const segments = url.pathname.split("/").filter(Boolean);
|
|
7690
|
+
segments[segments.length - 1] = "models";
|
|
7691
|
+
url.pathname = `/${segments.join("/")}`;
|
|
7692
|
+
return url.toString();
|
|
7693
|
+
}
|
|
7694
|
+
function nativeOutputImage(value) {
|
|
7695
|
+
const candidates = record$5(value)?.candidates;
|
|
7696
|
+
if (!Array.isArray(candidates)) return void 0;
|
|
7697
|
+
for (const candidate of candidates) {
|
|
7698
|
+
const parts = record$5(record$5(candidate)?.content)?.parts;
|
|
7699
|
+
if (!Array.isArray(parts)) continue;
|
|
7700
|
+
for (const value of parts) {
|
|
7701
|
+
const part = record$5(value);
|
|
7702
|
+
if (part?.thought === true) continue;
|
|
7703
|
+
const image = record$5(part?.inlineData ?? part?.inline_data);
|
|
7704
|
+
if (typeof image?.data === "string") return {
|
|
7705
|
+
data: image.data,
|
|
7706
|
+
mime_type: image.mimeType ?? image.mime_type
|
|
7707
|
+
};
|
|
7708
|
+
}
|
|
7709
|
+
}
|
|
7710
|
+
}
|
|
7636
7711
|
function outputImage(value) {
|
|
7637
7712
|
const interaction = record$5(value);
|
|
7638
7713
|
if (interaction === void 0) return void 0;
|
|
@@ -8198,6 +8273,39 @@ async function generateOpenAICompatibleImage(input) {
|
|
|
8198
8273
|
}), input.apiKey), input.provider, input);
|
|
8199
8274
|
}
|
|
8200
8275
|
async function editOpenAICompatibleImage(input) {
|
|
8276
|
+
if (input.editFormat === "siliconflowJson") {
|
|
8277
|
+
const body = {
|
|
8278
|
+
model: input.model,
|
|
8279
|
+
prompt: input.prompt,
|
|
8280
|
+
...input.editExtra ?? {},
|
|
8281
|
+
...input.extraBody ?? {}
|
|
8282
|
+
};
|
|
8283
|
+
const model = typeof body.model === "string" ? body.model.trim() : "";
|
|
8284
|
+
if (!model) throw new Error("硅基流动图生图需要编辑模型");
|
|
8285
|
+
body.model = model;
|
|
8286
|
+
if (model === "Qwen/Qwen-Image") throw new Error("Qwen/Qwen-Image 是文生图模型;请配置图生图模型,例如 Qwen/Qwen-Image-Edit");
|
|
8287
|
+
const maxReferences = model === "Qwen/Qwen-Image-Edit-2509" ? 3 : 1;
|
|
8288
|
+
if (input.sourceImages.length < 1 || input.sourceImages.length > maxReferences) throw new Error(`${model} 图生图支持 1–${String(maxReferences)} 张参考图,当前 ${String(input.sourceImages.length)} 张;请精简参考图或选择支持多图的编辑模型`);
|
|
8289
|
+
if ([
|
|
8290
|
+
"size",
|
|
8291
|
+
"image_size",
|
|
8292
|
+
"quality"
|
|
8293
|
+
].some((key) => key in body)) throw new Error("此硅基流动图生图格式不发送 size、image_size 或 quality;请从图生图额外字段中移除这些字段");
|
|
8294
|
+
delete body.image;
|
|
8295
|
+
delete body.image2;
|
|
8296
|
+
delete body.image3;
|
|
8297
|
+
for (const [index, source] of input.sourceImages.entries()) body[index === 0 ? "image" : `image${String(index + 1)}`] = toDataUrl$2(source);
|
|
8298
|
+
return parseImageResponse(await guardedFetch("siliconflow image edit request", input.signal, () => fetch(imageEndpoint$1(input.baseURL, "generations"), {
|
|
8299
|
+
method: "POST",
|
|
8300
|
+
redirect: "error",
|
|
8301
|
+
signal: input.signal,
|
|
8302
|
+
headers: {
|
|
8303
|
+
authorization: `Bearer ${input.apiKey}`,
|
|
8304
|
+
"content-type": "application/json"
|
|
8305
|
+
},
|
|
8306
|
+
body: JSON.stringify(body)
|
|
8307
|
+
}), input.apiKey), "siliconflow", input);
|
|
8308
|
+
}
|
|
8201
8309
|
if (input.editFormat === "qwenJson") return parseImageResponse(await guardedFetch("openai-compat image edit request", input.signal, () => fetch(imageEndpoint$1(input.baseURL, "generations"), {
|
|
8202
8310
|
method: "POST",
|
|
8203
8311
|
redirect: "error",
|
|
@@ -10093,16 +10201,18 @@ function jsonError$3(res, status, code) {
|
|
|
10093
10201
|
//#region lib/types/subscription.js
|
|
10094
10202
|
/** Translate image tool options into each subscription channel's wire inputs. */
|
|
10095
10203
|
function subscriptionToolParameters(provider, options) {
|
|
10096
|
-
|
|
10097
|
-
const ratio = options.aspectRatio;
|
|
10204
|
+
const requestedSize = options.size?.trim() || void 0;
|
|
10205
|
+
const ratio = options.aspectRatio?.trim() || void 0;
|
|
10206
|
+
const imageSize = options.imageSize?.trim() || void 0;
|
|
10207
|
+
if (requestedSize !== void 0 && ratio !== void 0) throw new Error("size 与 aspect_ratio 请只选一个;无关可选字段请省略,不要填写空字符串");
|
|
10098
10208
|
if (ratio !== void 0 && !SUBSCRIPTION_CAPABILITIES[provider].ratioOptions.some((option) => option.value === ratio)) throw new Error(`${provider} 不支持比例 ${ratio}`);
|
|
10099
|
-
const size =
|
|
10209
|
+
const size = requestedSize ?? (ratio === void 0 ? void 0 : provider === "chatgpt-sub" ? SUBSCRIPTION_CAPABILITIES[provider].sizeFor?.(ratio, "auto") : ratio);
|
|
10100
10210
|
let quality;
|
|
10101
|
-
if (
|
|
10102
|
-
if (provider === "grok-sub" && (
|
|
10103
|
-
else if (provider === "google-sub" &&
|
|
10104
|
-
else if (provider === "google-sub" &&
|
|
10105
|
-
else throw new Error(`${provider} 不支持清晰度 ${
|
|
10211
|
+
if (imageSize !== void 0) {
|
|
10212
|
+
if (provider === "grok-sub" && (imageSize === "1K" || imageSize === "2K")) quality = imageSize.toLowerCase();
|
|
10213
|
+
else if (provider === "google-sub" && imageSize === "4K") quality = "hd";
|
|
10214
|
+
else if (provider === "google-sub" && imageSize === "1K") quality = void 0;
|
|
10215
|
+
else throw new Error(`${provider} 不支持清晰度 ${imageSize};${provider === "chatgpt-sub" ? "请省略 image_size,使用 size 或 aspect_ratio 指定尺寸" : `image_size 仅支持 ${provider === "grok-sub" ? "1K、2K" : "1K、4K"}`}`);
|
|
10106
10216
|
}
|
|
10107
10217
|
return {
|
|
10108
10218
|
...size === void 0 ? {} : { size },
|
|
@@ -27771,6 +27881,7 @@ async function generateFromStudio(ctx, config, input, signal, fallbackWorkspaceR
|
|
|
27771
27881
|
generated = input.mode === "edit" ? await editGoogleImage({
|
|
27772
27882
|
apiKey: credential,
|
|
27773
27883
|
endpoint: wired.endpoint,
|
|
27884
|
+
native: wired.native,
|
|
27774
27885
|
model: wired.model,
|
|
27775
27886
|
prompt: input.prompt,
|
|
27776
27887
|
sourceImages,
|
|
@@ -27781,6 +27892,7 @@ async function generateFromStudio(ctx, config, input, signal, fallbackWorkspaceR
|
|
|
27781
27892
|
}) : await generateGoogleImage({
|
|
27782
27893
|
apiKey: credential,
|
|
27783
27894
|
endpoint: wired.endpoint,
|
|
27895
|
+
native: wired.native,
|
|
27784
27896
|
model: wired.model,
|
|
27785
27897
|
prompt: input.prompt,
|
|
27786
27898
|
aspectRatio,
|
|
@@ -28055,8 +28167,10 @@ function subscriptionResponse(input, startedAt, requestedCount, failedCount, ite
|
|
|
28055
28167
|
}
|
|
28056
28168
|
function studioProfile(config, provider, configured) {
|
|
28057
28169
|
const result = cloudStudioProfile(config, provider, configured);
|
|
28170
|
+
const editModel = provider === "openai-compat" ? config.openaiCompatEditModel?.trim() : void 0;
|
|
28058
28171
|
return {
|
|
28059
28172
|
...result,
|
|
28173
|
+
...editModel ? { editModel } : {},
|
|
28060
28174
|
...watermarkProfile(config, provider, result.model)
|
|
28061
28175
|
};
|
|
28062
28176
|
}
|
|
@@ -28154,7 +28268,7 @@ function option(value) {
|
|
|
28154
28268
|
};
|
|
28155
28269
|
}
|
|
28156
28270
|
function assertAllowed(profile, input) {
|
|
28157
|
-
if (input.model !== profile.
|
|
28271
|
+
if (input.model !== studioModelForMode(profile, input.mode)) throw new Error("模型配置已变化,请刷新工作台后重试");
|
|
28158
28272
|
if (!profile.ratioOptions.some((option) => option.value === input.ratio)) throw new Error("该 Provider 不支持所选比例");
|
|
28159
28273
|
const selectedQuality = input.provider === "openai" ? input.generationQuality ?? input.quality : input.resolution ?? input.quality;
|
|
28160
28274
|
if (!profile.qualityOptions.some((option) => option.value === selectedQuality)) throw new Error("该 Provider 不支持所选清晰度");
|
|
@@ -28413,15 +28527,7 @@ function googleModelsUrl(config) {
|
|
|
28413
28527
|
const endpoint = config.googleEndpoint;
|
|
28414
28528
|
const fallback = "https://generativelanguage.googleapis.com/v1beta/models";
|
|
28415
28529
|
if (typeof endpoint !== "string" || endpoint.trim().length === 0) return fallback;
|
|
28416
|
-
|
|
28417
|
-
const url = new URL(endpoint);
|
|
28418
|
-
const segments = url.pathname.split("/").filter((segment) => segment.length > 0);
|
|
28419
|
-
segments[segments.length - 1] = "models";
|
|
28420
|
-
url.pathname = `/${segments.join("/")}`;
|
|
28421
|
-
return url.toString();
|
|
28422
|
-
} catch {
|
|
28423
|
-
throw new Error("Google endpoint must be an absolute URL");
|
|
28424
|
-
}
|
|
28530
|
+
return googleModelsEndpoint(endpoint, config.googleNative === true);
|
|
28425
28531
|
}
|
|
28426
28532
|
function joinUrl(base, path) {
|
|
28427
28533
|
return new URL(path, base.endsWith("/") ? base : `${base}/`).toString();
|
|
@@ -28732,14 +28838,53 @@ function serveSettingsHealth(req, res, health) {
|
|
|
28732
28838
|
res.end(JSON.stringify(health));
|
|
28733
28839
|
}
|
|
28734
28840
|
//#endregion
|
|
28841
|
+
//#region lib/types/image-tool-context.js
|
|
28842
|
+
/** Live, credential-free instructions for the conversation model's image tools. */
|
|
28843
|
+
function imageToolContext(config) {
|
|
28844
|
+
const provider = config.provider ?? "google";
|
|
28845
|
+
let model = "not configured";
|
|
28846
|
+
let editModel = model;
|
|
28847
|
+
try {
|
|
28848
|
+
const active = resolveProvider(config);
|
|
28849
|
+
model = active.provider === "comfyui" ? active.workflow?.name ?? "workflow selected in settings" : active.model;
|
|
28850
|
+
const edit = resolveProvider(withProviderOverrides(config, void 0, void 0, "edit"));
|
|
28851
|
+
editModel = edit.provider === "comfyui" ? edit.workflow?.name ?? model : edit.model;
|
|
28852
|
+
} catch {}
|
|
28853
|
+
return [
|
|
28854
|
+
`Image tools current default provider: ${provider}; generation model: ${JSON.stringify(model)}; editing model: ${JSON.stringify(editModel)}.`,
|
|
28855
|
+
"Unless the user explicitly requests a different image provider or model, OMIT provider and model from generate_image, generate_images and edit_image. Do not guess a channel or switch channels after an error.",
|
|
28856
|
+
"Omit unused optional arguments entirely; never fill them with empty strings. size and aspect_ratio are alternatives: provide at most one.",
|
|
28857
|
+
"Subscription parameters: chatgpt-sub accepts size OR aspect_ratio, but NOT image_size. grok-sub image_size: 1K or 2K. google-sub image_size: 1K or 4K. Do not copy parameters from another provider.",
|
|
28858
|
+
"Minimal calls: generate_image({\"prompt\":\"a sailboat\"}); edit_image({\"prompt\":\"make the sail blue\"}). A valid ChatGPT subscription ratio example is {\"prompt\":\"a sailboat\",\"aspect_ratio\":\"1:1\"} with image_size and size omitted."
|
|
28859
|
+
].join("\n");
|
|
28860
|
+
}
|
|
28861
|
+
//#endregion
|
|
28735
28862
|
//#region lib/types/index.js
|
|
28736
28863
|
const name = "dsh-image-gen";
|
|
28737
28864
|
const inject = [
|
|
28738
28865
|
"tools",
|
|
28739
28866
|
"attachments",
|
|
28740
|
-
"credentials"
|
|
28741
|
-
"webServer"
|
|
28867
|
+
"credentials"
|
|
28742
28868
|
];
|
|
28869
|
+
/** Optional strings from tool callers may contain empty placeholders (#86). */
|
|
28870
|
+
function cleanImageToolArgs(args) {
|
|
28871
|
+
const clean = { ...args };
|
|
28872
|
+
const fields = clean;
|
|
28873
|
+
for (const key of [
|
|
28874
|
+
"model",
|
|
28875
|
+
"size",
|
|
28876
|
+
"aspect_ratio",
|
|
28877
|
+
"image_size",
|
|
28878
|
+
"quality",
|
|
28879
|
+
"workflow"
|
|
28880
|
+
]) {
|
|
28881
|
+
const value = fields[key];
|
|
28882
|
+
if (typeof value !== "string") continue;
|
|
28883
|
+
if (value.trim().length === 0) delete fields[key];
|
|
28884
|
+
else fields[key] = value.trim();
|
|
28885
|
+
}
|
|
28886
|
+
return clean;
|
|
28887
|
+
}
|
|
28743
28888
|
/** Validate the untrusted per-call provider override from tool arguments. */
|
|
28744
28889
|
function providerOverrideOf(value) {
|
|
28745
28890
|
if (value === void 0 || value === null || value === "") return void 0;
|
|
@@ -28756,142 +28901,152 @@ function apply(ctx, config = {}) {
|
|
|
28756
28901
|
const knownWorkspaceRoots = /* @__PURE__ */ new Set();
|
|
28757
28902
|
const canvasMirror = new CanvasMirror();
|
|
28758
28903
|
const subscriptionManager = new SubscriptionManager(ctx);
|
|
28759
|
-
registerSubscriptionRoutes(ctx, subscriptionManager);
|
|
28760
28904
|
const settingsHealth = installImageSettings(ctx, current(), {
|
|
28761
28905
|
setSource: (source) => {
|
|
28762
28906
|
current = () => migrateOpenAICompatConfig(plainConfig(source()));
|
|
28763
28907
|
},
|
|
28764
28908
|
onChange: () => {}
|
|
28765
28909
|
});
|
|
28766
|
-
ctx.
|
|
28767
|
-
|
|
28768
|
-
|
|
28769
|
-
|
|
28770
|
-
|
|
28771
|
-
|
|
28772
|
-
|
|
28773
|
-
|
|
28774
|
-
|
|
28775
|
-
|
|
28776
|
-
|
|
28777
|
-
|
|
28778
|
-
|
|
28779
|
-
|
|
28780
|
-
|
|
28781
|
-
|
|
28782
|
-
|
|
28783
|
-
|
|
28784
|
-
|
|
28785
|
-
|
|
28786
|
-
|
|
28787
|
-
|
|
28788
|
-
|
|
28789
|
-
|
|
28790
|
-
|
|
28791
|
-
...knownWorkspaceRoots,
|
|
28792
|
-
...discovered,
|
|
28793
|
-
process.cwd()
|
|
28794
|
-
]));
|
|
28795
|
-
} })
|
|
28796
|
-
}), "dsh-image-gen: delete route");
|
|
28797
|
-
ctx.effect(() => ctx.webServer.register({
|
|
28798
|
-
kind: "exact",
|
|
28799
|
-
path: SAVE_WORKSPACE_ROUTE,
|
|
28800
|
-
handler: (req, res) => serveSaveWorkspace(req, res, {
|
|
28801
|
-
readImage: (ref) => ctx.attachments.readImage(ref),
|
|
28802
|
-
saveToWorkspace: (options) => {
|
|
28803
|
-
if (options.workspaceRoot) knownWorkspaceRoots.add(options.workspaceRoot);
|
|
28804
|
-
return saveImageToWorkspace({
|
|
28805
|
-
workspaceRoot: options.workspaceRoot,
|
|
28806
|
-
folder: current().workspaceFolder,
|
|
28807
|
-
attachmentId: options.attachmentId,
|
|
28808
|
-
mediaType: options.mediaType,
|
|
28809
|
-
data: options.data
|
|
28810
|
-
});
|
|
28811
|
-
},
|
|
28812
|
-
getActiveWorkspaceRoot: () => Array.from(knownWorkspaceRoots)[0] || process.cwd(),
|
|
28813
|
-
getAllowedWorkspaceRoots: async () => {
|
|
28910
|
+
ctx.inject(["webServer"], (webCtx) => {
|
|
28911
|
+
registerSubscriptionRoutes(webCtx, subscriptionManager);
|
|
28912
|
+
webCtx.effect(() => webCtx.webServer.register({
|
|
28913
|
+
kind: "exact",
|
|
28914
|
+
path: SETTINGS_HEALTH_ROUTE,
|
|
28915
|
+
handler: (req, res) => serveSettingsHealth(req, res, settingsHealth())
|
|
28916
|
+
}), "dsh-image-gen: settings health");
|
|
28917
|
+
webCtx.effect(() => webCtx.webServer.register({
|
|
28918
|
+
kind: "exact",
|
|
28919
|
+
path: IMAGE_ROUTE,
|
|
28920
|
+
handler: (req, res) => serveImage(req, res, { readImage: (ref) => ctx.attachments.readImage(ref) })
|
|
28921
|
+
}), "dsh-image-gen: image route");
|
|
28922
|
+
webCtx.effect(() => webCtx.webServer.register({
|
|
28923
|
+
kind: "exact",
|
|
28924
|
+
path: IMPORT_ROUTE,
|
|
28925
|
+
handler: (req, res) => serveImport(req, res, {
|
|
28926
|
+
saveImage: (image) => ctx.attachments.saveImage(image),
|
|
28927
|
+
maxImageBytes: ctx.attachments.imageLimits.maxImageBytes,
|
|
28928
|
+
mediaTypes: ctx.attachments.imageLimits.mediaTypes
|
|
28929
|
+
})
|
|
28930
|
+
}), "dsh-image-gen: import route");
|
|
28931
|
+
webCtx.effect(() => webCtx.webServer.register({
|
|
28932
|
+
kind: "exact",
|
|
28933
|
+
path: DELETE_ROUTE,
|
|
28934
|
+
handler: (req, res) => serveDelete(req, res, { deleteWorkspaceImage: async (filePath) => {
|
|
28814
28935
|
const discovered = await getDshWorkspaceRoots().catch(() => []);
|
|
28815
|
-
return /* @__PURE__ */ new Set([
|
|
28936
|
+
return deleteImageFromWorkspace(filePath, /* @__PURE__ */ new Set([
|
|
28816
28937
|
...knownWorkspaceRoots,
|
|
28817
28938
|
...discovered,
|
|
28818
28939
|
process.cwd()
|
|
28819
|
-
]);
|
|
28820
|
-
}
|
|
28821
|
-
|
|
28822
|
-
|
|
28823
|
-
|
|
28824
|
-
|
|
28825
|
-
|
|
28826
|
-
|
|
28827
|
-
|
|
28828
|
-
|
|
28829
|
-
|
|
28830
|
-
|
|
28831
|
-
|
|
28832
|
-
|
|
28833
|
-
|
|
28834
|
-
|
|
28835
|
-
|
|
28836
|
-
|
|
28837
|
-
|
|
28838
|
-
|
|
28839
|
-
|
|
28840
|
-
|
|
28841
|
-
|
|
28842
|
-
|
|
28843
|
-
|
|
28844
|
-
|
|
28845
|
-
|
|
28846
|
-
|
|
28847
|
-
|
|
28848
|
-
})
|
|
28849
|
-
|
|
28940
|
+
]));
|
|
28941
|
+
} })
|
|
28942
|
+
}), "dsh-image-gen: delete route");
|
|
28943
|
+
webCtx.effect(() => webCtx.webServer.register({
|
|
28944
|
+
kind: "exact",
|
|
28945
|
+
path: SAVE_WORKSPACE_ROUTE,
|
|
28946
|
+
handler: (req, res) => serveSaveWorkspace(req, res, {
|
|
28947
|
+
readImage: (ref) => ctx.attachments.readImage(ref),
|
|
28948
|
+
saveToWorkspace: (options) => {
|
|
28949
|
+
if (options.workspaceRoot) knownWorkspaceRoots.add(options.workspaceRoot);
|
|
28950
|
+
return saveImageToWorkspace({
|
|
28951
|
+
workspaceRoot: options.workspaceRoot,
|
|
28952
|
+
folder: current().workspaceFolder,
|
|
28953
|
+
attachmentId: options.attachmentId,
|
|
28954
|
+
mediaType: options.mediaType,
|
|
28955
|
+
data: options.data
|
|
28956
|
+
});
|
|
28957
|
+
},
|
|
28958
|
+
getActiveWorkspaceRoot: () => Array.from(knownWorkspaceRoots)[0] || process.cwd(),
|
|
28959
|
+
getAllowedWorkspaceRoots: async () => {
|
|
28960
|
+
const discovered = await getDshWorkspaceRoots().catch(() => []);
|
|
28961
|
+
return /* @__PURE__ */ new Set([
|
|
28962
|
+
...knownWorkspaceRoots,
|
|
28963
|
+
...discovered,
|
|
28964
|
+
process.cwd()
|
|
28965
|
+
]);
|
|
28966
|
+
},
|
|
28967
|
+
isSaveEnabled: () => current().saveToWorkspace !== false
|
|
28968
|
+
})
|
|
28969
|
+
}), "dsh-image-gen: save workspace route");
|
|
28970
|
+
webCtx.effect(() => webCtx.webServer.register({
|
|
28971
|
+
kind: "exact",
|
|
28972
|
+
path: TEST_CONNECTION_ROUTE,
|
|
28973
|
+
handler: (req, res) => serveTestConnection(req, res, {
|
|
28974
|
+
resolveKey: (provider) => resolveApiKey(ctx, provider),
|
|
28975
|
+
config: () => current(),
|
|
28976
|
+
subscriptionManager
|
|
28977
|
+
})
|
|
28978
|
+
}), "dsh-image-gen: test connection route");
|
|
28979
|
+
webCtx.effect(() => webCtx.webServer.register({
|
|
28980
|
+
kind: "exact",
|
|
28981
|
+
path: CANVAS_STATE_ROUTE,
|
|
28982
|
+
handler: (req, res) => serveCanvasState(req, res, {
|
|
28983
|
+
mirror: canvasMirror,
|
|
28984
|
+
maxBodyBytes: Math.ceil(ctx.attachments.imageLimits.maxImageBytes * 1.4) + 262144,
|
|
28985
|
+
maxImageBytes: ctx.attachments.imageLimits.maxImageBytes
|
|
28986
|
+
})
|
|
28987
|
+
}), "dsh-image-gen: canvas state route");
|
|
28988
|
+
webCtx.effect(() => webCtx.webServer.register({
|
|
28989
|
+
kind: "exact",
|
|
28990
|
+
path: CANVAS_ASSET_ROUTE,
|
|
28991
|
+
handler: (req, res) => serveCanvasAsset(req, res, {
|
|
28992
|
+
maxImageBytes: ctx.attachments.imageLimits.maxImageBytes,
|
|
28993
|
+
saveImage: (image) => ctx.attachments.saveImage(image)
|
|
28994
|
+
})
|
|
28995
|
+
}), "dsh-image-gen: canvas asset route");
|
|
28996
|
+
});
|
|
28850
28997
|
ctx.inject(["systemPrompt"], (promptCtx) => {
|
|
28851
|
-
promptCtx.systemPrompt.context({
|
|
28998
|
+
promptCtx.effect(() => promptCtx.systemPrompt.context({
|
|
28999
|
+
name: "dsh-image-gen:image-defaults",
|
|
29000
|
+
order: 59,
|
|
29001
|
+
text: () => imageToolContext(current())
|
|
29002
|
+
}), "dsh-image-gen: image tool context");
|
|
29003
|
+
promptCtx.effect(() => promptCtx.systemPrompt.context({
|
|
28852
29004
|
name: "dsh-image-gen:canvas",
|
|
28853
29005
|
order: 60,
|
|
28854
29006
|
text: () => canvasMirror.digest()
|
|
28855
|
-
});
|
|
29007
|
+
}), "dsh-image-gen: canvas context");
|
|
28856
29008
|
});
|
|
28857
29009
|
registerCanvasTools(ctx, canvasMirror, { persistSelectionImage: (image) => ctx.attachments.saveImage({
|
|
28858
29010
|
data: image.data,
|
|
28859
29011
|
mediaType: image.mediaType,
|
|
28860
29012
|
name: "canvas-selection"
|
|
28861
29013
|
}) });
|
|
28862
|
-
ctx.
|
|
28863
|
-
|
|
28864
|
-
|
|
28865
|
-
|
|
28866
|
-
|
|
28867
|
-
|
|
28868
|
-
|
|
28869
|
-
|
|
28870
|
-
|
|
28871
|
-
|
|
28872
|
-
|
|
28873
|
-
|
|
28874
|
-
|
|
28875
|
-
|
|
28876
|
-
|
|
28877
|
-
|
|
28878
|
-
|
|
28879
|
-
|
|
28880
|
-
|
|
28881
|
-
|
|
28882
|
-
|
|
28883
|
-
|
|
28884
|
-
|
|
28885
|
-
|
|
28886
|
-
|
|
28887
|
-
|
|
28888
|
-
|
|
28889
|
-
|
|
28890
|
-
|
|
28891
|
-
|
|
28892
|
-
|
|
29014
|
+
ctx.inject(["webServer"], (webCtx) => {
|
|
29015
|
+
webCtx.effect(() => webCtx.webServer.register({
|
|
29016
|
+
kind: "exact",
|
|
29017
|
+
path: STUDIO_ROUTE,
|
|
29018
|
+
handler: (req, res) => serveStudio(req, res, {
|
|
29019
|
+
describe: async () => {
|
|
29020
|
+
const base = await describeStudio(ctx, current(), subscriptionManager);
|
|
29021
|
+
const workspaces = await getDshWorkspacesFull().catch(() => []);
|
|
29022
|
+
const activeRoot = Array.from(knownWorkspaceRoots)[0] || workspaces[0]?.path || process.cwd();
|
|
29023
|
+
return {
|
|
29024
|
+
...base,
|
|
29025
|
+
workspaceRoot: activeRoot,
|
|
29026
|
+
workspaces
|
|
29027
|
+
};
|
|
29028
|
+
},
|
|
29029
|
+
generate: (input, signal) => {
|
|
29030
|
+
const fallbackRoot = Array.from(knownWorkspaceRoots)[0] || process.cwd();
|
|
29031
|
+
return generateFromStudio(ctx, current(), input, signal, fallbackRoot, subscriptionManager);
|
|
29032
|
+
},
|
|
29033
|
+
maxBodyBytes: Math.ceil(ctx.attachments.imageLimits.maxImageBytes * 1.4 * 5) + 262144
|
|
29034
|
+
})
|
|
29035
|
+
}), "dsh-image-gen: studio route");
|
|
29036
|
+
const serveInspiration = createInspirationRoute();
|
|
29037
|
+
webCtx.effect(() => webCtx.webServer.register({
|
|
29038
|
+
kind: "prefix",
|
|
29039
|
+
path: INSPIRATION_ROUTE,
|
|
29040
|
+
handler: (req, res) => {
|
|
29041
|
+
const originalUrl = req.url ?? "/";
|
|
29042
|
+
req.url = originalUrl.startsWith("/plugins/dsh-image-gen/inspiration") ? originalUrl.slice(34) || "/" : originalUrl;
|
|
29043
|
+
return serveInspiration(req, res);
|
|
29044
|
+
}
|
|
29045
|
+
}), "dsh-image-gen: inspiration route");
|
|
29046
|
+
});
|
|
28893
29047
|
/** Per-item generation request shared by generate_image and generate_images. */
|
|
28894
|
-
const generateSingle = async (
|
|
29048
|
+
const generateSingle = async (rawArgs, exec) => {
|
|
29049
|
+
const args = cleanImageToolArgs(rawArgs);
|
|
28895
29050
|
const active = resolveProvider(withProviderOverrides(current(), providerOverrideOf(args.provider), args.model));
|
|
28896
29051
|
if (active.provider === "comfyui") {
|
|
28897
29052
|
const workflow = selectComfyUIWorkflow(active, args.workflow);
|
|
@@ -28926,6 +29081,7 @@ function apply(ctx, config = {}) {
|
|
|
28926
29081
|
return saveGenerated(ctx, await generateGoogleImage({
|
|
28927
29082
|
apiKey: credential,
|
|
28928
29083
|
endpoint: active.endpoint,
|
|
29084
|
+
native: active.native,
|
|
28929
29085
|
model: active.model,
|
|
28930
29086
|
prompt: args.prompt,
|
|
28931
29087
|
aspectRatio,
|
|
@@ -29006,7 +29162,7 @@ function apply(ctx, config = {}) {
|
|
|
29006
29162
|
"grok-sub",
|
|
29007
29163
|
"google-sub"
|
|
29008
29164
|
],
|
|
29009
|
-
description: "
|
|
29165
|
+
description: "OMIT unless the user explicitly requests a specific image provider. Omission uses the saved default; never guess a channel or fill this with an empty string. chatgpt-sub, grok-sub, and google-sub use logged-in subscription accounts instead of API keys."
|
|
29010
29166
|
},
|
|
29011
29167
|
model: {
|
|
29012
29168
|
type: "string",
|
|
@@ -29035,11 +29191,11 @@ function apply(ctx, config = {}) {
|
|
|
29035
29191
|
"2K",
|
|
29036
29192
|
"4K"
|
|
29037
29193
|
],
|
|
29038
|
-
description: "Optional output resolution for Google Gemini, xAI Grok, Grok subscription (1K/2K), or Google subscription (1K/4K)."
|
|
29194
|
+
description: "Optional output resolution for Google Gemini, xAI Grok, Grok subscription (1K/2K), or Google subscription (1K/4K). NEVER send this for chatgpt-sub; omit unused fields entirely."
|
|
29039
29195
|
},
|
|
29040
29196
|
size: {
|
|
29041
29197
|
type: "string",
|
|
29042
|
-
description: "Optional dimensions or size tier for OpenAI, Seedream, or DashScope; WIDTHxHEIGHT maps to aspect_ratio for xAI Grok."
|
|
29198
|
+
description: "Optional dimensions or size tier for OpenAI, Seedream, or DashScope; WIDTHxHEIGHT maps to aspect_ratio for xAI Grok. Do not combine with aspect_ratio; omit instead of using an empty string."
|
|
29043
29199
|
},
|
|
29044
29200
|
quality: {
|
|
29045
29201
|
type: "string",
|
|
@@ -29081,7 +29237,7 @@ function apply(ctx, config = {}) {
|
|
|
29081
29237
|
"grok-sub",
|
|
29082
29238
|
"google-sub"
|
|
29083
29239
|
],
|
|
29084
|
-
description: "
|
|
29240
|
+
description: "OMIT unless the user explicitly requests a specific image provider. Uses the saved default for all items; never guess a channel or fill unused fields with empty strings."
|
|
29085
29241
|
},
|
|
29086
29242
|
model: {
|
|
29087
29243
|
type: "string",
|
|
@@ -29110,7 +29266,7 @@ function apply(ctx, config = {}) {
|
|
|
29110
29266
|
"2K",
|
|
29111
29267
|
"4K"
|
|
29112
29268
|
],
|
|
29113
|
-
description: "Optional output resolution for Google Gemini, xAI Grok, Grok subscription (1K/2K), or Google subscription (1K/4K)."
|
|
29269
|
+
description: "Optional output resolution for Google Gemini, xAI Grok, Grok subscription (1K/2K), or Google subscription (1K/4K). NEVER send this for chatgpt-sub; omit unused fields entirely."
|
|
29114
29270
|
},
|
|
29115
29271
|
size: {
|
|
29116
29272
|
type: "string",
|
|
@@ -29303,7 +29459,7 @@ function apply(ctx, config = {}) {
|
|
|
29303
29459
|
"grok-sub",
|
|
29304
29460
|
"google-sub"
|
|
29305
29461
|
],
|
|
29306
|
-
description: "
|
|
29462
|
+
description: "OMIT unless the user explicitly requests a specific image provider. Omission uses the saved default; never guess a channel or fill this with an empty string. Subscription providers edit through their logged-in accounts."
|
|
29307
29463
|
},
|
|
29308
29464
|
model: {
|
|
29309
29465
|
type: "string",
|
|
@@ -29355,7 +29511,7 @@ function apply(ctx, config = {}) {
|
|
|
29355
29511
|
"2K",
|
|
29356
29512
|
"4K"
|
|
29357
29513
|
],
|
|
29358
|
-
description: "Optional output resolution for Google Gemini, xAI Grok, Grok subscription (1K/2K), or Google subscription (1K/4K)."
|
|
29514
|
+
description: "Optional output resolution for Google Gemini, xAI Grok, Grok subscription (1K/2K), or Google subscription (1K/4K). NEVER send this for chatgpt-sub; omit unused fields entirely."
|
|
29359
29515
|
},
|
|
29360
29516
|
size: {
|
|
29361
29517
|
type: "string",
|
|
@@ -29371,8 +29527,9 @@ function apply(ctx, config = {}) {
|
|
|
29371
29527
|
}
|
|
29372
29528
|
},
|
|
29373
29529
|
output: imageOutput("Edited"),
|
|
29374
|
-
async execute(
|
|
29375
|
-
const
|
|
29530
|
+
async execute(rawArgs, exec) {
|
|
29531
|
+
const args = cleanImageToolArgs(rawArgs);
|
|
29532
|
+
const active = resolveProvider(withProviderOverrides(current(), providerOverrideOf(args.provider), args.model, "edit"));
|
|
29376
29533
|
const canvasSelection = args.source === "canvas_selection";
|
|
29377
29534
|
if (canvasSelection && (args.source_attachment_id !== void 0 || Array.isArray(args.source_attachment_ids) || args.source_path !== void 0 || Array.isArray(args.source_paths))) throw new Error("edit_image source=canvas_selection cannot be combined with source_attachment_id, source_attachment_ids, source_path, or source_paths; provide exactly one selector");
|
|
29378
29535
|
const sourceImages = canvasSelection ? await resolveCanvasSelectionReferences({
|
|
@@ -29433,6 +29590,7 @@ function apply(ctx, config = {}) {
|
|
|
29433
29590
|
return saveGenerated(ctx, await editGoogleImage({
|
|
29434
29591
|
apiKey: credential,
|
|
29435
29592
|
endpoint: active.endpoint,
|
|
29593
|
+
native: active.native,
|
|
29436
29594
|
model: active.model,
|
|
29437
29595
|
prompt: args.prompt,
|
|
29438
29596
|
sourceImages,
|