dsh-image-gen 0.9.2 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +454 -452
- package/README.md +454 -452
- package/lib/client.js +984 -366
- package/lib/index.js +506 -194
- package/lib/types/client/conversation-image-revisions.d.ts +1 -0
- package/lib/types/client/conversation-regenerate.d.ts +1 -0
- package/lib/types/client/gallery-store.d.ts +1 -0
- package/lib/types/client/image-result-node.d.ts +1 -0
- package/lib/types/client/index.d.ts +8 -2
- package/lib/types/client/multi-model-compare.d.ts +3 -3
- package/lib/types/client/provider-pill.d.ts +9 -3
- package/lib/types/config.d.ts +15 -4
- package/lib/types/credentials.d.ts +2 -2
- package/lib/types/generation-timeout.d.ts +5 -0
- 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 +57 -0
- package/lib/types/studio-route.d.ts +1 -0
- package/lib/types/studio.d.ts +1 -1
- package/lib/types/test-route.d.ts +3 -3
- 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";
|
|
@@ -5760,12 +5764,80 @@ const CLOUD_IMAGE_PROVIDERS = [
|
|
|
5760
5764
|
"xai",
|
|
5761
5765
|
"zhipu"
|
|
5762
5766
|
];
|
|
5767
|
+
/** Normalize the relay model list while keeping the legacy default model selectable. */
|
|
5768
|
+
function resolveOpenAICompatModels(config) {
|
|
5769
|
+
const defaultModel = config.openaiCompatModel?.trim() ?? "";
|
|
5770
|
+
const models = [...new Set((config.openaiCompatModels ?? []).map((model) => model.trim()).filter(Boolean))];
|
|
5771
|
+
if (defaultModel.length > 0 && !models.includes(defaultModel)) models.unshift(defaultModel);
|
|
5772
|
+
return models;
|
|
5773
|
+
}
|
|
5774
|
+
const DEFAULT_OPENAI_COMPAT_CHANNEL_ID = "default";
|
|
5775
|
+
function openAICompatKeyRef(channelId) {
|
|
5776
|
+
return channelId === "default" ? OPENAI_COMPAT_API_KEY_ENV : `${OPENAI_COMPAT_API_KEY_ENV}_${channelId.replace(/[^A-Za-z0-9]/g, "_").toUpperCase()}`;
|
|
5777
|
+
}
|
|
5778
|
+
/** The legacy flat fields remain the first channel; additional entries are fully independent. */
|
|
5779
|
+
function resolveOpenAICompatChannels(source) {
|
|
5780
|
+
const legacy = {
|
|
5781
|
+
id: DEFAULT_OPENAI_COMPAT_CHANNEL_ID,
|
|
5782
|
+
label: source.openaiCompatLabel?.trim() || "OpenAI 兼容",
|
|
5783
|
+
keyRef: OPENAI_COMPAT_API_KEY_ENV,
|
|
5784
|
+
baseURL: source.openaiCompatBaseURL ?? "",
|
|
5785
|
+
model: source.openaiCompatModel ?? "",
|
|
5786
|
+
models: [...source.openaiCompatModels ?? []],
|
|
5787
|
+
editModel: source.openaiCompatEditModel ?? "",
|
|
5788
|
+
quality: source.openaiCompatQuality ?? "",
|
|
5789
|
+
qualityOptions: [...source.openaiCompatQualityOptions ?? []],
|
|
5790
|
+
extraBody: source.openaiCompatExtraBody ?? {},
|
|
5791
|
+
editSizeMode: source.openaiCompatEditSizeMode ?? "auto",
|
|
5792
|
+
editFormat: source.openaiCompatEditFormat ?? "multipart",
|
|
5793
|
+
editExtra: source.openaiCompatEditExtra ?? {},
|
|
5794
|
+
sizes: source.openaiCompatSizes ?? {}
|
|
5795
|
+
};
|
|
5796
|
+
const channels = [legacy];
|
|
5797
|
+
const seen = /* @__PURE__ */ new Set([legacy.id]);
|
|
5798
|
+
for (const [index, entry] of (source.openaiCompatChannels ?? []).entries()) {
|
|
5799
|
+
const id = typeof entry?.id === "string" ? entry.id.trim() : "";
|
|
5800
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/.test(id) || seen.has(id)) continue;
|
|
5801
|
+
seen.add(id);
|
|
5802
|
+
const storedKeyRef = typeof entry.keyRef === "string" ? entry.keyRef.trim() : "";
|
|
5803
|
+
channels.push({
|
|
5804
|
+
id,
|
|
5805
|
+
label: typeof entry.label === "string" && entry.label.trim().length > 0 ? entry.label.trim() : `OpenAI 兼容 ${index + 2}`,
|
|
5806
|
+
keyRef: /^DSH_IMAGE_GEN_OPENAI_COMPAT_KEY_[A-Z0-9_]+$/.test(storedKeyRef) ? storedKeyRef : openAICompatKeyRef(id),
|
|
5807
|
+
baseURL: typeof entry.baseURL === "string" ? entry.baseURL : "",
|
|
5808
|
+
model: typeof entry.model === "string" ? entry.model : "",
|
|
5809
|
+
models: Array.isArray(entry.models) ? entry.models.filter((model) => typeof model === "string") : [],
|
|
5810
|
+
editModel: typeof entry.editModel === "string" ? entry.editModel : "",
|
|
5811
|
+
quality: typeof entry.quality === "string" ? entry.quality : "",
|
|
5812
|
+
qualityOptions: Array.isArray(entry.qualityOptions) ? entry.qualityOptions.filter((quality) => typeof quality === "string") : [],
|
|
5813
|
+
extraBody: entry.extraBody ?? {},
|
|
5814
|
+
editSizeMode: entry.editSizeMode === "selected" ? "selected" : "auto",
|
|
5815
|
+
editFormat: entry.editFormat ?? "multipart",
|
|
5816
|
+
editExtra: entry.editExtra ?? {},
|
|
5817
|
+
sizes: entry.sizes ?? {}
|
|
5818
|
+
});
|
|
5819
|
+
}
|
|
5820
|
+
return channels;
|
|
5821
|
+
}
|
|
5822
|
+
function resolveOpenAICompatChannel(source, channelId) {
|
|
5823
|
+
const channels = resolveOpenAICompatChannels(source);
|
|
5824
|
+
const explicit = channelId?.trim() ?? "";
|
|
5825
|
+
if (explicit.length > 0) {
|
|
5826
|
+
const selected = channels.find((channel) => channel.id === explicit);
|
|
5827
|
+
if (selected === void 0) throw new Error(`Unknown OpenAI-compatible channel "${explicit}". Available channels: ${channels.map((channel) => channel.id).join(", ")}.`);
|
|
5828
|
+
return selected;
|
|
5829
|
+
}
|
|
5830
|
+
const active = source.openaiCompatActiveChannel?.trim() ?? "";
|
|
5831
|
+
return channels.find((channel) => channel.id === active) ?? channels[0];
|
|
5832
|
+
}
|
|
5763
5833
|
/**
|
|
5764
5834
|
* Providers the browser workbench can drive: the BYOK cloud set plus the
|
|
5765
5835
|
* logged-in subscription channels. ComfyUI stays out; it has its own workflow
|
|
5766
5836
|
* pipeline and no shared request shape.
|
|
5767
5837
|
*/
|
|
5768
5838
|
const STUDIO_PROVIDERS = [...CLOUD_IMAGE_PROVIDERS, ...SUBSCRIPTION_PROVIDERS];
|
|
5839
|
+
/** Default timeout for API-key cloud generation in the browser workbench. */
|
|
5840
|
+
const DEFAULT_CLOUD_TIMEOUT_MS = 15e4;
|
|
5769
5841
|
/** Timeout for one subscription image call, shared by the tool path and the
|
|
5770
5842
|
* studio route so neither drifts from the other. */
|
|
5771
5843
|
const SUBSCRIPTION_TIMEOUT_MS = 3e5;
|
|
@@ -5827,6 +5899,16 @@ const STUDIO_WATERMARK_VALUES = [
|
|
|
5827
5899
|
"off",
|
|
5828
5900
|
"on"
|
|
5829
5901
|
];
|
|
5902
|
+
function studioModelOptionsForMode(profile, mode) {
|
|
5903
|
+
if (mode === "edit" && profile.editModel !== void 0) return [{
|
|
5904
|
+
value: profile.editModel,
|
|
5905
|
+
label: profile.editModel
|
|
5906
|
+
}];
|
|
5907
|
+
return profile.modelOptions ?? [{
|
|
5908
|
+
value: profile.model,
|
|
5909
|
+
label: profile.model
|
|
5910
|
+
}];
|
|
5911
|
+
}
|
|
5830
5912
|
function studioWatermarkCapability(profile, mode) {
|
|
5831
5913
|
return profile?.watermarkCapabilities?.[mode] ?? (mode === "generate" && profile?.watermarkOptions?.length ? { status: "supported" } : {
|
|
5832
5914
|
status: "unknown",
|
|
@@ -5953,14 +6035,40 @@ function volatile(schema) {
|
|
|
5953
6035
|
*/
|
|
5954
6036
|
const Config = z.object({
|
|
5955
6037
|
provider: volatile(z.union(IMAGE_PROVIDERS).default("google")),
|
|
6038
|
+
cloudTimeoutMs: volatile(z.number().min(5e3).max(36e5).default(DEFAULT_CLOUD_TIMEOUT_MS)),
|
|
5956
6039
|
googleModel: volatile(z.string().default(DEFAULT_GOOGLE_MODEL)),
|
|
5957
6040
|
googleEndpoint: volatile(z.string().default(DEFAULT_GOOGLE_ENDPOINT)),
|
|
5958
6041
|
googleNative: volatile(z.boolean().default(false)),
|
|
5959
6042
|
openaiBaseURL: volatile(z.string().default(DEFAULT_OPENAI_BASE_URL)),
|
|
5960
6043
|
openaiModel: volatile(z.string().default(DEFAULT_OPENAI_MODEL)),
|
|
5961
6044
|
openaiSizes: volatile(z.dict(z.dict(z.string())).default({})),
|
|
6045
|
+
openaiCompatLabel: volatile(z.string().default("")),
|
|
5962
6046
|
openaiCompatBaseURL: volatile(z.string().default("")),
|
|
5963
6047
|
openaiCompatModel: volatile(z.string().default("")),
|
|
6048
|
+
openaiCompatModels: volatile(z.array(z.string()).default([])),
|
|
6049
|
+
openaiCompatChannels: volatile(z.array(z.object({
|
|
6050
|
+
id: z.string(),
|
|
6051
|
+
label: z.string().default(""),
|
|
6052
|
+
keyRef: z.string(),
|
|
6053
|
+
baseURL: z.string().default(""),
|
|
6054
|
+
model: z.string().default(""),
|
|
6055
|
+
models: z.array(z.string()).default([]),
|
|
6056
|
+
editModel: z.string().default(""),
|
|
6057
|
+
quality: z.string().default(""),
|
|
6058
|
+
qualityOptions: z.array(z.string()).default([]),
|
|
6059
|
+
extraBody: z.dict(z.any()).default({}),
|
|
6060
|
+
editSizeMode: z.union(["auto", "selected"]).default("auto"),
|
|
6061
|
+
editFormat: z.union([
|
|
6062
|
+
z.const("multipart"),
|
|
6063
|
+
z.const("jsonImageUrlArray"),
|
|
6064
|
+
z.const("formReferenceImages"),
|
|
6065
|
+
z.const("siliconflowJson")
|
|
6066
|
+
]).default("multipart"),
|
|
6067
|
+
editExtra: z.dict(z.any()).default({}),
|
|
6068
|
+
sizes: z.dict(z.dict(z.string())).default({})
|
|
6069
|
+
})).default([])),
|
|
6070
|
+
openaiCompatActiveChannel: volatile(z.string().default("")),
|
|
6071
|
+
openaiCompatEditModel: volatile(z.string().default("")),
|
|
5964
6072
|
openaiCompatQuality: volatile(z.string().default("")),
|
|
5965
6073
|
openaiCompatQualityOptions: volatile(z.array(z.string()).default([
|
|
5966
6074
|
"auto",
|
|
@@ -5973,7 +6081,8 @@ const Config = z.object({
|
|
|
5973
6081
|
openaiCompatEditFormat: volatile(z.union([
|
|
5974
6082
|
z.const("multipart"),
|
|
5975
6083
|
z.const("jsonImageUrlArray"),
|
|
5976
|
-
z.const("formReferenceImages")
|
|
6084
|
+
z.const("formReferenceImages"),
|
|
6085
|
+
z.const("siliconflowJson")
|
|
5977
6086
|
]).default("multipart")),
|
|
5978
6087
|
openaiCompatEditExtra: volatile(z.dict(z.any()).default({})),
|
|
5979
6088
|
openaiCompatSizes: volatile(z.dict(z.dict(z.string())).default({})),
|
|
@@ -6097,17 +6206,40 @@ function resolveProvider(config) {
|
|
|
6097
6206
|
};
|
|
6098
6207
|
}
|
|
6099
6208
|
}
|
|
6209
|
+
/** Project one named relay channel onto the legacy flat fields used by all provider adapters. */
|
|
6210
|
+
function withOpenAICompatChannel(config, channelId) {
|
|
6211
|
+
const channel = resolveOpenAICompatChannel(config, channelId);
|
|
6212
|
+
if (channel.id === "default") return {
|
|
6213
|
+
...config,
|
|
6214
|
+
openaiCompatActiveChannel: "default"
|
|
6215
|
+
};
|
|
6216
|
+
return {
|
|
6217
|
+
...config,
|
|
6218
|
+
openaiCompatLabel: channel.label,
|
|
6219
|
+
openaiCompatBaseURL: channel.baseURL,
|
|
6220
|
+
openaiCompatModel: channel.model,
|
|
6221
|
+
openaiCompatModels: channel.models ?? [],
|
|
6222
|
+
openaiCompatEditModel: channel.editModel ?? "",
|
|
6223
|
+
openaiCompatQuality: channel.quality ?? "",
|
|
6224
|
+
openaiCompatQualityOptions: channel.qualityOptions ?? [],
|
|
6225
|
+
openaiCompatExtraBody: channel.extraBody ?? {},
|
|
6226
|
+
openaiCompatEditSizeMode: channel.editSizeMode ?? "auto",
|
|
6227
|
+
openaiCompatEditFormat: channel.editFormat ?? "multipart",
|
|
6228
|
+
openaiCompatEditExtra: channel.editExtra ?? {},
|
|
6229
|
+
openaiCompatSizes: channel.sizes ?? {},
|
|
6230
|
+
openaiCompatActiveChannel: channel.id
|
|
6231
|
+
};
|
|
6232
|
+
}
|
|
6100
6233
|
/**
|
|
6101
6234
|
* Apply a per-call provider and/or model override on top of the saved config.
|
|
6102
6235
|
* `model` is ignored for ComfyUI, whose per-call equivalent is `workflow`.
|
|
6103
6236
|
*/
|
|
6104
|
-
function withProviderOverrides(config, provider, model) {
|
|
6237
|
+
function withProviderOverrides(config, provider, model, mode = "generate") {
|
|
6105
6238
|
const base = provider === void 0 ? { ...config } : {
|
|
6106
6239
|
...config,
|
|
6107
6240
|
provider
|
|
6108
6241
|
};
|
|
6109
|
-
|
|
6110
|
-
const trimmed = model.trim();
|
|
6242
|
+
const trimmed = model?.trim() || (mode === "edit" && base.provider === "openai-compat" ? base.openaiCompatEditModel?.trim() : "") || "";
|
|
6111
6243
|
if (trimmed.length === 0) return base;
|
|
6112
6244
|
switch (base.provider ?? "google") {
|
|
6113
6245
|
case "google": return {
|
|
@@ -6120,7 +6252,8 @@ function withProviderOverrides(config, provider, model) {
|
|
|
6120
6252
|
};
|
|
6121
6253
|
case "openai-compat": return {
|
|
6122
6254
|
...base,
|
|
6123
|
-
openaiCompatModel: trimmed
|
|
6255
|
+
openaiCompatModel: trimmed,
|
|
6256
|
+
...mode === "edit" ? { openaiCompatEditModel: trimmed } : {}
|
|
6124
6257
|
};
|
|
6125
6258
|
case "seedream": return {
|
|
6126
6259
|
...base,
|
|
@@ -6160,6 +6293,7 @@ function migrateOpenAICompatConfig(config) {
|
|
|
6160
6293
|
...config,
|
|
6161
6294
|
openaiCompatBaseURL: legacyBase,
|
|
6162
6295
|
...compatModel.length > 0 ? {} : { openaiCompatModel: config.openaiModel },
|
|
6296
|
+
...(config.openaiCompatModels?.length ?? 0) > 0 ? {} : { openaiCompatModels: [compatModel || config.openaiModel?.trim() || ""].filter(Boolean) },
|
|
6163
6297
|
openaiBaseURL: DEFAULT_OPENAI_BASE_URL,
|
|
6164
6298
|
...config.provider === "openai" ? { provider: "openai-compat" } : {}
|
|
6165
6299
|
};
|
|
@@ -6182,6 +6316,31 @@ function selectComfyUIWorkflow(active, requested) {
|
|
|
6182
6316
|
return workflow;
|
|
6183
6317
|
}
|
|
6184
6318
|
//#endregion
|
|
6319
|
+
//#region lib/types/generation-timeout.js
|
|
6320
|
+
const UPSTREAM_TIMEOUT_HINT = "上游可能已扣费或仍在生成,请到服务商后台确认";
|
|
6321
|
+
var GenerationTimeoutError = class extends Error {
|
|
6322
|
+
constructor(timeoutMs) {
|
|
6323
|
+
super(`生图超时(${String(timeoutMs / 1e3)} 秒未返回),请检查网络或代理后重试;${UPSTREAM_TIMEOUT_HINT}`);
|
|
6324
|
+
this.name = "GenerationTimeoutError";
|
|
6325
|
+
}
|
|
6326
|
+
};
|
|
6327
|
+
async function withGenerationTimeout(timeoutMs, signal, operation) {
|
|
6328
|
+
const watchdog = new AbortController();
|
|
6329
|
+
const combinedSignal = AbortSignal.any([signal, watchdog.signal]);
|
|
6330
|
+
const timeout = setTimeout(() => {
|
|
6331
|
+
watchdog.abort();
|
|
6332
|
+
}, timeoutMs);
|
|
6333
|
+
timeout.unref?.();
|
|
6334
|
+
try {
|
|
6335
|
+
return await operation(combinedSignal);
|
|
6336
|
+
} catch (error) {
|
|
6337
|
+
if (watchdog.signal.aborted && !signal.aborted) throw new GenerationTimeoutError(timeoutMs);
|
|
6338
|
+
throw error;
|
|
6339
|
+
} finally {
|
|
6340
|
+
clearTimeout(timeout);
|
|
6341
|
+
}
|
|
6342
|
+
}
|
|
6343
|
+
//#endregion
|
|
6185
6344
|
//#region node_modules/.pnpm/@deepseek-ai+dsh-credential_98fa173416f048996704a605a96a2b0e/node_modules/@deepseek-ai/dsh-credentials/lib/index.js
|
|
6186
6345
|
/**
|
|
6187
6346
|
* Service Definition for the credential-reference capability seam (`ctx.credentials`). Settings and composition files carry
|
|
@@ -6222,8 +6381,8 @@ function isCredentialRefName(value) {
|
|
|
6222
6381
|
* never count as configured: the host treats an empty stored value as absent,
|
|
6223
6382
|
* and a whitespace-only key is never sent to a provider.
|
|
6224
6383
|
*/
|
|
6225
|
-
async function resolveApiKey(ctx, provider) {
|
|
6226
|
-
const value = (await ctx.credentials.resolve(credentialRef(CLOUD_CREDENTIAL_REFS[provider])))?.value.trim() ?? "";
|
|
6384
|
+
async function resolveApiKey(ctx, provider, keyRef) {
|
|
6385
|
+
const value = (await ctx.credentials.resolve(credentialRef(keyRef ?? CLOUD_CREDENTIAL_REFS[provider])))?.value.trim() ?? "";
|
|
6227
6386
|
return value.length > 0 ? value : void 0;
|
|
6228
6387
|
}
|
|
6229
6388
|
/**
|
|
@@ -6232,8 +6391,8 @@ async function resolveApiKey(ctx, provider) {
|
|
|
6232
6391
|
* wording; without it the message is the browser-facing Chinese one used by
|
|
6233
6392
|
* the Studio route.
|
|
6234
6393
|
*/
|
|
6235
|
-
async function requireApiKey(ctx, provider, tool) {
|
|
6236
|
-
const value = await resolveApiKey(ctx, provider);
|
|
6394
|
+
async function requireApiKey(ctx, provider, tool, keyRef) {
|
|
6395
|
+
const value = await resolveApiKey(ctx, provider, keyRef);
|
|
6237
6396
|
if (value !== void 0) return value;
|
|
6238
6397
|
throw new Error(tool === void 0 ? `${PROVIDER_DISPLAY_NAMES[provider]} 尚未配置 API Key,请先到 设置 > 插件 > 图像生成 配置` : `${tool} requires the ${PROVIDER_DISPLAY_NAMES[provider]} API key; configure it in Settings > Plugins > Image generation.`);
|
|
6239
6398
|
}
|
|
@@ -8263,6 +8422,39 @@ async function generateOpenAICompatibleImage(input) {
|
|
|
8263
8422
|
}), input.apiKey), input.provider, input);
|
|
8264
8423
|
}
|
|
8265
8424
|
async function editOpenAICompatibleImage(input) {
|
|
8425
|
+
if (input.editFormat === "siliconflowJson") {
|
|
8426
|
+
const body = {
|
|
8427
|
+
model: input.model,
|
|
8428
|
+
prompt: input.prompt,
|
|
8429
|
+
...input.editExtra ?? {},
|
|
8430
|
+
...input.extraBody ?? {}
|
|
8431
|
+
};
|
|
8432
|
+
const model = typeof body.model === "string" ? body.model.trim() : "";
|
|
8433
|
+
if (!model) throw new Error("硅基流动图生图需要编辑模型");
|
|
8434
|
+
body.model = model;
|
|
8435
|
+
if (model === "Qwen/Qwen-Image") throw new Error("Qwen/Qwen-Image 是文生图模型;请配置图生图模型,例如 Qwen/Qwen-Image-Edit");
|
|
8436
|
+
const maxReferences = model === "Qwen/Qwen-Image-Edit-2509" ? 3 : 1;
|
|
8437
|
+
if (input.sourceImages.length < 1 || input.sourceImages.length > maxReferences) throw new Error(`${model} 图生图支持 1–${String(maxReferences)} 张参考图,当前 ${String(input.sourceImages.length)} 张;请精简参考图或选择支持多图的编辑模型`);
|
|
8438
|
+
if ([
|
|
8439
|
+
"size",
|
|
8440
|
+
"image_size",
|
|
8441
|
+
"quality"
|
|
8442
|
+
].some((key) => key in body)) throw new Error("此硅基流动图生图格式不发送 size、image_size 或 quality;请从图生图额外字段中移除这些字段");
|
|
8443
|
+
delete body.image;
|
|
8444
|
+
delete body.image2;
|
|
8445
|
+
delete body.image3;
|
|
8446
|
+
for (const [index, source] of input.sourceImages.entries()) body[index === 0 ? "image" : `image${String(index + 1)}`] = toDataUrl$2(source);
|
|
8447
|
+
return parseImageResponse(await guardedFetch("siliconflow image edit request", input.signal, () => fetch(imageEndpoint$1(input.baseURL, "generations"), {
|
|
8448
|
+
method: "POST",
|
|
8449
|
+
redirect: "error",
|
|
8450
|
+
signal: input.signal,
|
|
8451
|
+
headers: {
|
|
8452
|
+
authorization: `Bearer ${input.apiKey}`,
|
|
8453
|
+
"content-type": "application/json"
|
|
8454
|
+
},
|
|
8455
|
+
body: JSON.stringify(body)
|
|
8456
|
+
}), input.apiKey), "siliconflow", input);
|
|
8457
|
+
}
|
|
8266
8458
|
if (input.editFormat === "qwenJson") return parseImageResponse(await guardedFetch("openai-compat image edit request", input.signal, () => fetch(imageEndpoint$1(input.baseURL, "generations"), {
|
|
8267
8459
|
method: "POST",
|
|
8268
8460
|
redirect: "error",
|
|
@@ -10158,16 +10350,18 @@ function jsonError$3(res, status, code) {
|
|
|
10158
10350
|
//#region lib/types/subscription.js
|
|
10159
10351
|
/** Translate image tool options into each subscription channel's wire inputs. */
|
|
10160
10352
|
function subscriptionToolParameters(provider, options) {
|
|
10161
|
-
|
|
10162
|
-
const ratio = options.aspectRatio;
|
|
10353
|
+
const requestedSize = options.size?.trim() || void 0;
|
|
10354
|
+
const ratio = options.aspectRatio?.trim() || void 0;
|
|
10355
|
+
const imageSize = options.imageSize?.trim() || void 0;
|
|
10356
|
+
if (requestedSize !== void 0 && ratio !== void 0) throw new Error("size 与 aspect_ratio 请只选一个;无关可选字段请省略,不要填写空字符串");
|
|
10163
10357
|
if (ratio !== void 0 && !SUBSCRIPTION_CAPABILITIES[provider].ratioOptions.some((option) => option.value === ratio)) throw new Error(`${provider} 不支持比例 ${ratio}`);
|
|
10164
|
-
const size =
|
|
10358
|
+
const size = requestedSize ?? (ratio === void 0 ? void 0 : provider === "chatgpt-sub" ? SUBSCRIPTION_CAPABILITIES[provider].sizeFor?.(ratio, "auto") : ratio);
|
|
10165
10359
|
let quality;
|
|
10166
|
-
if (
|
|
10167
|
-
if (provider === "grok-sub" && (
|
|
10168
|
-
else if (provider === "google-sub" &&
|
|
10169
|
-
else if (provider === "google-sub" &&
|
|
10170
|
-
else throw new Error(`${provider} 不支持清晰度 ${
|
|
10360
|
+
if (imageSize !== void 0) {
|
|
10361
|
+
if (provider === "grok-sub" && (imageSize === "1K" || imageSize === "2K")) quality = imageSize.toLowerCase();
|
|
10362
|
+
else if (provider === "google-sub" && imageSize === "4K") quality = "hd";
|
|
10363
|
+
else if (provider === "google-sub" && imageSize === "1K") quality = void 0;
|
|
10364
|
+
else throw new Error(`${provider} 不支持清晰度 ${imageSize};${provider === "chatgpt-sub" ? "请省略 image_size,使用 size 或 aspect_ratio 指定尺寸" : `image_size 仅支持 ${provider === "grok-sub" ? "1K、2K" : "1K、4K"}`}`);
|
|
10171
10365
|
}
|
|
10172
10366
|
return {
|
|
10173
10367
|
...size === void 0 ? {} : { size },
|
|
@@ -27765,11 +27959,14 @@ function openAICompatStudioProfile(config, model, configured) {
|
|
|
27765
27959
|
}
|
|
27766
27960
|
/** Return only browser-safe capability data. */
|
|
27767
27961
|
async function describeStudio(ctx, config, subscriptions) {
|
|
27768
|
-
const
|
|
27962
|
+
const compatChannels = resolveOpenAICompatChannels(config);
|
|
27963
|
+
const configuredEntries = await Promise.all([...CLOUD_IMAGE_PROVIDERS.filter((provider) => provider !== "openai-compat").map(async (provider) => {
|
|
27769
27964
|
return [provider, await resolveApiKey(ctx, provider) !== void 0];
|
|
27770
|
-
}))
|
|
27965
|
+
}), ...compatChannels.map(async (channel) => {
|
|
27966
|
+
return [`openai-compat:${channel.id}`, await resolveApiKey(ctx, "openai-compat", channel.keyRef) !== void 0];
|
|
27967
|
+
})]);
|
|
27771
27968
|
const configured = Object.fromEntries(configuredEntries);
|
|
27772
|
-
const cloudProfiles = CLOUD_IMAGE_PROVIDERS.
|
|
27969
|
+
const cloudProfiles = CLOUD_IMAGE_PROVIDERS.flatMap((provider) => provider === "openai-compat" ? compatChannels.map((channel) => studioProfile(config, provider, configured[`openai-compat:${channel.id}`] === true, channel.id)) : [studioProfile(config, provider, configured[provider] === true)]);
|
|
27773
27970
|
const subStatuses = await Promise.all(SUBSCRIPTION_PROVIDERS.map(async (provider) => {
|
|
27774
27971
|
return [provider, subscriptions !== void 0 && (await subscriptions.loginStatus(vendorOf(provider))).state === "logged-in"];
|
|
27775
27972
|
}));
|
|
@@ -27778,9 +27975,12 @@ async function describeStudio(ctx, config, subscriptions) {
|
|
|
27778
27975
|
});
|
|
27779
27976
|
const profiles = [...cloudProfiles, ...subProfiles];
|
|
27780
27977
|
const preferred = config.provider;
|
|
27978
|
+
const activeProvider = isStudioPreferred(preferred) ? preferred : profiles.find((profile) => profile.configured)?.provider ?? "google";
|
|
27979
|
+
const activeChannel = activeProvider === "openai-compat" ? profiles.find((profile) => profile.provider === activeProvider && profile.channel === resolveOpenAICompatChannel(config).id)?.channel ?? profiles.find((profile) => profile.provider === activeProvider)?.channel : void 0;
|
|
27781
27980
|
return {
|
|
27782
27981
|
providers: profiles,
|
|
27783
|
-
activeProvider
|
|
27982
|
+
activeProvider,
|
|
27983
|
+
...activeChannel === void 0 ? {} : { activeChannel }
|
|
27784
27984
|
};
|
|
27785
27985
|
}
|
|
27786
27986
|
/** The persisted default provider is usable directly when the workbench can drive it. */
|
|
@@ -27816,12 +28016,14 @@ async function generateFromStudio(ctx, config, input, signal, fallbackWorkspaceR
|
|
|
27816
28016
|
assertAllowed(subscriptionStudioProfile(input.provider, true), input);
|
|
27817
28017
|
return generateSubscriptionFromStudio(ctx, subscriptions, input, signal);
|
|
27818
28018
|
}
|
|
27819
|
-
|
|
27820
|
-
const
|
|
28019
|
+
const channel = input.provider === "openai-compat" ? resolveOpenAICompatChannel(config, input.channel) : void 0;
|
|
28020
|
+
const requestConfig = input.provider === "openai-compat" ? withOpenAICompatChannel(config, input.channel) : config;
|
|
28021
|
+
assertAllowed(studioProfile(requestConfig, input.provider, true, channel?.id), input);
|
|
28022
|
+
const active = resolveProvider(withProviderOverrides(requestConfig, input.provider, input.model));
|
|
27821
28023
|
if (active.provider === "comfyui") throw new Error("ComfyUI 暂未接入工作台");
|
|
27822
28024
|
if (isSubscriptionProvider(active.provider)) throw new Error("订阅生图应通过订阅渠道执行");
|
|
27823
28025
|
const wired = active;
|
|
27824
|
-
const credential = await requireApiKey(ctx, input.provider);
|
|
28026
|
+
const credential = await requireApiKey(ctx, input.provider, void 0, channel?.keyRef);
|
|
27825
28027
|
const rawRefs = input.references ?? (input.reference ? [input.reference] : []);
|
|
27826
28028
|
if (input.mode === "edit" && rawRefs.length === 0) throw new Error("图生图需要至少一张参考图");
|
|
27827
28029
|
const sourceImages = input.mode === "edit" ? await Promise.all(rawRefs.map((ref) => readStudioReference(ctx, ref, signal))) : [];
|
|
@@ -27858,16 +28060,16 @@ async function generateFromStudio(ctx, config, input, signal, fallbackWorkspaceR
|
|
|
27858
28060
|
output = `${aspectRatio}, ${imageSize}`;
|
|
27859
28061
|
} else if (wired.provider === "openai" || wired.provider === "openai-compat" || wired.provider === "xai" || wired.provider === "zhipu") {
|
|
27860
28062
|
const p = wired.provider;
|
|
27861
|
-
const officialTable = p === "openai" ? officialSizeTable(
|
|
28063
|
+
const officialTable = p === "openai" ? officialSizeTable(requestConfig, wired.model) : void 0;
|
|
27862
28064
|
const officialResolution = input.resolution ?? (officialTable ? openAICompatTable({ openaiCompatSizes: officialTable }).find((group) => group.ratio === input.ratio)?.tiers[0]?.tier : void 0) ?? "1K";
|
|
27863
|
-
const size = p === "openai-compat" ? openAIRequestSize(
|
|
28065
|
+
const size = p === "openai-compat" ? openAIRequestSize(requestConfig, input.ratio, input.resolution ?? input.quality) : p === "openai" ? openAIRequestSize({ openaiCompatSizes: officialTable }, input.ratio, officialResolution) : p === "xai" ? void 0 : CLOUD_CAPABILITIES[p].sizeFor?.(input.ratio, input.quality);
|
|
27864
28066
|
if (p === "openai" && size !== void 0) validateOfficialSize(wired.model, size);
|
|
27865
28067
|
const requestedQuality = input.generationQuality ?? input.quality;
|
|
27866
|
-
const quality = p === "openai" ? requestedQuality === "auto" ? void 0 : requestedQuality : p === "openai-compat" ? compatRequestQuality(
|
|
28068
|
+
const quality = p === "openai" ? requestedQuality === "auto" ? void 0 : requestedQuality : p === "openai-compat" ? compatRequestQuality(requestConfig, input.generationQuality) : p === "zhipu" ? "hd" : void 0;
|
|
27867
28069
|
const extraBody = p === "xai" ? {
|
|
27868
28070
|
aspect_ratio: input.ratio,
|
|
27869
28071
|
resolution: input.quality
|
|
27870
|
-
} : p === "openai-compat" ? input.mode === "generate" ? compatGenerationExtraBody(
|
|
28072
|
+
} : p === "openai-compat" ? input.mode === "generate" ? compatGenerationExtraBody(requestConfig, input.watermark) : compatEditExtraBody(requestConfig, input.watermark) : void 0;
|
|
27871
28073
|
generated = input.mode === "edit" ? await editOpenAICompatibleImage({
|
|
27872
28074
|
apiKey: credential,
|
|
27873
28075
|
baseURL: wired.baseURL,
|
|
@@ -27882,7 +28084,7 @@ async function generateFromStudio(ctx, config, input, signal, fallbackWorkspaceR
|
|
|
27882
28084
|
...p === "openai-compat" ? {
|
|
27883
28085
|
editFormat: wired.editFormat,
|
|
27884
28086
|
editExtra: wired.editExtra,
|
|
27885
|
-
editSizeMode:
|
|
28087
|
+
editSizeMode: requestConfig.openaiCompatEditSizeMode ?? "auto"
|
|
27886
28088
|
} : p === "xai" ? { editFormat: "xaiJson" } : {}
|
|
27887
28089
|
}) : await generateOpenAICompatibleImage({
|
|
27888
28090
|
provider: p,
|
|
@@ -27967,6 +28169,7 @@ async function generateFromStudio(ctx, config, input, signal, fallbackWorkspaceR
|
|
|
27967
28169
|
attachment: single.attachment,
|
|
27968
28170
|
output: single.output,
|
|
27969
28171
|
provider: input.provider,
|
|
28172
|
+
...channel === void 0 ? {} : { channel: channel.id },
|
|
27970
28173
|
model: input.model,
|
|
27971
28174
|
prompt: input.prompt,
|
|
27972
28175
|
createdAt: Date.now(),
|
|
@@ -27997,6 +28200,7 @@ async function generateFromStudio(ctx, config, input, signal, fallbackWorkspaceR
|
|
|
27997
28200
|
attachment: first.attachment,
|
|
27998
28201
|
output: first.output,
|
|
27999
28202
|
provider: input.provider,
|
|
28203
|
+
...channel === void 0 ? {} : { channel: channel.id },
|
|
28000
28204
|
model: input.model,
|
|
28001
28205
|
prompt: input.prompt,
|
|
28002
28206
|
createdAt: Date.now(),
|
|
@@ -28120,11 +28324,19 @@ function subscriptionResponse(input, startedAt, requestedCount, failedCount, ite
|
|
|
28120
28324
|
...errors !== void 0 && errors.length > 0 ? { errors } : {}
|
|
28121
28325
|
};
|
|
28122
28326
|
}
|
|
28123
|
-
function studioProfile(config, provider, configured) {
|
|
28124
|
-
const
|
|
28327
|
+
function studioProfile(config, provider, configured, channelId) {
|
|
28328
|
+
const channel = provider === "openai-compat" && channelId !== void 0 ? resolveOpenAICompatChannel(config, channelId) : void 0;
|
|
28329
|
+
const scoped = channel === void 0 ? config : withOpenAICompatChannel(config, channel.id);
|
|
28330
|
+
const result = cloudStudioProfile(scoped, provider, configured);
|
|
28331
|
+
const editModel = provider === "openai-compat" ? scoped.openaiCompatEditModel?.trim() : void 0;
|
|
28125
28332
|
return {
|
|
28126
28333
|
...result,
|
|
28127
|
-
...
|
|
28334
|
+
...channel === void 0 ? {} : {
|
|
28335
|
+
channel: channel.id,
|
|
28336
|
+
label: channel.label
|
|
28337
|
+
},
|
|
28338
|
+
...editModel ? { editModel } : {},
|
|
28339
|
+
...watermarkProfile(scoped, provider, result.model)
|
|
28128
28340
|
};
|
|
28129
28341
|
}
|
|
28130
28342
|
function cloudStudioProfile(config, provider, configured) {
|
|
@@ -28163,7 +28375,14 @@ function cloudStudioProfile(config, provider, configured) {
|
|
|
28163
28375
|
/** Output capabilities remain visible before credentials, address or model are configured. */
|
|
28164
28376
|
function compatibleStudioProfile(config, model, configured) {
|
|
28165
28377
|
const groups = openAICompatTable(config);
|
|
28166
|
-
const
|
|
28378
|
+
const capabilityProfile = {
|
|
28379
|
+
modelOptions: resolveOpenAICompatModels({
|
|
28380
|
+
...config,
|
|
28381
|
+
openaiCompatModel: model
|
|
28382
|
+
}).map((value) => ({
|
|
28383
|
+
value,
|
|
28384
|
+
label: value
|
|
28385
|
+
})),
|
|
28167
28386
|
generationQualityOptions: compatQualityOptions(config),
|
|
28168
28387
|
defaultGenerationQuality: compatRequestQuality(config) ?? "__default__",
|
|
28169
28388
|
resolutionOptions: Object.fromEntries(groups.map((group) => [group.ratio, group.tiers.map((entry) => ({
|
|
@@ -28180,7 +28399,7 @@ function compatibleStudioProfile(config, model, configured) {
|
|
|
28180
28399
|
value: "standard",
|
|
28181
28400
|
label: "标准(推荐)"
|
|
28182
28401
|
}], "1:1", "standard"),
|
|
28183
|
-
...
|
|
28402
|
+
...capabilityProfile,
|
|
28184
28403
|
resolutionOptions: Object.fromEntries(groups.map((group) => [group.ratio, [{
|
|
28185
28404
|
value: "standard",
|
|
28186
28405
|
label: `标准 · ${group.tiers[0].size}`
|
|
@@ -28188,7 +28407,7 @@ function compatibleStudioProfile(config, model, configured) {
|
|
|
28188
28407
|
};
|
|
28189
28408
|
return {
|
|
28190
28409
|
...openAICompatStudioProfile(config, model, configured),
|
|
28191
|
-
...
|
|
28410
|
+
...capabilityProfile
|
|
28192
28411
|
};
|
|
28193
28412
|
}
|
|
28194
28413
|
function profile(provider, model, configured, ratioOptions, qualityOptions, defaultRatio, defaultQuality) {
|
|
@@ -28221,7 +28440,7 @@ function option(value) {
|
|
|
28221
28440
|
};
|
|
28222
28441
|
}
|
|
28223
28442
|
function assertAllowed(profile, input) {
|
|
28224
|
-
if (input.
|
|
28443
|
+
if (!studioModelOptionsForMode(profile, input.mode).some((option) => option.value === input.model)) throw new Error("模型配置已变化,请刷新工作台后重试");
|
|
28225
28444
|
if (!profile.ratioOptions.some((option) => option.value === input.ratio)) throw new Error("该 Provider 不支持所选比例");
|
|
28226
28445
|
const selectedQuality = input.provider === "openai" ? input.generationQuality ?? input.quality : input.resolution ?? input.quality;
|
|
28227
28446
|
if (!profile.qualityOptions.some((option) => option.value === selectedQuality)) throw new Error("该 Provider 不支持所选清晰度");
|
|
@@ -28272,8 +28491,6 @@ async function serveStudio(req, res, deps) {
|
|
|
28272
28491
|
if (!(req.headers["content-type"] ?? "").toLowerCase().startsWith("application/json")) return jsonError$1(res, 415, "json-required");
|
|
28273
28492
|
const controller = new AbortController();
|
|
28274
28493
|
const watchdog = new AbortController();
|
|
28275
|
-
const CLOUD_WATCHDOG_MS = 15e4;
|
|
28276
|
-
let subscriptionChannel = false;
|
|
28277
28494
|
const signal = AbortSignal.any([controller.signal, watchdog.signal]);
|
|
28278
28495
|
const onConnectionClose = () => {
|
|
28279
28496
|
if (!res.writableEnded) controller.abort(/* @__PURE__ */ new Error("The browser closed the image generation request."));
|
|
@@ -28290,10 +28507,12 @@ async function serveStudio(req, res, deps) {
|
|
|
28290
28507
|
} catch (error) {
|
|
28291
28508
|
return jsonError$1(res, 400, errorMessage(error, "invalid-request"));
|
|
28292
28509
|
}
|
|
28293
|
-
subscriptionChannel = isSubscriptionProvider(input.provider);
|
|
28294
|
-
|
|
28510
|
+
const subscriptionChannel = isSubscriptionProvider(input.provider);
|
|
28511
|
+
const timeoutMs = subscriptionChannel ? SUBSCRIPTION_TIMEOUT_MS : deps.timeoutMs ?? 15e4;
|
|
28512
|
+
const timeout = setTimeout(() => {
|
|
28295
28513
|
watchdog.abort();
|
|
28296
|
-
},
|
|
28514
|
+
}, timeoutMs);
|
|
28515
|
+
timeout.unref?.();
|
|
28297
28516
|
res.writeHead(200, {
|
|
28298
28517
|
"content-type": "application/json; charset=utf-8",
|
|
28299
28518
|
"cache-control": "no-store",
|
|
@@ -28308,11 +28527,12 @@ async function serveStudio(req, res, deps) {
|
|
|
28308
28527
|
res.end(JSON.stringify(output));
|
|
28309
28528
|
} catch (error) {
|
|
28310
28529
|
if (res.destroyed || res.writableEnded) return;
|
|
28311
|
-
const message = watchdog.signal.aborted ? subscriptionChannel ?
|
|
28530
|
+
const message = watchdog.signal.aborted ? subscriptionChannel ? `上游生成超时(订阅通道 ${String(Math.round(timeoutMs / 1e3))} 秒未返回),请稍后重试;${UPSTREAM_TIMEOUT_HINT}` : `上游生成超时(${String(Math.round(timeoutMs / 1e3))} 秒未返回),请检查网络或代理后重试;${UPSTREAM_TIMEOUT_HINT}` : errorMessage(error, "generation-failed");
|
|
28312
28531
|
res.end(JSON.stringify({ error: message }));
|
|
28313
28532
|
}
|
|
28314
28533
|
} finally {
|
|
28315
28534
|
clearInterval(heartbeat);
|
|
28535
|
+
clearTimeout(timeout);
|
|
28316
28536
|
}
|
|
28317
28537
|
} catch (error) {
|
|
28318
28538
|
if (res.headersSent || res.writableEnded || res.destroyed) return;
|
|
@@ -28328,6 +28548,8 @@ function parseStudioGenerateRequest(value) {
|
|
|
28328
28548
|
if (input === void 0) throw new Error("请求格式无效");
|
|
28329
28549
|
if (input.mode !== "generate" && input.mode !== "edit") throw new Error("请选择生成类型");
|
|
28330
28550
|
if (!studioProvider(input.provider)) throw new Error("不支持该图像 Provider");
|
|
28551
|
+
if (input.channel !== void 0 && input.provider !== "openai-compat") throw new Error("仅 OpenAI 兼容 Provider 支持选择渠道");
|
|
28552
|
+
const channel = input.channel === void 0 ? void 0 : requiredText(input.channel, "请选择渠道", 100);
|
|
28331
28553
|
const prompt = requiredText(input.prompt, "请输入提示词");
|
|
28332
28554
|
const model = requiredText(input.model, "请选择模型", 200);
|
|
28333
28555
|
const ratio = requiredText(input.ratio, "请选择比例", 32);
|
|
@@ -28348,6 +28570,7 @@ function parseStudioGenerateRequest(value) {
|
|
|
28348
28570
|
return {
|
|
28349
28571
|
mode: input.mode,
|
|
28350
28572
|
provider: input.provider,
|
|
28573
|
+
...channel === void 0 ? {} : { channel },
|
|
28351
28574
|
model,
|
|
28352
28575
|
prompt,
|
|
28353
28576
|
ratio,
|
|
@@ -28706,12 +28929,17 @@ async function serveTestConnection(req, res, deps) {
|
|
|
28706
28929
|
const provider = record(body)?.provider;
|
|
28707
28930
|
if (typeof provider !== "string" || !IMAGE_PROVIDERS.includes(provider)) return jsonError(res, 400, "invalid-provider");
|
|
28708
28931
|
const active = provider;
|
|
28709
|
-
|
|
28932
|
+
const action = record(body)?.action;
|
|
28933
|
+
const rawChannel = record(body)?.channel;
|
|
28934
|
+
if (rawChannel !== void 0 && (typeof rawChannel !== "string" || rawChannel.trim().length === 0 || active !== "openai-compat")) return jsonError(res, 400, "invalid-channel");
|
|
28935
|
+
const channel = typeof rawChannel === "string" ? rawChannel.trim() : void 0;
|
|
28936
|
+
const requestConfig = () => active === "openai-compat" ? withOpenAICompatChannel(deps.config(), channel) : deps.config();
|
|
28937
|
+
if (action === "models") {
|
|
28710
28938
|
if (active === "comfyui" || isSubscriptionProvider(active)) return jsonError(res, 400, "models-unsupported");
|
|
28711
28939
|
let result;
|
|
28712
28940
|
try {
|
|
28713
|
-
const apiKey = await deps.resolveKey(active);
|
|
28714
|
-
result = active === "google" ? await fetchGoogleImageModels(
|
|
28941
|
+
const apiKey = await deps.resolveKey(active, channel);
|
|
28942
|
+
result = active === "google" ? await fetchGoogleImageModels(requestConfig(), apiKey) : active === "dashscope" ? await fetchDashScopeImageModels(requestConfig(), apiKey) : await fetchOpenAIImageModels(active, requestConfig(), apiKey);
|
|
28715
28943
|
} catch (error) {
|
|
28716
28944
|
result = {
|
|
28717
28945
|
ok: false,
|
|
@@ -28726,8 +28954,8 @@ async function serveTestConnection(req, res, deps) {
|
|
|
28726
28954
|
if (active === "comfyui") result = await probeComfyUIConnection(deps.config());
|
|
28727
28955
|
else if (isSubscriptionProvider(active)) result = await probeSubscriptionConnection(active, deps.subscriptionManager);
|
|
28728
28956
|
else {
|
|
28729
|
-
const apiKey = await deps.resolveKey(active);
|
|
28730
|
-
result = await probeProviderConnection(active,
|
|
28957
|
+
const apiKey = await deps.resolveKey(active, channel);
|
|
28958
|
+
result = await probeProviderConnection(active, requestConfig(), apiKey);
|
|
28731
28959
|
}
|
|
28732
28960
|
} catch (error) {
|
|
28733
28961
|
result = {
|
|
@@ -28791,20 +29019,65 @@ function serveSettingsHealth(req, res, health) {
|
|
|
28791
29019
|
res.end(JSON.stringify(health));
|
|
28792
29020
|
}
|
|
28793
29021
|
//#endregion
|
|
29022
|
+
//#region lib/types/image-tool-context.js
|
|
29023
|
+
/** Live, credential-free instructions for the conversation model's image tools. */
|
|
29024
|
+
function imageToolContext(config) {
|
|
29025
|
+
const provider = config.provider ?? "google";
|
|
29026
|
+
let model = "not configured";
|
|
29027
|
+
let editModel = model;
|
|
29028
|
+
try {
|
|
29029
|
+
const active = resolveProvider(config);
|
|
29030
|
+
model = active.provider === "comfyui" ? active.workflow?.name ?? "workflow selected in settings" : active.model;
|
|
29031
|
+
const edit = resolveProvider(withProviderOverrides(config, void 0, void 0, "edit"));
|
|
29032
|
+
editModel = edit.provider === "comfyui" ? edit.workflow?.name ?? model : edit.model;
|
|
29033
|
+
} catch {}
|
|
29034
|
+
return [
|
|
29035
|
+
`Image tools current default provider: ${provider}; generation model: ${JSON.stringify(model)}; editing model: ${JSON.stringify(editModel)}.`,
|
|
29036
|
+
"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.",
|
|
29037
|
+
"Omit unused optional arguments entirely; never fill them with empty strings. size and aspect_ratio are alternatives: provide at most one.",
|
|
29038
|
+
"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.",
|
|
29039
|
+
"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."
|
|
29040
|
+
].join("\n");
|
|
29041
|
+
}
|
|
29042
|
+
//#endregion
|
|
28794
29043
|
//#region lib/types/index.js
|
|
28795
29044
|
const name = "dsh-image-gen";
|
|
28796
29045
|
const inject = [
|
|
28797
29046
|
"tools",
|
|
28798
29047
|
"attachments",
|
|
28799
|
-
"credentials"
|
|
28800
|
-
"webServer"
|
|
29048
|
+
"credentials"
|
|
28801
29049
|
];
|
|
29050
|
+
/** Optional strings from tool callers may contain empty placeholders (#86). */
|
|
29051
|
+
function cleanImageToolArgs(args) {
|
|
29052
|
+
const clean = { ...args };
|
|
29053
|
+
const fields = clean;
|
|
29054
|
+
for (const key of [
|
|
29055
|
+
"model",
|
|
29056
|
+
"channel",
|
|
29057
|
+
"size",
|
|
29058
|
+
"aspect_ratio",
|
|
29059
|
+
"image_size",
|
|
29060
|
+
"quality",
|
|
29061
|
+
"workflow"
|
|
29062
|
+
]) {
|
|
29063
|
+
const value = fields[key];
|
|
29064
|
+
if (typeof value !== "string") continue;
|
|
29065
|
+
if (value.trim().length === 0) delete fields[key];
|
|
29066
|
+
else fields[key] = value.trim();
|
|
29067
|
+
}
|
|
29068
|
+
return clean;
|
|
29069
|
+
}
|
|
28802
29070
|
/** Validate the untrusted per-call provider override from tool arguments. */
|
|
28803
29071
|
function providerOverrideOf(value) {
|
|
28804
29072
|
if (value === void 0 || value === null || value === "") return void 0;
|
|
28805
29073
|
if (typeof value !== "string" || !IMAGE_PROVIDERS.includes(value)) throw new Error(`Unsupported provider ${JSON.stringify(value)}. Supported providers: ${IMAGE_PROVIDERS.join(", ")}.`);
|
|
28806
29074
|
return value;
|
|
28807
29075
|
}
|
|
29076
|
+
function configForToolCall(config, provider, channel) {
|
|
29077
|
+
const selected = provider ?? config.provider ?? "google";
|
|
29078
|
+
if (channel !== void 0 && selected !== "openai-compat") throw new Error("The channel argument is only supported with provider=openai-compat.");
|
|
29079
|
+
return selected === "openai-compat" ? withOpenAICompatChannel(config, channel) : config;
|
|
29080
|
+
}
|
|
28808
29081
|
function apply(ctx, config = {}) {
|
|
28809
29082
|
const plainConfig = (source) => {
|
|
28810
29083
|
const out = {};
|
|
@@ -28815,143 +29088,155 @@ function apply(ctx, config = {}) {
|
|
|
28815
29088
|
const knownWorkspaceRoots = /* @__PURE__ */ new Set();
|
|
28816
29089
|
const canvasMirror = new CanvasMirror();
|
|
28817
29090
|
const subscriptionManager = new SubscriptionManager(ctx);
|
|
28818
|
-
registerSubscriptionRoutes(ctx, subscriptionManager);
|
|
28819
29091
|
const settingsHealth = installImageSettings(ctx, current(), {
|
|
28820
29092
|
setSource: (source) => {
|
|
28821
29093
|
current = () => migrateOpenAICompatConfig(plainConfig(source()));
|
|
28822
29094
|
},
|
|
28823
29095
|
onChange: () => {}
|
|
28824
29096
|
});
|
|
28825
|
-
ctx.
|
|
28826
|
-
|
|
28827
|
-
|
|
28828
|
-
|
|
28829
|
-
|
|
28830
|
-
|
|
28831
|
-
|
|
28832
|
-
|
|
28833
|
-
|
|
28834
|
-
|
|
28835
|
-
|
|
28836
|
-
|
|
28837
|
-
|
|
28838
|
-
|
|
28839
|
-
|
|
28840
|
-
|
|
28841
|
-
|
|
28842
|
-
|
|
28843
|
-
|
|
28844
|
-
|
|
28845
|
-
|
|
28846
|
-
|
|
28847
|
-
|
|
28848
|
-
|
|
28849
|
-
|
|
28850
|
-
...knownWorkspaceRoots,
|
|
28851
|
-
...discovered,
|
|
28852
|
-
process.cwd()
|
|
28853
|
-
]));
|
|
28854
|
-
} })
|
|
28855
|
-
}), "dsh-image-gen: delete route");
|
|
28856
|
-
ctx.effect(() => ctx.webServer.register({
|
|
28857
|
-
kind: "exact",
|
|
28858
|
-
path: SAVE_WORKSPACE_ROUTE,
|
|
28859
|
-
handler: (req, res) => serveSaveWorkspace(req, res, {
|
|
28860
|
-
readImage: (ref) => ctx.attachments.readImage(ref),
|
|
28861
|
-
saveToWorkspace: (options) => {
|
|
28862
|
-
if (options.workspaceRoot) knownWorkspaceRoots.add(options.workspaceRoot);
|
|
28863
|
-
return saveImageToWorkspace({
|
|
28864
|
-
workspaceRoot: options.workspaceRoot,
|
|
28865
|
-
folder: current().workspaceFolder,
|
|
28866
|
-
attachmentId: options.attachmentId,
|
|
28867
|
-
mediaType: options.mediaType,
|
|
28868
|
-
data: options.data
|
|
28869
|
-
});
|
|
28870
|
-
},
|
|
28871
|
-
getActiveWorkspaceRoot: () => Array.from(knownWorkspaceRoots)[0] || process.cwd(),
|
|
28872
|
-
getAllowedWorkspaceRoots: async () => {
|
|
29097
|
+
ctx.inject(["webServer"], (webCtx) => {
|
|
29098
|
+
registerSubscriptionRoutes(webCtx, subscriptionManager);
|
|
29099
|
+
webCtx.effect(() => webCtx.webServer.register({
|
|
29100
|
+
kind: "exact",
|
|
29101
|
+
path: SETTINGS_HEALTH_ROUTE,
|
|
29102
|
+
handler: (req, res) => serveSettingsHealth(req, res, settingsHealth())
|
|
29103
|
+
}), "dsh-image-gen: settings health");
|
|
29104
|
+
webCtx.effect(() => webCtx.webServer.register({
|
|
29105
|
+
kind: "exact",
|
|
29106
|
+
path: IMAGE_ROUTE,
|
|
29107
|
+
handler: (req, res) => serveImage(req, res, { readImage: (ref) => ctx.attachments.readImage(ref) })
|
|
29108
|
+
}), "dsh-image-gen: image route");
|
|
29109
|
+
webCtx.effect(() => webCtx.webServer.register({
|
|
29110
|
+
kind: "exact",
|
|
29111
|
+
path: IMPORT_ROUTE,
|
|
29112
|
+
handler: (req, res) => serveImport(req, res, {
|
|
29113
|
+
saveImage: (image) => ctx.attachments.saveImage(image),
|
|
29114
|
+
maxImageBytes: ctx.attachments.imageLimits.maxImageBytes,
|
|
29115
|
+
mediaTypes: ctx.attachments.imageLimits.mediaTypes
|
|
29116
|
+
})
|
|
29117
|
+
}), "dsh-image-gen: import route");
|
|
29118
|
+
webCtx.effect(() => webCtx.webServer.register({
|
|
29119
|
+
kind: "exact",
|
|
29120
|
+
path: DELETE_ROUTE,
|
|
29121
|
+
handler: (req, res) => serveDelete(req, res, { deleteWorkspaceImage: async (filePath) => {
|
|
28873
29122
|
const discovered = await getDshWorkspaceRoots().catch(() => []);
|
|
28874
|
-
return /* @__PURE__ */ new Set([
|
|
29123
|
+
return deleteImageFromWorkspace(filePath, /* @__PURE__ */ new Set([
|
|
28875
29124
|
...knownWorkspaceRoots,
|
|
28876
29125
|
...discovered,
|
|
28877
29126
|
process.cwd()
|
|
28878
|
-
]);
|
|
28879
|
-
}
|
|
28880
|
-
|
|
28881
|
-
|
|
28882
|
-
|
|
28883
|
-
|
|
28884
|
-
|
|
28885
|
-
|
|
28886
|
-
|
|
28887
|
-
|
|
28888
|
-
|
|
28889
|
-
|
|
28890
|
-
|
|
28891
|
-
|
|
28892
|
-
|
|
28893
|
-
|
|
28894
|
-
|
|
28895
|
-
|
|
28896
|
-
|
|
28897
|
-
|
|
28898
|
-
|
|
28899
|
-
|
|
28900
|
-
|
|
28901
|
-
|
|
28902
|
-
|
|
28903
|
-
|
|
28904
|
-
|
|
28905
|
-
|
|
28906
|
-
|
|
28907
|
-
})
|
|
28908
|
-
|
|
29127
|
+
]));
|
|
29128
|
+
} })
|
|
29129
|
+
}), "dsh-image-gen: delete route");
|
|
29130
|
+
webCtx.effect(() => webCtx.webServer.register({
|
|
29131
|
+
kind: "exact",
|
|
29132
|
+
path: SAVE_WORKSPACE_ROUTE,
|
|
29133
|
+
handler: (req, res) => serveSaveWorkspace(req, res, {
|
|
29134
|
+
readImage: (ref) => ctx.attachments.readImage(ref),
|
|
29135
|
+
saveToWorkspace: (options) => {
|
|
29136
|
+
if (options.workspaceRoot) knownWorkspaceRoots.add(options.workspaceRoot);
|
|
29137
|
+
return saveImageToWorkspace({
|
|
29138
|
+
workspaceRoot: options.workspaceRoot,
|
|
29139
|
+
folder: current().workspaceFolder,
|
|
29140
|
+
attachmentId: options.attachmentId,
|
|
29141
|
+
mediaType: options.mediaType,
|
|
29142
|
+
data: options.data
|
|
29143
|
+
});
|
|
29144
|
+
},
|
|
29145
|
+
getActiveWorkspaceRoot: () => Array.from(knownWorkspaceRoots)[0] || process.cwd(),
|
|
29146
|
+
getAllowedWorkspaceRoots: async () => {
|
|
29147
|
+
const discovered = await getDshWorkspaceRoots().catch(() => []);
|
|
29148
|
+
return /* @__PURE__ */ new Set([
|
|
29149
|
+
...knownWorkspaceRoots,
|
|
29150
|
+
...discovered,
|
|
29151
|
+
process.cwd()
|
|
29152
|
+
]);
|
|
29153
|
+
},
|
|
29154
|
+
isSaveEnabled: () => current().saveToWorkspace !== false
|
|
29155
|
+
})
|
|
29156
|
+
}), "dsh-image-gen: save workspace route");
|
|
29157
|
+
webCtx.effect(() => webCtx.webServer.register({
|
|
29158
|
+
kind: "exact",
|
|
29159
|
+
path: TEST_CONNECTION_ROUTE,
|
|
29160
|
+
handler: (req, res) => serveTestConnection(req, res, {
|
|
29161
|
+
resolveKey: (provider, channel) => resolveApiKey(ctx, provider, provider === "openai-compat" ? resolveOpenAICompatChannel(current(), channel).keyRef : void 0),
|
|
29162
|
+
config: () => current(),
|
|
29163
|
+
subscriptionManager
|
|
29164
|
+
})
|
|
29165
|
+
}), "dsh-image-gen: test connection route");
|
|
29166
|
+
webCtx.effect(() => webCtx.webServer.register({
|
|
29167
|
+
kind: "exact",
|
|
29168
|
+
path: CANVAS_STATE_ROUTE,
|
|
29169
|
+
handler: (req, res) => serveCanvasState(req, res, {
|
|
29170
|
+
mirror: canvasMirror,
|
|
29171
|
+
maxBodyBytes: Math.ceil(ctx.attachments.imageLimits.maxImageBytes * 1.4) + 262144,
|
|
29172
|
+
maxImageBytes: ctx.attachments.imageLimits.maxImageBytes
|
|
29173
|
+
})
|
|
29174
|
+
}), "dsh-image-gen: canvas state route");
|
|
29175
|
+
webCtx.effect(() => webCtx.webServer.register({
|
|
29176
|
+
kind: "exact",
|
|
29177
|
+
path: CANVAS_ASSET_ROUTE,
|
|
29178
|
+
handler: (req, res) => serveCanvasAsset(req, res, {
|
|
29179
|
+
maxImageBytes: ctx.attachments.imageLimits.maxImageBytes,
|
|
29180
|
+
saveImage: (image) => ctx.attachments.saveImage(image)
|
|
29181
|
+
})
|
|
29182
|
+
}), "dsh-image-gen: canvas asset route");
|
|
29183
|
+
});
|
|
28909
29184
|
ctx.inject(["systemPrompt"], (promptCtx) => {
|
|
28910
|
-
promptCtx.systemPrompt.context({
|
|
29185
|
+
promptCtx.effect(() => promptCtx.systemPrompt.context({
|
|
29186
|
+
name: "dsh-image-gen:image-defaults",
|
|
29187
|
+
order: 59,
|
|
29188
|
+
text: () => imageToolContext(current())
|
|
29189
|
+
}), "dsh-image-gen: image tool context");
|
|
29190
|
+
promptCtx.effect(() => promptCtx.systemPrompt.context({
|
|
28911
29191
|
name: "dsh-image-gen:canvas",
|
|
28912
29192
|
order: 60,
|
|
28913
29193
|
text: () => canvasMirror.digest()
|
|
28914
|
-
});
|
|
29194
|
+
}), "dsh-image-gen: canvas context");
|
|
28915
29195
|
});
|
|
28916
29196
|
registerCanvasTools(ctx, canvasMirror, { persistSelectionImage: (image) => ctx.attachments.saveImage({
|
|
28917
29197
|
data: image.data,
|
|
28918
29198
|
mediaType: image.mediaType,
|
|
28919
29199
|
name: "canvas-selection"
|
|
28920
29200
|
}) });
|
|
28921
|
-
ctx.
|
|
28922
|
-
|
|
28923
|
-
|
|
28924
|
-
|
|
28925
|
-
|
|
28926
|
-
|
|
28927
|
-
|
|
28928
|
-
|
|
28929
|
-
|
|
28930
|
-
|
|
28931
|
-
|
|
28932
|
-
|
|
28933
|
-
|
|
28934
|
-
|
|
28935
|
-
|
|
28936
|
-
|
|
28937
|
-
|
|
28938
|
-
|
|
28939
|
-
|
|
28940
|
-
|
|
28941
|
-
|
|
28942
|
-
|
|
28943
|
-
|
|
28944
|
-
|
|
28945
|
-
|
|
28946
|
-
|
|
28947
|
-
|
|
28948
|
-
|
|
28949
|
-
|
|
28950
|
-
|
|
28951
|
-
|
|
29201
|
+
ctx.inject(["webServer"], (webCtx) => {
|
|
29202
|
+
webCtx.effect(() => webCtx.webServer.register({
|
|
29203
|
+
kind: "exact",
|
|
29204
|
+
path: STUDIO_ROUTE,
|
|
29205
|
+
handler: (req, res) => serveStudio(req, res, {
|
|
29206
|
+
describe: async () => {
|
|
29207
|
+
const base = await describeStudio(ctx, current(), subscriptionManager);
|
|
29208
|
+
const workspaces = await getDshWorkspacesFull().catch(() => []);
|
|
29209
|
+
const activeRoot = Array.from(knownWorkspaceRoots)[0] || workspaces[0]?.path || process.cwd();
|
|
29210
|
+
return {
|
|
29211
|
+
...base,
|
|
29212
|
+
workspaceRoot: activeRoot,
|
|
29213
|
+
workspaces
|
|
29214
|
+
};
|
|
29215
|
+
},
|
|
29216
|
+
generate: (input, signal) => {
|
|
29217
|
+
const fallbackRoot = Array.from(knownWorkspaceRoots)[0] || process.cwd();
|
|
29218
|
+
return generateFromStudio(ctx, current(), input, signal, fallbackRoot, subscriptionManager);
|
|
29219
|
+
},
|
|
29220
|
+
maxBodyBytes: Math.ceil(ctx.attachments.imageLimits.maxImageBytes * 1.4 * 5) + 262144,
|
|
29221
|
+
timeoutMs: current().cloudTimeoutMs ?? 15e4
|
|
29222
|
+
})
|
|
29223
|
+
}), "dsh-image-gen: studio route");
|
|
29224
|
+
const serveInspiration = createInspirationRoute();
|
|
29225
|
+
webCtx.effect(() => webCtx.webServer.register({
|
|
29226
|
+
kind: "prefix",
|
|
29227
|
+
path: INSPIRATION_ROUTE,
|
|
29228
|
+
handler: (req, res) => {
|
|
29229
|
+
const originalUrl = req.url ?? "/";
|
|
29230
|
+
req.url = originalUrl.startsWith("/plugins/dsh-image-gen/inspiration") ? originalUrl.slice(34) || "/" : originalUrl;
|
|
29231
|
+
return serveInspiration(req, res);
|
|
29232
|
+
}
|
|
29233
|
+
}), "dsh-image-gen: inspiration route");
|
|
29234
|
+
});
|
|
28952
29235
|
/** Per-item generation request shared by generate_image and generate_images. */
|
|
28953
|
-
const generateSingle = async (
|
|
28954
|
-
const
|
|
29236
|
+
const generateSingle = async (rawArgs, exec) => {
|
|
29237
|
+
const args = cleanImageToolArgs(rawArgs);
|
|
29238
|
+
const requestConfig = configForToolCall(current(), providerOverrideOf(args.provider), args.channel);
|
|
29239
|
+
const active = resolveProvider(withProviderOverrides(requestConfig, providerOverrideOf(args.provider), args.model));
|
|
28955
29240
|
if (active.provider === "comfyui") {
|
|
28956
29241
|
const workflow = selectComfyUIWorkflow(active, args.workflow);
|
|
28957
29242
|
return saveGenerated(ctx, await generateComfyUIImage({
|
|
@@ -28978,11 +29263,12 @@ function apply(ctx, config = {}) {
|
|
|
28978
29263
|
signal: exec.signal
|
|
28979
29264
|
}), active.provider, active.model, "subscription", current(), exec, knownWorkspaceRoots);
|
|
28980
29265
|
}
|
|
28981
|
-
const
|
|
29266
|
+
const runCloud = (operation) => withGenerationTimeout(current().cloudTimeoutMs ?? 15e4, exec.signal, operation);
|
|
29267
|
+
const credential = await requireApiKey(ctx, active.provider, "generate_image", active.provider === "openai-compat" ? resolveOpenAICompatChannel(requestConfig, args.channel).keyRef : void 0);
|
|
28982
29268
|
if (active.provider === "google") {
|
|
28983
29269
|
const aspectRatio = args.aspect_ratio ?? active.aspectRatio;
|
|
28984
29270
|
const imageSize = args.image_size ?? active.imageSize;
|
|
28985
|
-
return saveGenerated(ctx, await generateGoogleImage({
|
|
29271
|
+
return saveGenerated(ctx, await runCloud((signal) => generateGoogleImage({
|
|
28986
29272
|
apiKey: credential,
|
|
28987
29273
|
endpoint: active.endpoint,
|
|
28988
29274
|
native: active.native,
|
|
@@ -28991,20 +29277,20 @@ function apply(ctx, config = {}) {
|
|
|
28991
29277
|
aspectRatio,
|
|
28992
29278
|
imageSize,
|
|
28993
29279
|
maxBytes: ctx.attachments.imageLimits.maxImageBytes,
|
|
28994
|
-
signal
|
|
28995
|
-
}), active.provider, active.model, `${aspectRatio}, ${imageSize}`, current(), exec, knownWorkspaceRoots);
|
|
29280
|
+
signal
|
|
29281
|
+
})), active.provider, active.model, `${aspectRatio}, ${imageSize}`, current(), exec, knownWorkspaceRoots);
|
|
28996
29282
|
}
|
|
28997
29283
|
if (active.provider === "dashscope") {
|
|
28998
29284
|
const size = args.size ?? active.imageSize;
|
|
28999
|
-
return saveGenerated(ctx, await generateDashScopeImage({
|
|
29285
|
+
return saveGenerated(ctx, await runCloud((signal) => generateDashScopeImage({
|
|
29000
29286
|
apiKey: credential,
|
|
29001
29287
|
endpoint: active.endpoint,
|
|
29002
29288
|
model: active.model,
|
|
29003
29289
|
prompt: args.prompt,
|
|
29004
29290
|
size,
|
|
29005
29291
|
maxBytes: ctx.attachments.imageLimits.maxImageBytes,
|
|
29006
|
-
signal
|
|
29007
|
-
}), active.provider, active.model, size, current(), exec, knownWorkspaceRoots);
|
|
29292
|
+
signal
|
|
29293
|
+
})), active.provider, active.model, size, current(), exec, knownWorkspaceRoots);
|
|
29008
29294
|
}
|
|
29009
29295
|
if (active.provider === "xai") {
|
|
29010
29296
|
const extraBody = xaiToolParameters({
|
|
@@ -29012,7 +29298,7 @@ function apply(ctx, config = {}) {
|
|
|
29012
29298
|
...args.image_size !== void 0 ? { imageSize: args.image_size } : {},
|
|
29013
29299
|
...args.size !== void 0 ? { size: args.size } : {}
|
|
29014
29300
|
});
|
|
29015
|
-
return saveGenerated(ctx, await generateOpenAICompatibleImage({
|
|
29301
|
+
return saveGenerated(ctx, await runCloud((signal) => generateOpenAICompatibleImage({
|
|
29016
29302
|
provider: "xai",
|
|
29017
29303
|
apiKey: credential,
|
|
29018
29304
|
baseURL: active.baseURL,
|
|
@@ -29020,15 +29306,15 @@ function apply(ctx, config = {}) {
|
|
|
29020
29306
|
prompt: args.prompt,
|
|
29021
29307
|
extraBody,
|
|
29022
29308
|
maxBytes: ctx.attachments.imageLimits.maxImageBytes,
|
|
29023
|
-
signal
|
|
29024
|
-
}), active.provider, active.model, extraBody.aspect_ratio ?? "auto", current(), exec, knownWorkspaceRoots);
|
|
29309
|
+
signal
|
|
29310
|
+
})), active.provider, active.model, extraBody.aspect_ratio ?? "auto", current(), exec, knownWorkspaceRoots);
|
|
29025
29311
|
}
|
|
29026
29312
|
const size = args.size ?? active.imageSize;
|
|
29027
29313
|
const arkOptions = active.provider === "seedream" ? active.arkOptions : void 0;
|
|
29028
29314
|
if (active.provider === "openai") validateOfficialSize(active.model, size);
|
|
29029
|
-
const quality = active.provider === "openai-compat" ? compatRequestQuality(
|
|
29315
|
+
const quality = active.provider === "openai-compat" ? compatRequestQuality(requestConfig, args.quality) : active.provider === "openai" ? args.quality : void 0;
|
|
29030
29316
|
if (active.provider === "openai" && quality !== void 0 && !officialQualityOptions(active.model).some((option) => option.value === quality)) throw new Error("该 OpenAI 模型不支持所选生成质量");
|
|
29031
|
-
|
|
29317
|
+
const value = await saveGenerated(ctx, await runCloud((signal) => generateOpenAICompatibleImage({
|
|
29032
29318
|
provider: active.provider,
|
|
29033
29319
|
apiKey: credential,
|
|
29034
29320
|
baseURL: active.baseURL,
|
|
@@ -29036,11 +29322,15 @@ function apply(ctx, config = {}) {
|
|
|
29036
29322
|
prompt: args.prompt,
|
|
29037
29323
|
size,
|
|
29038
29324
|
maxBytes: ctx.attachments.imageLimits.maxImageBytes,
|
|
29039
|
-
signal
|
|
29325
|
+
signal,
|
|
29040
29326
|
...quality === void 0 ? {} : { quality },
|
|
29041
|
-
...active.provider === "openai-compat" ? { extraBody:
|
|
29327
|
+
...active.provider === "openai-compat" ? { extraBody: requestConfig.openaiCompatExtraBody ?? {} } : {},
|
|
29042
29328
|
...arkOptions === void 0 ? {} : { arkOptions }
|
|
29043
|
-
}), active.provider, active.model, quality === void 0 ? size : `${size}, quality=${quality}`, current(), exec, knownWorkspaceRoots);
|
|
29329
|
+
})), active.provider, active.model, quality === void 0 ? size : `${size}, quality=${quality}`, current(), exec, knownWorkspaceRoots);
|
|
29330
|
+
return active.provider === "openai-compat" ? {
|
|
29331
|
+
...value,
|
|
29332
|
+
channel: resolveOpenAICompatChannel(requestConfig, args.channel).id
|
|
29333
|
+
} : value;
|
|
29044
29334
|
};
|
|
29045
29335
|
ctx.tools.register(defineTool({
|
|
29046
29336
|
name: "generate_image",
|
|
@@ -29066,12 +29356,16 @@ function apply(ctx, config = {}) {
|
|
|
29066
29356
|
"grok-sub",
|
|
29067
29357
|
"google-sub"
|
|
29068
29358
|
],
|
|
29069
|
-
description: "
|
|
29359
|
+
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."
|
|
29070
29360
|
},
|
|
29071
29361
|
model: {
|
|
29072
29362
|
type: "string",
|
|
29073
29363
|
description: "Optional model name for this call only, overriding the configured model. Not used by ComfyUI (use workflow instead) nor by the subscription providers (model fixed by the subscription)."
|
|
29074
29364
|
},
|
|
29365
|
+
channel: {
|
|
29366
|
+
type: "string",
|
|
29367
|
+
description: "Optional configured OpenAI-compatible channel id for this call only. Only meaningful with provider=openai-compat; omit to use the active channel."
|
|
29368
|
+
},
|
|
29075
29369
|
aspect_ratio: {
|
|
29076
29370
|
type: "string",
|
|
29077
29371
|
enum: [
|
|
@@ -29095,11 +29389,11 @@ function apply(ctx, config = {}) {
|
|
|
29095
29389
|
"2K",
|
|
29096
29390
|
"4K"
|
|
29097
29391
|
],
|
|
29098
|
-
description: "Optional output resolution for Google Gemini, xAI Grok, Grok subscription (1K/2K), or Google subscription (1K/4K)."
|
|
29392
|
+
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."
|
|
29099
29393
|
},
|
|
29100
29394
|
size: {
|
|
29101
29395
|
type: "string",
|
|
29102
|
-
description: "Optional dimensions or size tier for OpenAI, Seedream, or DashScope; WIDTHxHEIGHT maps to aspect_ratio for xAI Grok."
|
|
29396
|
+
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."
|
|
29103
29397
|
},
|
|
29104
29398
|
quality: {
|
|
29105
29399
|
type: "string",
|
|
@@ -29141,12 +29435,16 @@ function apply(ctx, config = {}) {
|
|
|
29141
29435
|
"grok-sub",
|
|
29142
29436
|
"google-sub"
|
|
29143
29437
|
],
|
|
29144
|
-
description: "
|
|
29438
|
+
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."
|
|
29145
29439
|
},
|
|
29146
29440
|
model: {
|
|
29147
29441
|
type: "string",
|
|
29148
29442
|
description: "Optional model name for this call only, applied to every item."
|
|
29149
29443
|
},
|
|
29444
|
+
channel: {
|
|
29445
|
+
type: "string",
|
|
29446
|
+
description: "Optional configured OpenAI-compatible channel id applied to every item; omit to use the active channel."
|
|
29447
|
+
},
|
|
29150
29448
|
aspect_ratio: {
|
|
29151
29449
|
type: "string",
|
|
29152
29450
|
enum: [
|
|
@@ -29170,7 +29468,7 @@ function apply(ctx, config = {}) {
|
|
|
29170
29468
|
"2K",
|
|
29171
29469
|
"4K"
|
|
29172
29470
|
],
|
|
29173
|
-
description: "Optional output resolution for Google Gemini, xAI Grok, Grok subscription (1K/2K), or Google subscription (1K/4K)."
|
|
29471
|
+
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."
|
|
29174
29472
|
},
|
|
29175
29473
|
size: {
|
|
29176
29474
|
type: "string",
|
|
@@ -29242,6 +29540,7 @@ function apply(ctx, config = {}) {
|
|
|
29242
29540
|
type: "string",
|
|
29243
29541
|
required: true
|
|
29244
29542
|
},
|
|
29543
|
+
channel: { type: "string" },
|
|
29245
29544
|
model: {
|
|
29246
29545
|
type: "string",
|
|
29247
29546
|
required: true
|
|
@@ -29314,6 +29613,7 @@ function apply(ctx, config = {}) {
|
|
|
29314
29613
|
prompt,
|
|
29315
29614
|
...args.provider !== void 0 ? { provider: args.provider } : {},
|
|
29316
29615
|
...args.model !== void 0 ? { model: args.model } : {},
|
|
29616
|
+
...args.channel !== void 0 ? { channel: args.channel } : {},
|
|
29317
29617
|
...args.aspect_ratio !== void 0 ? { aspect_ratio: args.aspect_ratio } : {},
|
|
29318
29618
|
...args.image_size !== void 0 ? { image_size: args.image_size } : {},
|
|
29319
29619
|
...args.size !== void 0 ? { size: args.size } : {},
|
|
@@ -29363,12 +29663,16 @@ function apply(ctx, config = {}) {
|
|
|
29363
29663
|
"grok-sub",
|
|
29364
29664
|
"google-sub"
|
|
29365
29665
|
],
|
|
29366
|
-
description: "
|
|
29666
|
+
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."
|
|
29367
29667
|
},
|
|
29368
29668
|
model: {
|
|
29369
29669
|
type: "string",
|
|
29370
29670
|
description: "Optional model name for this call only, overriding the configured model. Not used by ComfyUI (use workflow instead) nor by the subscription providers (model fixed by the subscription)."
|
|
29371
29671
|
},
|
|
29672
|
+
channel: {
|
|
29673
|
+
type: "string",
|
|
29674
|
+
description: "Optional configured OpenAI-compatible channel id for this call only. Only meaningful with provider=openai-compat; omit to use the active channel."
|
|
29675
|
+
},
|
|
29372
29676
|
source: {
|
|
29373
29677
|
type: "string",
|
|
29374
29678
|
enum: ["canvas_selection"],
|
|
@@ -29415,7 +29719,7 @@ function apply(ctx, config = {}) {
|
|
|
29415
29719
|
"2K",
|
|
29416
29720
|
"4K"
|
|
29417
29721
|
],
|
|
29418
|
-
description: "Optional output resolution for Google Gemini, xAI Grok, Grok subscription (1K/2K), or Google subscription (1K/4K)."
|
|
29722
|
+
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."
|
|
29419
29723
|
},
|
|
29420
29724
|
size: {
|
|
29421
29725
|
type: "string",
|
|
@@ -29431,8 +29735,10 @@ function apply(ctx, config = {}) {
|
|
|
29431
29735
|
}
|
|
29432
29736
|
},
|
|
29433
29737
|
output: imageOutput("Edited"),
|
|
29434
|
-
async execute(
|
|
29435
|
-
const
|
|
29738
|
+
async execute(rawArgs, exec) {
|
|
29739
|
+
const args = cleanImageToolArgs(rawArgs);
|
|
29740
|
+
const requestConfig = configForToolCall(current(), providerOverrideOf(args.provider), args.channel);
|
|
29741
|
+
const active = resolveProvider(withProviderOverrides(requestConfig, providerOverrideOf(args.provider), args.model, "edit"));
|
|
29436
29742
|
const canvasSelection = args.source === "canvas_selection";
|
|
29437
29743
|
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");
|
|
29438
29744
|
const sourceImages = canvasSelection ? await resolveCanvasSelectionReferences({
|
|
@@ -29486,7 +29792,7 @@ function apply(ctx, config = {}) {
|
|
|
29486
29792
|
signal: exec.signal
|
|
29487
29793
|
}), active.provider, active.model, "subscription edit", current(), exec, knownWorkspaceRoots);
|
|
29488
29794
|
}
|
|
29489
|
-
const credential = await requireApiKey(ctx, active.provider, "edit_image");
|
|
29795
|
+
const credential = await requireApiKey(ctx, active.provider, "edit_image", active.provider === "openai-compat" ? resolveOpenAICompatChannel(requestConfig, args.channel).keyRef : void 0);
|
|
29490
29796
|
if (active.provider === "google") {
|
|
29491
29797
|
const aspectRatio = args.aspect_ratio ?? active.aspectRatio;
|
|
29492
29798
|
const imageSize = args.image_size ?? active.imageSize;
|
|
@@ -29506,14 +29812,14 @@ function apply(ctx, config = {}) {
|
|
|
29506
29812
|
const size = args.size ?? active.imageSize;
|
|
29507
29813
|
if (active.provider === "openai" || active.provider === "openai-compat" || active.provider === "xai" || active.provider === "zhipu") {
|
|
29508
29814
|
if (active.provider === "openai") validateOfficialSize(active.model, size);
|
|
29509
|
-
const quality = active.provider === "openai-compat" ? compatRequestQuality(
|
|
29815
|
+
const quality = active.provider === "openai-compat" ? compatRequestQuality(requestConfig, args.quality) : active.provider === "openai" ? args.quality : void 0;
|
|
29510
29816
|
if (active.provider === "openai" && quality !== void 0 && !officialQualityOptions(active.model).some((option) => option.value === quality)) throw new Error("该 OpenAI 模型不支持所选生成质量");
|
|
29511
29817
|
const xaiExtraBody = active.provider === "xai" ? xaiToolParameters({
|
|
29512
29818
|
...args.aspect_ratio !== void 0 ? { aspectRatio: args.aspect_ratio } : {},
|
|
29513
29819
|
...args.image_size !== void 0 ? { imageSize: args.image_size } : {},
|
|
29514
29820
|
...args.size !== void 0 ? { size: args.size } : {}
|
|
29515
29821
|
}) : void 0;
|
|
29516
|
-
|
|
29822
|
+
const value = await saveGenerated(ctx, await editOpenAICompatibleImage({
|
|
29517
29823
|
apiKey: credential,
|
|
29518
29824
|
baseURL: active.baseURL,
|
|
29519
29825
|
model: active.model,
|
|
@@ -29526,12 +29832,16 @@ function apply(ctx, config = {}) {
|
|
|
29526
29832
|
...active.provider === "openai-compat" ? {
|
|
29527
29833
|
editFormat: active.editFormat,
|
|
29528
29834
|
editExtra: active.editExtra,
|
|
29529
|
-
editSizeMode:
|
|
29835
|
+
editSizeMode: requestConfig.openaiCompatEditSizeMode ?? "auto"
|
|
29530
29836
|
} : active.provider === "xai" ? {
|
|
29531
29837
|
editFormat: "xaiJson",
|
|
29532
29838
|
...xaiExtraBody === void 0 ? {} : { extraBody: xaiExtraBody }
|
|
29533
29839
|
} : {}
|
|
29534
29840
|
}), active.provider, active.model, active.provider === "xai" ? xaiExtraBody?.aspect_ratio ?? "auto" : quality === void 0 ? size : `${size}, quality=${quality}`, current(), exec, knownWorkspaceRoots);
|
|
29841
|
+
return active.provider === "openai-compat" ? {
|
|
29842
|
+
...value,
|
|
29843
|
+
channel: resolveOpenAICompatChannel(requestConfig, args.channel).id
|
|
29844
|
+
} : value;
|
|
29535
29845
|
}
|
|
29536
29846
|
if (active.provider === "seedream") return saveGenerated(ctx, await editSeedreamImage({
|
|
29537
29847
|
apiKey: credential,
|
|
@@ -29696,6 +30006,7 @@ function imageOutput(verb) {
|
|
|
29696
30006
|
type: "string",
|
|
29697
30007
|
required: true
|
|
29698
30008
|
},
|
|
30009
|
+
channel: { type: "string" },
|
|
29699
30010
|
model: {
|
|
29700
30011
|
type: "string",
|
|
29701
30012
|
required: true
|
|
@@ -29726,6 +30037,7 @@ function imageOutput(verb) {
|
|
|
29726
30037
|
provider: value.provider,
|
|
29727
30038
|
model: value.model,
|
|
29728
30039
|
output: value.output,
|
|
30040
|
+
...value.channel === void 0 ? {} : { channel: value.channel },
|
|
29729
30041
|
...verb === "Edited" ? { operation: "edit" } : {},
|
|
29730
30042
|
...typeof value.savedTo === "string" ? { savedTo: value.savedTo } : {},
|
|
29731
30043
|
...typeof value.seed === "number" ? { seed: value.seed } : {},
|