dsh-image-gen 0.9.3 → 0.10.1

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/lib/index.js CHANGED
@@ -5764,12 +5764,80 @@ const CLOUD_IMAGE_PROVIDERS = [
5764
5764
  "xai",
5765
5765
  "zhipu"
5766
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
+ }
5767
5833
  /**
5768
5834
  * Providers the browser workbench can drive: the BYOK cloud set plus the
5769
5835
  * logged-in subscription channels. ComfyUI stays out; it has its own workflow
5770
5836
  * pipeline and no shared request shape.
5771
5837
  */
5772
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;
5773
5841
  /** Timeout for one subscription image call, shared by the tool path and the
5774
5842
  * studio route so neither drifts from the other. */
5775
5843
  const SUBSCRIPTION_TIMEOUT_MS = 3e5;
@@ -5831,9 +5899,15 @@ const STUDIO_WATERMARK_VALUES = [
5831
5899
  "off",
5832
5900
  "on"
5833
5901
  ];
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;
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
+ }];
5837
5911
  }
5838
5912
  function studioWatermarkCapability(profile, mode) {
5839
5913
  return profile?.watermarkCapabilities?.[mode] ?? (mode === "generate" && profile?.watermarkOptions?.length ? { status: "supported" } : {
@@ -5961,14 +6035,39 @@ function volatile(schema) {
5961
6035
  */
5962
6036
  const Config = z.object({
5963
6037
  provider: volatile(z.union(IMAGE_PROVIDERS).default("google")),
6038
+ cloudTimeoutMs: volatile(z.number().min(5e3).max(36e5).default(DEFAULT_CLOUD_TIMEOUT_MS)),
5964
6039
  googleModel: volatile(z.string().default(DEFAULT_GOOGLE_MODEL)),
5965
6040
  googleEndpoint: volatile(z.string().default(DEFAULT_GOOGLE_ENDPOINT)),
5966
6041
  googleNative: volatile(z.boolean().default(false)),
5967
6042
  openaiBaseURL: volatile(z.string().default(DEFAULT_OPENAI_BASE_URL)),
5968
6043
  openaiModel: volatile(z.string().default(DEFAULT_OPENAI_MODEL)),
5969
6044
  openaiSizes: volatile(z.dict(z.dict(z.string())).default({})),
6045
+ openaiCompatLabel: volatile(z.string().default("")),
5970
6046
  openaiCompatBaseURL: volatile(z.string().default("")),
5971
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("")),
5972
6071
  openaiCompatEditModel: volatile(z.string().default("")),
5973
6072
  openaiCompatQuality: volatile(z.string().default("")),
5974
6073
  openaiCompatQualityOptions: volatile(z.array(z.string()).default([
@@ -6107,6 +6206,30 @@ function resolveProvider(config) {
6107
6206
  };
6108
6207
  }
6109
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
+ }
6110
6233
  /**
6111
6234
  * Apply a per-call provider and/or model override on top of the saved config.
6112
6235
  * `model` is ignored for ComfyUI, whose per-call equivalent is `workflow`.
@@ -6170,6 +6293,7 @@ function migrateOpenAICompatConfig(config) {
6170
6293
  ...config,
6171
6294
  openaiCompatBaseURL: legacyBase,
6172
6295
  ...compatModel.length > 0 ? {} : { openaiCompatModel: config.openaiModel },
6296
+ ...(config.openaiCompatModels?.length ?? 0) > 0 ? {} : { openaiCompatModels: [compatModel || config.openaiModel?.trim() || ""].filter(Boolean) },
6173
6297
  openaiBaseURL: DEFAULT_OPENAI_BASE_URL,
6174
6298
  ...config.provider === "openai" ? { provider: "openai-compat" } : {}
6175
6299
  };
@@ -6192,6 +6316,31 @@ function selectComfyUIWorkflow(active, requested) {
6192
6316
  return workflow;
6193
6317
  }
6194
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
6195
6344
  //#region node_modules/.pnpm/@deepseek-ai+dsh-credential_98fa173416f048996704a605a96a2b0e/node_modules/@deepseek-ai/dsh-credentials/lib/index.js
6196
6345
  /**
6197
6346
  * Service Definition for the credential-reference capability seam (`ctx.credentials`). Settings and composition files carry
@@ -6232,8 +6381,8 @@ function isCredentialRefName(value) {
6232
6381
  * never count as configured: the host treats an empty stored value as absent,
6233
6382
  * and a whitespace-only key is never sent to a provider.
6234
6383
  */
6235
- async function resolveApiKey(ctx, provider) {
6236
- 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() ?? "";
6237
6386
  return value.length > 0 ? value : void 0;
6238
6387
  }
6239
6388
  /**
@@ -6242,8 +6391,8 @@ async function resolveApiKey(ctx, provider) {
6242
6391
  * wording; without it the message is the browser-facing Chinese one used by
6243
6392
  * the Studio route.
6244
6393
  */
6245
- async function requireApiKey(ctx, provider, tool) {
6246
- const value = await resolveApiKey(ctx, provider);
6394
+ async function requireApiKey(ctx, provider, tool, keyRef) {
6395
+ const value = await resolveApiKey(ctx, provider, keyRef);
6247
6396
  if (value !== void 0) return value;
6248
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.`);
6249
6398
  }
@@ -27810,11 +27959,14 @@ function openAICompatStudioProfile(config, model, configured) {
27810
27959
  }
27811
27960
  /** Return only browser-safe capability data. */
27812
27961
  async function describeStudio(ctx, config, subscriptions) {
27813
- const configuredEntries = await Promise.all(CLOUD_IMAGE_PROVIDERS.map(async (provider) => {
27962
+ const compatChannels = resolveOpenAICompatChannels(config);
27963
+ const configuredEntries = await Promise.all([...CLOUD_IMAGE_PROVIDERS.filter((provider) => provider !== "openai-compat").map(async (provider) => {
27814
27964
  return [provider, await resolveApiKey(ctx, provider) !== void 0];
27815
- }));
27965
+ }), ...compatChannels.map(async (channel) => {
27966
+ return [`openai-compat:${channel.id}`, await resolveApiKey(ctx, "openai-compat", channel.keyRef) !== void 0];
27967
+ })]);
27816
27968
  const configured = Object.fromEntries(configuredEntries);
27817
- const cloudProfiles = CLOUD_IMAGE_PROVIDERS.map((provider) => studioProfile(config, provider, configured[provider]));
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)]);
27818
27970
  const subStatuses = await Promise.all(SUBSCRIPTION_PROVIDERS.map(async (provider) => {
27819
27971
  return [provider, subscriptions !== void 0 && (await subscriptions.loginStatus(vendorOf(provider))).state === "logged-in"];
27820
27972
  }));
@@ -27823,9 +27975,12 @@ async function describeStudio(ctx, config, subscriptions) {
27823
27975
  });
27824
27976
  const profiles = [...cloudProfiles, ...subProfiles];
27825
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;
27826
27980
  return {
27827
27981
  providers: profiles,
27828
- activeProvider: isStudioPreferred(preferred) ? preferred : profiles.find((profile) => profile.configured)?.provider ?? "google"
27982
+ activeProvider,
27983
+ ...activeChannel === void 0 ? {} : { activeChannel }
27829
27984
  };
27830
27985
  }
27831
27986
  /** The persisted default provider is usable directly when the workbench can drive it. */
@@ -27861,12 +28016,14 @@ async function generateFromStudio(ctx, config, input, signal, fallbackWorkspaceR
27861
28016
  assertAllowed(subscriptionStudioProfile(input.provider, true), input);
27862
28017
  return generateSubscriptionFromStudio(ctx, subscriptions, input, signal);
27863
28018
  }
27864
- assertAllowed(studioProfile(config, input.provider, true), input);
27865
- const active = resolveProvider(withProviderOverrides(config, input.provider, input.model));
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));
27866
28023
  if (active.provider === "comfyui") throw new Error("ComfyUI 暂未接入工作台");
27867
28024
  if (isSubscriptionProvider(active.provider)) throw new Error("订阅生图应通过订阅渠道执行");
27868
28025
  const wired = active;
27869
- const credential = await requireApiKey(ctx, input.provider);
28026
+ const credential = await requireApiKey(ctx, input.provider, void 0, channel?.keyRef);
27870
28027
  const rawRefs = input.references ?? (input.reference ? [input.reference] : []);
27871
28028
  if (input.mode === "edit" && rawRefs.length === 0) throw new Error("图生图需要至少一张参考图");
27872
28029
  const sourceImages = input.mode === "edit" ? await Promise.all(rawRefs.map((ref) => readStudioReference(ctx, ref, signal))) : [];
@@ -27903,16 +28060,16 @@ async function generateFromStudio(ctx, config, input, signal, fallbackWorkspaceR
27903
28060
  output = `${aspectRatio}, ${imageSize}`;
27904
28061
  } else if (wired.provider === "openai" || wired.provider === "openai-compat" || wired.provider === "xai" || wired.provider === "zhipu") {
27905
28062
  const p = wired.provider;
27906
- const officialTable = p === "openai" ? officialSizeTable(config, wired.model) : void 0;
28063
+ const officialTable = p === "openai" ? officialSizeTable(requestConfig, wired.model) : void 0;
27907
28064
  const officialResolution = input.resolution ?? (officialTable ? openAICompatTable({ openaiCompatSizes: officialTable }).find((group) => group.ratio === input.ratio)?.tiers[0]?.tier : void 0) ?? "1K";
27908
- const size = p === "openai-compat" ? openAIRequestSize(config, 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);
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);
27909
28066
  if (p === "openai" && size !== void 0) validateOfficialSize(wired.model, size);
27910
28067
  const requestedQuality = input.generationQuality ?? input.quality;
27911
- const quality = p === "openai" ? requestedQuality === "auto" ? void 0 : requestedQuality : p === "openai-compat" ? compatRequestQuality(config, input.generationQuality) : p === "zhipu" ? "hd" : void 0;
28068
+ const quality = p === "openai" ? requestedQuality === "auto" ? void 0 : requestedQuality : p === "openai-compat" ? compatRequestQuality(requestConfig, input.generationQuality) : p === "zhipu" ? "hd" : void 0;
27912
28069
  const extraBody = p === "xai" ? {
27913
28070
  aspect_ratio: input.ratio,
27914
28071
  resolution: input.quality
27915
- } : p === "openai-compat" ? input.mode === "generate" ? compatGenerationExtraBody(config, input.watermark) : compatEditExtraBody(config, input.watermark) : void 0;
28072
+ } : p === "openai-compat" ? input.mode === "generate" ? compatGenerationExtraBody(requestConfig, input.watermark) : compatEditExtraBody(requestConfig, input.watermark) : void 0;
27916
28073
  generated = input.mode === "edit" ? await editOpenAICompatibleImage({
27917
28074
  apiKey: credential,
27918
28075
  baseURL: wired.baseURL,
@@ -27927,7 +28084,7 @@ async function generateFromStudio(ctx, config, input, signal, fallbackWorkspaceR
27927
28084
  ...p === "openai-compat" ? {
27928
28085
  editFormat: wired.editFormat,
27929
28086
  editExtra: wired.editExtra,
27930
- editSizeMode: config.openaiCompatEditSizeMode ?? "auto"
28087
+ editSizeMode: requestConfig.openaiCompatEditSizeMode ?? "auto"
27931
28088
  } : p === "xai" ? { editFormat: "xaiJson" } : {}
27932
28089
  }) : await generateOpenAICompatibleImage({
27933
28090
  provider: p,
@@ -28012,6 +28169,7 @@ async function generateFromStudio(ctx, config, input, signal, fallbackWorkspaceR
28012
28169
  attachment: single.attachment,
28013
28170
  output: single.output,
28014
28171
  provider: input.provider,
28172
+ ...channel === void 0 ? {} : { channel: channel.id },
28015
28173
  model: input.model,
28016
28174
  prompt: input.prompt,
28017
28175
  createdAt: Date.now(),
@@ -28042,6 +28200,7 @@ async function generateFromStudio(ctx, config, input, signal, fallbackWorkspaceR
28042
28200
  attachment: first.attachment,
28043
28201
  output: first.output,
28044
28202
  provider: input.provider,
28203
+ ...channel === void 0 ? {} : { channel: channel.id },
28045
28204
  model: input.model,
28046
28205
  prompt: input.prompt,
28047
28206
  createdAt: Date.now(),
@@ -28165,13 +28324,19 @@ function subscriptionResponse(input, startedAt, requestedCount, failedCount, ite
28165
28324
  ...errors !== void 0 && errors.length > 0 ? { errors } : {}
28166
28325
  };
28167
28326
  }
28168
- function studioProfile(config, provider, configured) {
28169
- const result = cloudStudioProfile(config, provider, configured);
28170
- const editModel = provider === "openai-compat" ? config.openaiCompatEditModel?.trim() : void 0;
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;
28171
28332
  return {
28172
28333
  ...result,
28334
+ ...channel === void 0 ? {} : {
28335
+ channel: channel.id,
28336
+ label: channel.label
28337
+ },
28173
28338
  ...editModel ? { editModel } : {},
28174
- ...watermarkProfile(config, provider, result.model)
28339
+ ...watermarkProfile(scoped, provider, result.model)
28175
28340
  };
28176
28341
  }
28177
28342
  function cloudStudioProfile(config, provider, configured) {
@@ -28210,7 +28375,14 @@ function cloudStudioProfile(config, provider, configured) {
28210
28375
  /** Output capabilities remain visible before credentials, address or model are configured. */
28211
28376
  function compatibleStudioProfile(config, model, configured) {
28212
28377
  const groups = openAICompatTable(config);
28213
- const qualityProfile = {
28378
+ const capabilityProfile = {
28379
+ modelOptions: resolveOpenAICompatModels({
28380
+ ...config,
28381
+ openaiCompatModel: model
28382
+ }).map((value) => ({
28383
+ value,
28384
+ label: value
28385
+ })),
28214
28386
  generationQualityOptions: compatQualityOptions(config),
28215
28387
  defaultGenerationQuality: compatRequestQuality(config) ?? "__default__",
28216
28388
  resolutionOptions: Object.fromEntries(groups.map((group) => [group.ratio, group.tiers.map((entry) => ({
@@ -28227,7 +28399,7 @@ function compatibleStudioProfile(config, model, configured) {
28227
28399
  value: "standard",
28228
28400
  label: "标准(推荐)"
28229
28401
  }], "1:1", "standard"),
28230
- ...qualityProfile,
28402
+ ...capabilityProfile,
28231
28403
  resolutionOptions: Object.fromEntries(groups.map((group) => [group.ratio, [{
28232
28404
  value: "standard",
28233
28405
  label: `标准 · ${group.tiers[0].size}`
@@ -28235,7 +28407,7 @@ function compatibleStudioProfile(config, model, configured) {
28235
28407
  };
28236
28408
  return {
28237
28409
  ...openAICompatStudioProfile(config, model, configured),
28238
- ...qualityProfile
28410
+ ...capabilityProfile
28239
28411
  };
28240
28412
  }
28241
28413
  function profile(provider, model, configured, ratioOptions, qualityOptions, defaultRatio, defaultQuality) {
@@ -28268,7 +28440,7 @@ function option(value) {
28268
28440
  };
28269
28441
  }
28270
28442
  function assertAllowed(profile, input) {
28271
- if (input.model !== studioModelForMode(profile, input.mode)) throw new Error("模型配置已变化,请刷新工作台后重试");
28443
+ if (!studioModelOptionsForMode(profile, input.mode).some((option) => option.value === input.model)) throw new Error("模型配置已变化,请刷新工作台后重试");
28272
28444
  if (!profile.ratioOptions.some((option) => option.value === input.ratio)) throw new Error("该 Provider 不支持所选比例");
28273
28445
  const selectedQuality = input.provider === "openai" ? input.generationQuality ?? input.quality : input.resolution ?? input.quality;
28274
28446
  if (!profile.qualityOptions.some((option) => option.value === selectedQuality)) throw new Error("该 Provider 不支持所选清晰度");
@@ -28319,8 +28491,6 @@ async function serveStudio(req, res, deps) {
28319
28491
  if (!(req.headers["content-type"] ?? "").toLowerCase().startsWith("application/json")) return jsonError$1(res, 415, "json-required");
28320
28492
  const controller = new AbortController();
28321
28493
  const watchdog = new AbortController();
28322
- const CLOUD_WATCHDOG_MS = 15e4;
28323
- let subscriptionChannel = false;
28324
28494
  const signal = AbortSignal.any([controller.signal, watchdog.signal]);
28325
28495
  const onConnectionClose = () => {
28326
28496
  if (!res.writableEnded) controller.abort(/* @__PURE__ */ new Error("The browser closed the image generation request."));
@@ -28337,10 +28507,12 @@ async function serveStudio(req, res, deps) {
28337
28507
  } catch (error) {
28338
28508
  return jsonError$1(res, 400, errorMessage(error, "invalid-request"));
28339
28509
  }
28340
- subscriptionChannel = isSubscriptionProvider(input.provider);
28341
- setTimeout(() => {
28510
+ const subscriptionChannel = isSubscriptionProvider(input.provider);
28511
+ const timeoutMs = subscriptionChannel ? SUBSCRIPTION_TIMEOUT_MS : deps.timeoutMs ?? 15e4;
28512
+ const timeout = setTimeout(() => {
28342
28513
  watchdog.abort();
28343
- }, subscriptionChannel ? SUBSCRIPTION_TIMEOUT_MS : CLOUD_WATCHDOG_MS).unref?.();
28514
+ }, timeoutMs);
28515
+ timeout.unref?.();
28344
28516
  res.writeHead(200, {
28345
28517
  "content-type": "application/json; charset=utf-8",
28346
28518
  "cache-control": "no-store",
@@ -28355,11 +28527,12 @@ async function serveStudio(req, res, deps) {
28355
28527
  res.end(JSON.stringify(output));
28356
28528
  } catch (error) {
28357
28529
  if (res.destroyed || res.writableEnded) return;
28358
- const message = watchdog.signal.aborted ? subscriptionChannel ? "上游生成超时(订阅通道 300 秒未返回),请稍后重试" : "上游生成超时(150 秒未返回),请检查网络或代理后重试" : errorMessage(error, "generation-failed");
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");
28359
28531
  res.end(JSON.stringify({ error: message }));
28360
28532
  }
28361
28533
  } finally {
28362
28534
  clearInterval(heartbeat);
28535
+ clearTimeout(timeout);
28363
28536
  }
28364
28537
  } catch (error) {
28365
28538
  if (res.headersSent || res.writableEnded || res.destroyed) return;
@@ -28375,6 +28548,8 @@ function parseStudioGenerateRequest(value) {
28375
28548
  if (input === void 0) throw new Error("请求格式无效");
28376
28549
  if (input.mode !== "generate" && input.mode !== "edit") throw new Error("请选择生成类型");
28377
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);
28378
28553
  const prompt = requiredText(input.prompt, "请输入提示词");
28379
28554
  const model = requiredText(input.model, "请选择模型", 200);
28380
28555
  const ratio = requiredText(input.ratio, "请选择比例", 32);
@@ -28395,6 +28570,7 @@ function parseStudioGenerateRequest(value) {
28395
28570
  return {
28396
28571
  mode: input.mode,
28397
28572
  provider: input.provider,
28573
+ ...channel === void 0 ? {} : { channel },
28398
28574
  model,
28399
28575
  prompt,
28400
28576
  ratio,
@@ -28753,12 +28929,17 @@ async function serveTestConnection(req, res, deps) {
28753
28929
  const provider = record(body)?.provider;
28754
28930
  if (typeof provider !== "string" || !IMAGE_PROVIDERS.includes(provider)) return jsonError(res, 400, "invalid-provider");
28755
28931
  const active = provider;
28756
- if (record(body)?.action === "models") {
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") {
28757
28938
  if (active === "comfyui" || isSubscriptionProvider(active)) return jsonError(res, 400, "models-unsupported");
28758
28939
  let result;
28759
28940
  try {
28760
- const apiKey = await deps.resolveKey(active);
28761
- result = active === "google" ? await fetchGoogleImageModels(deps.config(), apiKey) : active === "dashscope" ? await fetchDashScopeImageModels(deps.config(), apiKey) : await fetchOpenAIImageModels(active, deps.config(), apiKey);
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);
28762
28943
  } catch (error) {
28763
28944
  result = {
28764
28945
  ok: false,
@@ -28773,8 +28954,8 @@ async function serveTestConnection(req, res, deps) {
28773
28954
  if (active === "comfyui") result = await probeComfyUIConnection(deps.config());
28774
28955
  else if (isSubscriptionProvider(active)) result = await probeSubscriptionConnection(active, deps.subscriptionManager);
28775
28956
  else {
28776
- const apiKey = await deps.resolveKey(active);
28777
- result = await probeProviderConnection(active, deps.config(), apiKey);
28957
+ const apiKey = await deps.resolveKey(active, channel);
28958
+ result = await probeProviderConnection(active, requestConfig(), apiKey);
28778
28959
  }
28779
28960
  } catch (error) {
28780
28961
  result = {
@@ -28840,8 +29021,10 @@ function serveSettingsHealth(req, res, health) {
28840
29021
  //#endregion
28841
29022
  //#region lib/types/image-tool-context.js
28842
29023
  /** Live, credential-free instructions for the conversation model's image tools. */
28843
- function imageToolContext(config) {
29024
+ function imageToolContext(config, configuredProviders) {
28844
29025
  const provider = config.provider ?? "google";
29026
+ const configured = configuredProviders === void 0 ? void 0 : [...configuredProviders];
29027
+ if (configured !== void 0 && !configured.includes("comfyui") && resolveComfyUIWorkflows(config).length > 0) configured.push("comfyui");
28845
29028
  let model = "not configured";
28846
29029
  let editModel = model;
28847
29030
  try {
@@ -28852,6 +29035,7 @@ function imageToolContext(config) {
28852
29035
  } catch {}
28853
29036
  return [
28854
29037
  `Image tools current default provider: ${provider}; generation model: ${JSON.stringify(model)}; editing model: ${JSON.stringify(editModel)}.`,
29038
+ ...configured === void 0 ? [] : [`Configured image providers (credential saved, subscription logged in, or ComfyUI workflow present): ${configured.length > 0 ? configured.join(", ") : "none"}. Do not select unlisted providers; configuration presence does not guarantee service reachability.`],
28855
29039
  "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
29040
  "Omit unused optional arguments entirely; never fill them with empty strings. size and aspect_ratio are alternatives: provide at most one.",
28857
29041
  "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.",
@@ -28872,6 +29056,7 @@ function cleanImageToolArgs(args) {
28872
29056
  const fields = clean;
28873
29057
  for (const key of [
28874
29058
  "model",
29059
+ "channel",
28875
29060
  "size",
28876
29061
  "aspect_ratio",
28877
29062
  "image_size",
@@ -28891,6 +29076,11 @@ function providerOverrideOf(value) {
28891
29076
  if (typeof value !== "string" || !IMAGE_PROVIDERS.includes(value)) throw new Error(`Unsupported provider ${JSON.stringify(value)}. Supported providers: ${IMAGE_PROVIDERS.join(", ")}.`);
28892
29077
  return value;
28893
29078
  }
29079
+ function configForToolCall(config, provider, channel) {
29080
+ const selected = provider ?? config.provider ?? "google";
29081
+ if (channel !== void 0 && selected !== "openai-compat") throw new Error("The channel argument is only supported with provider=openai-compat.");
29082
+ return selected === "openai-compat" ? withOpenAICompatChannel(config, channel) : config;
29083
+ }
28894
29084
  function apply(ctx, config = {}) {
28895
29085
  const plainConfig = (source) => {
28896
29086
  const out = {};
@@ -28901,11 +29091,23 @@ function apply(ctx, config = {}) {
28901
29091
  const knownWorkspaceRoots = /* @__PURE__ */ new Set();
28902
29092
  const canvasMirror = new CanvasMirror();
28903
29093
  const subscriptionManager = new SubscriptionManager(ctx);
29094
+ let configuredImageProviders;
29095
+ let imageContextRevision = 0;
29096
+ const refreshImageToolContext = async () => {
29097
+ const revision = ++imageContextRevision;
29098
+ const config = current();
29099
+ try {
29100
+ const providers = (await describeStudio(ctx, config, subscriptionManager)).providers.filter((profile) => profile.configured).map((profile) => profile.provider === "openai-compat" && profile.channel !== void 0 ? `${profile.provider} (channel: ${profile.channel})` : profile.provider);
29101
+ if (revision === imageContextRevision) configuredImageProviders = providers;
29102
+ } catch {}
29103
+ };
28904
29104
  const settingsHealth = installImageSettings(ctx, current(), {
28905
29105
  setSource: (source) => {
28906
29106
  current = () => migrateOpenAICompatConfig(plainConfig(source()));
28907
29107
  },
28908
- onChange: () => {}
29108
+ onChange: () => {
29109
+ refreshImageToolContext();
29110
+ }
28909
29111
  });
28910
29112
  ctx.inject(["webServer"], (webCtx) => {
28911
29113
  registerSubscriptionRoutes(webCtx, subscriptionManager);
@@ -28971,7 +29173,7 @@ function apply(ctx, config = {}) {
28971
29173
  kind: "exact",
28972
29174
  path: TEST_CONNECTION_ROUTE,
28973
29175
  handler: (req, res) => serveTestConnection(req, res, {
28974
- resolveKey: (provider) => resolveApiKey(ctx, provider),
29176
+ resolveKey: (provider, channel) => resolveApiKey(ctx, provider, provider === "openai-compat" ? resolveOpenAICompatChannel(current(), channel).keyRef : void 0),
28975
29177
  config: () => current(),
28976
29178
  subscriptionManager
28977
29179
  })
@@ -28995,10 +29197,19 @@ function apply(ctx, config = {}) {
28995
29197
  }), "dsh-image-gen: canvas asset route");
28996
29198
  });
28997
29199
  ctx.inject(["systemPrompt"], (promptCtx) => {
29200
+ refreshImageToolContext();
29201
+ ctx.inject(["credentials"], (credentialCtx) => {
29202
+ credentialCtx.on("credentials/reference-updated", () => {
29203
+ refreshImageToolContext();
29204
+ });
29205
+ credentialCtx.on("credentials/record-updated", () => {
29206
+ refreshImageToolContext();
29207
+ });
29208
+ });
28998
29209
  promptCtx.effect(() => promptCtx.systemPrompt.context({
28999
29210
  name: "dsh-image-gen:image-defaults",
29000
29211
  order: 59,
29001
- text: () => imageToolContext(current())
29212
+ text: () => imageToolContext(current(), configuredImageProviders)
29002
29213
  }), "dsh-image-gen: image tool context");
29003
29214
  promptCtx.effect(() => promptCtx.systemPrompt.context({
29004
29215
  name: "dsh-image-gen:canvas",
@@ -29030,7 +29241,8 @@ function apply(ctx, config = {}) {
29030
29241
  const fallbackRoot = Array.from(knownWorkspaceRoots)[0] || process.cwd();
29031
29242
  return generateFromStudio(ctx, current(), input, signal, fallbackRoot, subscriptionManager);
29032
29243
  },
29033
- maxBodyBytes: Math.ceil(ctx.attachments.imageLimits.maxImageBytes * 1.4 * 5) + 262144
29244
+ maxBodyBytes: Math.ceil(ctx.attachments.imageLimits.maxImageBytes * 1.4 * 5) + 262144,
29245
+ timeoutMs: current().cloudTimeoutMs ?? 15e4
29034
29246
  })
29035
29247
  }), "dsh-image-gen: studio route");
29036
29248
  const serveInspiration = createInspirationRoute();
@@ -29047,7 +29259,8 @@ function apply(ctx, config = {}) {
29047
29259
  /** Per-item generation request shared by generate_image and generate_images. */
29048
29260
  const generateSingle = async (rawArgs, exec) => {
29049
29261
  const args = cleanImageToolArgs(rawArgs);
29050
- const active = resolveProvider(withProviderOverrides(current(), providerOverrideOf(args.provider), args.model));
29262
+ const requestConfig = configForToolCall(current(), providerOverrideOf(args.provider), args.channel);
29263
+ const active = resolveProvider(withProviderOverrides(requestConfig, providerOverrideOf(args.provider), args.model));
29051
29264
  if (active.provider === "comfyui") {
29052
29265
  const workflow = selectComfyUIWorkflow(active, args.workflow);
29053
29266
  return saveGenerated(ctx, await generateComfyUIImage({
@@ -29074,11 +29287,12 @@ function apply(ctx, config = {}) {
29074
29287
  signal: exec.signal
29075
29288
  }), active.provider, active.model, "subscription", current(), exec, knownWorkspaceRoots);
29076
29289
  }
29077
- const credential = await requireApiKey(ctx, active.provider, "generate_image");
29290
+ const runCloud = (operation) => withGenerationTimeout(current().cloudTimeoutMs ?? 15e4, exec.signal, operation);
29291
+ const credential = await requireApiKey(ctx, active.provider, "generate_image", active.provider === "openai-compat" ? resolveOpenAICompatChannel(requestConfig, args.channel).keyRef : void 0);
29078
29292
  if (active.provider === "google") {
29079
29293
  const aspectRatio = args.aspect_ratio ?? active.aspectRatio;
29080
29294
  const imageSize = args.image_size ?? active.imageSize;
29081
- return saveGenerated(ctx, await generateGoogleImage({
29295
+ return saveGenerated(ctx, await runCloud((signal) => generateGoogleImage({
29082
29296
  apiKey: credential,
29083
29297
  endpoint: active.endpoint,
29084
29298
  native: active.native,
@@ -29087,20 +29301,20 @@ function apply(ctx, config = {}) {
29087
29301
  aspectRatio,
29088
29302
  imageSize,
29089
29303
  maxBytes: ctx.attachments.imageLimits.maxImageBytes,
29090
- signal: exec.signal
29091
- }), active.provider, active.model, `${aspectRatio}, ${imageSize}`, current(), exec, knownWorkspaceRoots);
29304
+ signal
29305
+ })), active.provider, active.model, `${aspectRatio}, ${imageSize}`, current(), exec, knownWorkspaceRoots);
29092
29306
  }
29093
29307
  if (active.provider === "dashscope") {
29094
29308
  const size = args.size ?? active.imageSize;
29095
- return saveGenerated(ctx, await generateDashScopeImage({
29309
+ return saveGenerated(ctx, await runCloud((signal) => generateDashScopeImage({
29096
29310
  apiKey: credential,
29097
29311
  endpoint: active.endpoint,
29098
29312
  model: active.model,
29099
29313
  prompt: args.prompt,
29100
29314
  size,
29101
29315
  maxBytes: ctx.attachments.imageLimits.maxImageBytes,
29102
- signal: exec.signal
29103
- }), active.provider, active.model, size, current(), exec, knownWorkspaceRoots);
29316
+ signal
29317
+ })), active.provider, active.model, size, current(), exec, knownWorkspaceRoots);
29104
29318
  }
29105
29319
  if (active.provider === "xai") {
29106
29320
  const extraBody = xaiToolParameters({
@@ -29108,7 +29322,7 @@ function apply(ctx, config = {}) {
29108
29322
  ...args.image_size !== void 0 ? { imageSize: args.image_size } : {},
29109
29323
  ...args.size !== void 0 ? { size: args.size } : {}
29110
29324
  });
29111
- return saveGenerated(ctx, await generateOpenAICompatibleImage({
29325
+ return saveGenerated(ctx, await runCloud((signal) => generateOpenAICompatibleImage({
29112
29326
  provider: "xai",
29113
29327
  apiKey: credential,
29114
29328
  baseURL: active.baseURL,
@@ -29116,15 +29330,15 @@ function apply(ctx, config = {}) {
29116
29330
  prompt: args.prompt,
29117
29331
  extraBody,
29118
29332
  maxBytes: ctx.attachments.imageLimits.maxImageBytes,
29119
- signal: exec.signal
29120
- }), active.provider, active.model, extraBody.aspect_ratio ?? "auto", current(), exec, knownWorkspaceRoots);
29333
+ signal
29334
+ })), active.provider, active.model, extraBody.aspect_ratio ?? "auto", current(), exec, knownWorkspaceRoots);
29121
29335
  }
29122
29336
  const size = args.size ?? active.imageSize;
29123
29337
  const arkOptions = active.provider === "seedream" ? active.arkOptions : void 0;
29124
29338
  if (active.provider === "openai") validateOfficialSize(active.model, size);
29125
- const quality = active.provider === "openai-compat" ? compatRequestQuality(current(), args.quality) : active.provider === "openai" ? args.quality : void 0;
29339
+ const quality = active.provider === "openai-compat" ? compatRequestQuality(requestConfig, args.quality) : active.provider === "openai" ? args.quality : void 0;
29126
29340
  if (active.provider === "openai" && quality !== void 0 && !officialQualityOptions(active.model).some((option) => option.value === quality)) throw new Error("该 OpenAI 模型不支持所选生成质量");
29127
- return saveGenerated(ctx, await generateOpenAICompatibleImage({
29341
+ const value = await saveGenerated(ctx, await runCloud((signal) => generateOpenAICompatibleImage({
29128
29342
  provider: active.provider,
29129
29343
  apiKey: credential,
29130
29344
  baseURL: active.baseURL,
@@ -29132,11 +29346,15 @@ function apply(ctx, config = {}) {
29132
29346
  prompt: args.prompt,
29133
29347
  size,
29134
29348
  maxBytes: ctx.attachments.imageLimits.maxImageBytes,
29135
- signal: exec.signal,
29349
+ signal,
29136
29350
  ...quality === void 0 ? {} : { quality },
29137
- ...active.provider === "openai-compat" ? { extraBody: current().openaiCompatExtraBody ?? {} } : {},
29351
+ ...active.provider === "openai-compat" ? { extraBody: requestConfig.openaiCompatExtraBody ?? {} } : {},
29138
29352
  ...arkOptions === void 0 ? {} : { arkOptions }
29139
- }), active.provider, active.model, quality === void 0 ? size : `${size}, quality=${quality}`, current(), exec, knownWorkspaceRoots);
29353
+ })), active.provider, active.model, quality === void 0 ? size : `${size}, quality=${quality}`, current(), exec, knownWorkspaceRoots);
29354
+ return active.provider === "openai-compat" ? {
29355
+ ...value,
29356
+ channel: resolveOpenAICompatChannel(requestConfig, args.channel).id
29357
+ } : value;
29140
29358
  };
29141
29359
  ctx.tools.register(defineTool({
29142
29360
  name: "generate_image",
@@ -29166,7 +29384,11 @@ function apply(ctx, config = {}) {
29166
29384
  },
29167
29385
  model: {
29168
29386
  type: "string",
29169
- 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)."
29387
+ description: "OMIT unless the user explicitly requests a specific model; omission uses the configured model. Optional override for this call only. Not used by ComfyUI (use workflow instead) nor by the subscription providers (model fixed by the subscription)."
29388
+ },
29389
+ channel: {
29390
+ type: "string",
29391
+ description: "Optional configured OpenAI-compatible channel id for this call only. Only meaningful with provider=openai-compat; omit to use the active channel."
29170
29392
  },
29171
29393
  aspect_ratio: {
29172
29394
  type: "string",
@@ -29241,7 +29463,11 @@ function apply(ctx, config = {}) {
29241
29463
  },
29242
29464
  model: {
29243
29465
  type: "string",
29244
- description: "Optional model name for this call only, applied to every item."
29466
+ description: "OMIT unless the user explicitly requests a specific model for every item; omission uses the configured model."
29467
+ },
29468
+ channel: {
29469
+ type: "string",
29470
+ description: "Optional configured OpenAI-compatible channel id applied to every item; omit to use the active channel."
29245
29471
  },
29246
29472
  aspect_ratio: {
29247
29473
  type: "string",
@@ -29338,6 +29564,7 @@ function apply(ctx, config = {}) {
29338
29564
  type: "string",
29339
29565
  required: true
29340
29566
  },
29567
+ channel: { type: "string" },
29341
29568
  model: {
29342
29569
  type: "string",
29343
29570
  required: true
@@ -29410,6 +29637,7 @@ function apply(ctx, config = {}) {
29410
29637
  prompt,
29411
29638
  ...args.provider !== void 0 ? { provider: args.provider } : {},
29412
29639
  ...args.model !== void 0 ? { model: args.model } : {},
29640
+ ...args.channel !== void 0 ? { channel: args.channel } : {},
29413
29641
  ...args.aspect_ratio !== void 0 ? { aspect_ratio: args.aspect_ratio } : {},
29414
29642
  ...args.image_size !== void 0 ? { image_size: args.image_size } : {},
29415
29643
  ...args.size !== void 0 ? { size: args.size } : {},
@@ -29463,7 +29691,11 @@ function apply(ctx, config = {}) {
29463
29691
  },
29464
29692
  model: {
29465
29693
  type: "string",
29466
- 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)."
29694
+ description: "OMIT unless the user explicitly requests a specific model; omission uses the configured model. Optional override for this call only. Not used by ComfyUI (use workflow instead) nor by the subscription providers (model fixed by the subscription)."
29695
+ },
29696
+ channel: {
29697
+ type: "string",
29698
+ description: "Optional configured OpenAI-compatible channel id for this call only. Only meaningful with provider=openai-compat; omit to use the active channel."
29467
29699
  },
29468
29700
  source: {
29469
29701
  type: "string",
@@ -29529,7 +29761,8 @@ function apply(ctx, config = {}) {
29529
29761
  output: imageOutput("Edited"),
29530
29762
  async execute(rawArgs, exec) {
29531
29763
  const args = cleanImageToolArgs(rawArgs);
29532
- const active = resolveProvider(withProviderOverrides(current(), providerOverrideOf(args.provider), args.model, "edit"));
29764
+ const requestConfig = configForToolCall(current(), providerOverrideOf(args.provider), args.channel);
29765
+ const active = resolveProvider(withProviderOverrides(requestConfig, providerOverrideOf(args.provider), args.model, "edit"));
29533
29766
  const canvasSelection = args.source === "canvas_selection";
29534
29767
  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");
29535
29768
  const sourceImages = canvasSelection ? await resolveCanvasSelectionReferences({
@@ -29583,7 +29816,7 @@ function apply(ctx, config = {}) {
29583
29816
  signal: exec.signal
29584
29817
  }), active.provider, active.model, "subscription edit", current(), exec, knownWorkspaceRoots);
29585
29818
  }
29586
- const credential = await requireApiKey(ctx, active.provider, "edit_image");
29819
+ const credential = await requireApiKey(ctx, active.provider, "edit_image", active.provider === "openai-compat" ? resolveOpenAICompatChannel(requestConfig, args.channel).keyRef : void 0);
29587
29820
  if (active.provider === "google") {
29588
29821
  const aspectRatio = args.aspect_ratio ?? active.aspectRatio;
29589
29822
  const imageSize = args.image_size ?? active.imageSize;
@@ -29603,14 +29836,14 @@ function apply(ctx, config = {}) {
29603
29836
  const size = args.size ?? active.imageSize;
29604
29837
  if (active.provider === "openai" || active.provider === "openai-compat" || active.provider === "xai" || active.provider === "zhipu") {
29605
29838
  if (active.provider === "openai") validateOfficialSize(active.model, size);
29606
- const quality = active.provider === "openai-compat" ? compatRequestQuality(current(), args.quality) : active.provider === "openai" ? args.quality : void 0;
29839
+ const quality = active.provider === "openai-compat" ? compatRequestQuality(requestConfig, args.quality) : active.provider === "openai" ? args.quality : void 0;
29607
29840
  if (active.provider === "openai" && quality !== void 0 && !officialQualityOptions(active.model).some((option) => option.value === quality)) throw new Error("该 OpenAI 模型不支持所选生成质量");
29608
29841
  const xaiExtraBody = active.provider === "xai" ? xaiToolParameters({
29609
29842
  ...args.aspect_ratio !== void 0 ? { aspectRatio: args.aspect_ratio } : {},
29610
29843
  ...args.image_size !== void 0 ? { imageSize: args.image_size } : {},
29611
29844
  ...args.size !== void 0 ? { size: args.size } : {}
29612
29845
  }) : void 0;
29613
- return saveGenerated(ctx, await editOpenAICompatibleImage({
29846
+ const value = await saveGenerated(ctx, await editOpenAICompatibleImage({
29614
29847
  apiKey: credential,
29615
29848
  baseURL: active.baseURL,
29616
29849
  model: active.model,
@@ -29623,12 +29856,16 @@ function apply(ctx, config = {}) {
29623
29856
  ...active.provider === "openai-compat" ? {
29624
29857
  editFormat: active.editFormat,
29625
29858
  editExtra: active.editExtra,
29626
- editSizeMode: current().openaiCompatEditSizeMode ?? "auto"
29859
+ editSizeMode: requestConfig.openaiCompatEditSizeMode ?? "auto"
29627
29860
  } : active.provider === "xai" ? {
29628
29861
  editFormat: "xaiJson",
29629
29862
  ...xaiExtraBody === void 0 ? {} : { extraBody: xaiExtraBody }
29630
29863
  } : {}
29631
29864
  }), active.provider, active.model, active.provider === "xai" ? xaiExtraBody?.aspect_ratio ?? "auto" : quality === void 0 ? size : `${size}, quality=${quality}`, current(), exec, knownWorkspaceRoots);
29865
+ return active.provider === "openai-compat" ? {
29866
+ ...value,
29867
+ channel: resolveOpenAICompatChannel(requestConfig, args.channel).id
29868
+ } : value;
29632
29869
  }
29633
29870
  if (active.provider === "seedream") return saveGenerated(ctx, await editSeedreamImage({
29634
29871
  apiKey: credential,
@@ -29793,6 +30030,7 @@ function imageOutput(verb) {
29793
30030
  type: "string",
29794
30031
  required: true
29795
30032
  },
30033
+ channel: { type: "string" },
29796
30034
  model: {
29797
30035
  type: "string",
29798
30036
  required: true
@@ -29823,6 +30061,7 @@ function imageOutput(verb) {
29823
30061
  provider: value.provider,
29824
30062
  model: value.model,
29825
30063
  output: value.output,
30064
+ ...value.channel === void 0 ? {} : { channel: value.channel },
29826
30065
  ...verb === "Edited" ? { operation: "edit" } : {},
29827
30066
  ...typeof value.savedTo === "string" ? { savedTo: value.savedTo } : {},
29828
30067
  ...typeof value.seed === "number" ? { seed: value.seed } : {},