dsh-image-gen 0.9.3 → 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/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 = {
@@ -28872,6 +29053,7 @@ function cleanImageToolArgs(args) {
28872
29053
  const fields = clean;
28873
29054
  for (const key of [
28874
29055
  "model",
29056
+ "channel",
28875
29057
  "size",
28876
29058
  "aspect_ratio",
28877
29059
  "image_size",
@@ -28891,6 +29073,11 @@ function providerOverrideOf(value) {
28891
29073
  if (typeof value !== "string" || !IMAGE_PROVIDERS.includes(value)) throw new Error(`Unsupported provider ${JSON.stringify(value)}. Supported providers: ${IMAGE_PROVIDERS.join(", ")}.`);
28892
29074
  return value;
28893
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
+ }
28894
29081
  function apply(ctx, config = {}) {
28895
29082
  const plainConfig = (source) => {
28896
29083
  const out = {};
@@ -28971,7 +29158,7 @@ function apply(ctx, config = {}) {
28971
29158
  kind: "exact",
28972
29159
  path: TEST_CONNECTION_ROUTE,
28973
29160
  handler: (req, res) => serveTestConnection(req, res, {
28974
- resolveKey: (provider) => resolveApiKey(ctx, provider),
29161
+ resolveKey: (provider, channel) => resolveApiKey(ctx, provider, provider === "openai-compat" ? resolveOpenAICompatChannel(current(), channel).keyRef : void 0),
28975
29162
  config: () => current(),
28976
29163
  subscriptionManager
28977
29164
  })
@@ -29030,7 +29217,8 @@ function apply(ctx, config = {}) {
29030
29217
  const fallbackRoot = Array.from(knownWorkspaceRoots)[0] || process.cwd();
29031
29218
  return generateFromStudio(ctx, current(), input, signal, fallbackRoot, subscriptionManager);
29032
29219
  },
29033
- maxBodyBytes: Math.ceil(ctx.attachments.imageLimits.maxImageBytes * 1.4 * 5) + 262144
29220
+ maxBodyBytes: Math.ceil(ctx.attachments.imageLimits.maxImageBytes * 1.4 * 5) + 262144,
29221
+ timeoutMs: current().cloudTimeoutMs ?? 15e4
29034
29222
  })
29035
29223
  }), "dsh-image-gen: studio route");
29036
29224
  const serveInspiration = createInspirationRoute();
@@ -29047,7 +29235,8 @@ function apply(ctx, config = {}) {
29047
29235
  /** Per-item generation request shared by generate_image and generate_images. */
29048
29236
  const generateSingle = async (rawArgs, exec) => {
29049
29237
  const args = cleanImageToolArgs(rawArgs);
29050
- const active = resolveProvider(withProviderOverrides(current(), providerOverrideOf(args.provider), args.model));
29238
+ const requestConfig = configForToolCall(current(), providerOverrideOf(args.provider), args.channel);
29239
+ const active = resolveProvider(withProviderOverrides(requestConfig, providerOverrideOf(args.provider), args.model));
29051
29240
  if (active.provider === "comfyui") {
29052
29241
  const workflow = selectComfyUIWorkflow(active, args.workflow);
29053
29242
  return saveGenerated(ctx, await generateComfyUIImage({
@@ -29074,11 +29263,12 @@ function apply(ctx, config = {}) {
29074
29263
  signal: exec.signal
29075
29264
  }), active.provider, active.model, "subscription", current(), exec, knownWorkspaceRoots);
29076
29265
  }
29077
- const credential = await requireApiKey(ctx, active.provider, "generate_image");
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);
29078
29268
  if (active.provider === "google") {
29079
29269
  const aspectRatio = args.aspect_ratio ?? active.aspectRatio;
29080
29270
  const imageSize = args.image_size ?? active.imageSize;
29081
- return saveGenerated(ctx, await generateGoogleImage({
29271
+ return saveGenerated(ctx, await runCloud((signal) => generateGoogleImage({
29082
29272
  apiKey: credential,
29083
29273
  endpoint: active.endpoint,
29084
29274
  native: active.native,
@@ -29087,20 +29277,20 @@ function apply(ctx, config = {}) {
29087
29277
  aspectRatio,
29088
29278
  imageSize,
29089
29279
  maxBytes: ctx.attachments.imageLimits.maxImageBytes,
29090
- signal: exec.signal
29091
- }), active.provider, active.model, `${aspectRatio}, ${imageSize}`, current(), exec, knownWorkspaceRoots);
29280
+ signal
29281
+ })), active.provider, active.model, `${aspectRatio}, ${imageSize}`, current(), exec, knownWorkspaceRoots);
29092
29282
  }
29093
29283
  if (active.provider === "dashscope") {
29094
29284
  const size = args.size ?? active.imageSize;
29095
- return saveGenerated(ctx, await generateDashScopeImage({
29285
+ return saveGenerated(ctx, await runCloud((signal) => generateDashScopeImage({
29096
29286
  apiKey: credential,
29097
29287
  endpoint: active.endpoint,
29098
29288
  model: active.model,
29099
29289
  prompt: args.prompt,
29100
29290
  size,
29101
29291
  maxBytes: ctx.attachments.imageLimits.maxImageBytes,
29102
- signal: exec.signal
29103
- }), active.provider, active.model, size, current(), exec, knownWorkspaceRoots);
29292
+ signal
29293
+ })), active.provider, active.model, size, current(), exec, knownWorkspaceRoots);
29104
29294
  }
29105
29295
  if (active.provider === "xai") {
29106
29296
  const extraBody = xaiToolParameters({
@@ -29108,7 +29298,7 @@ function apply(ctx, config = {}) {
29108
29298
  ...args.image_size !== void 0 ? { imageSize: args.image_size } : {},
29109
29299
  ...args.size !== void 0 ? { size: args.size } : {}
29110
29300
  });
29111
- return saveGenerated(ctx, await generateOpenAICompatibleImage({
29301
+ return saveGenerated(ctx, await runCloud((signal) => generateOpenAICompatibleImage({
29112
29302
  provider: "xai",
29113
29303
  apiKey: credential,
29114
29304
  baseURL: active.baseURL,
@@ -29116,15 +29306,15 @@ function apply(ctx, config = {}) {
29116
29306
  prompt: args.prompt,
29117
29307
  extraBody,
29118
29308
  maxBytes: ctx.attachments.imageLimits.maxImageBytes,
29119
- signal: exec.signal
29120
- }), 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);
29121
29311
  }
29122
29312
  const size = args.size ?? active.imageSize;
29123
29313
  const arkOptions = active.provider === "seedream" ? active.arkOptions : void 0;
29124
29314
  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;
29315
+ const quality = active.provider === "openai-compat" ? compatRequestQuality(requestConfig, args.quality) : active.provider === "openai" ? args.quality : void 0;
29126
29316
  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({
29317
+ const value = await saveGenerated(ctx, await runCloud((signal) => generateOpenAICompatibleImage({
29128
29318
  provider: active.provider,
29129
29319
  apiKey: credential,
29130
29320
  baseURL: active.baseURL,
@@ -29132,11 +29322,15 @@ function apply(ctx, config = {}) {
29132
29322
  prompt: args.prompt,
29133
29323
  size,
29134
29324
  maxBytes: ctx.attachments.imageLimits.maxImageBytes,
29135
- signal: exec.signal,
29325
+ signal,
29136
29326
  ...quality === void 0 ? {} : { quality },
29137
- ...active.provider === "openai-compat" ? { extraBody: current().openaiCompatExtraBody ?? {} } : {},
29327
+ ...active.provider === "openai-compat" ? { extraBody: requestConfig.openaiCompatExtraBody ?? {} } : {},
29138
29328
  ...arkOptions === void 0 ? {} : { arkOptions }
29139
- }), 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;
29140
29334
  };
29141
29335
  ctx.tools.register(defineTool({
29142
29336
  name: "generate_image",
@@ -29168,6 +29362,10 @@ function apply(ctx, config = {}) {
29168
29362
  type: "string",
29169
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)."
29170
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
+ },
29171
29369
  aspect_ratio: {
29172
29370
  type: "string",
29173
29371
  enum: [
@@ -29243,6 +29441,10 @@ function apply(ctx, config = {}) {
29243
29441
  type: "string",
29244
29442
  description: "Optional model name for this call only, applied to every item."
29245
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
+ },
29246
29448
  aspect_ratio: {
29247
29449
  type: "string",
29248
29450
  enum: [
@@ -29338,6 +29540,7 @@ function apply(ctx, config = {}) {
29338
29540
  type: "string",
29339
29541
  required: true
29340
29542
  },
29543
+ channel: { type: "string" },
29341
29544
  model: {
29342
29545
  type: "string",
29343
29546
  required: true
@@ -29410,6 +29613,7 @@ function apply(ctx, config = {}) {
29410
29613
  prompt,
29411
29614
  ...args.provider !== void 0 ? { provider: args.provider } : {},
29412
29615
  ...args.model !== void 0 ? { model: args.model } : {},
29616
+ ...args.channel !== void 0 ? { channel: args.channel } : {},
29413
29617
  ...args.aspect_ratio !== void 0 ? { aspect_ratio: args.aspect_ratio } : {},
29414
29618
  ...args.image_size !== void 0 ? { image_size: args.image_size } : {},
29415
29619
  ...args.size !== void 0 ? { size: args.size } : {},
@@ -29465,6 +29669,10 @@ function apply(ctx, config = {}) {
29465
29669
  type: "string",
29466
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)."
29467
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
+ },
29468
29676
  source: {
29469
29677
  type: "string",
29470
29678
  enum: ["canvas_selection"],
@@ -29529,7 +29737,8 @@ function apply(ctx, config = {}) {
29529
29737
  output: imageOutput("Edited"),
29530
29738
  async execute(rawArgs, exec) {
29531
29739
  const args = cleanImageToolArgs(rawArgs);
29532
- const active = resolveProvider(withProviderOverrides(current(), providerOverrideOf(args.provider), args.model, "edit"));
29740
+ const requestConfig = configForToolCall(current(), providerOverrideOf(args.provider), args.channel);
29741
+ const active = resolveProvider(withProviderOverrides(requestConfig, providerOverrideOf(args.provider), args.model, "edit"));
29533
29742
  const canvasSelection = args.source === "canvas_selection";
29534
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");
29535
29744
  const sourceImages = canvasSelection ? await resolveCanvasSelectionReferences({
@@ -29583,7 +29792,7 @@ function apply(ctx, config = {}) {
29583
29792
  signal: exec.signal
29584
29793
  }), active.provider, active.model, "subscription edit", current(), exec, knownWorkspaceRoots);
29585
29794
  }
29586
- 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);
29587
29796
  if (active.provider === "google") {
29588
29797
  const aspectRatio = args.aspect_ratio ?? active.aspectRatio;
29589
29798
  const imageSize = args.image_size ?? active.imageSize;
@@ -29603,14 +29812,14 @@ function apply(ctx, config = {}) {
29603
29812
  const size = args.size ?? active.imageSize;
29604
29813
  if (active.provider === "openai" || active.provider === "openai-compat" || active.provider === "xai" || active.provider === "zhipu") {
29605
29814
  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;
29815
+ const quality = active.provider === "openai-compat" ? compatRequestQuality(requestConfig, args.quality) : active.provider === "openai" ? args.quality : void 0;
29607
29816
  if (active.provider === "openai" && quality !== void 0 && !officialQualityOptions(active.model).some((option) => option.value === quality)) throw new Error("该 OpenAI 模型不支持所选生成质量");
29608
29817
  const xaiExtraBody = active.provider === "xai" ? xaiToolParameters({
29609
29818
  ...args.aspect_ratio !== void 0 ? { aspectRatio: args.aspect_ratio } : {},
29610
29819
  ...args.image_size !== void 0 ? { imageSize: args.image_size } : {},
29611
29820
  ...args.size !== void 0 ? { size: args.size } : {}
29612
29821
  }) : void 0;
29613
- return saveGenerated(ctx, await editOpenAICompatibleImage({
29822
+ const value = await saveGenerated(ctx, await editOpenAICompatibleImage({
29614
29823
  apiKey: credential,
29615
29824
  baseURL: active.baseURL,
29616
29825
  model: active.model,
@@ -29623,12 +29832,16 @@ function apply(ctx, config = {}) {
29623
29832
  ...active.provider === "openai-compat" ? {
29624
29833
  editFormat: active.editFormat,
29625
29834
  editExtra: active.editExtra,
29626
- editSizeMode: current().openaiCompatEditSizeMode ?? "auto"
29835
+ editSizeMode: requestConfig.openaiCompatEditSizeMode ?? "auto"
29627
29836
  } : active.provider === "xai" ? {
29628
29837
  editFormat: "xaiJson",
29629
29838
  ...xaiExtraBody === void 0 ? {} : { extraBody: xaiExtraBody }
29630
29839
  } : {}
29631
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;
29632
29845
  }
29633
29846
  if (active.provider === "seedream") return saveGenerated(ctx, await editSeedreamImage({
29634
29847
  apiKey: credential,
@@ -29793,6 +30006,7 @@ function imageOutput(verb) {
29793
30006
  type: "string",
29794
30007
  required: true
29795
30008
  },
30009
+ channel: { type: "string" },
29796
30010
  model: {
29797
30011
  type: "string",
29798
30012
  required: true
@@ -29823,6 +30037,7 @@ function imageOutput(verb) {
29823
30037
  provider: value.provider,
29824
30038
  model: value.model,
29825
30039
  output: value.output,
30040
+ ...value.channel === void 0 ? {} : { channel: value.channel },
29826
30041
  ...verb === "Edited" ? { operation: "edit" } : {},
29827
30042
  ...typeof value.savedTo === "string" ? { savedTo: value.savedTo } : {},
29828
30043
  ...typeof value.seed === "number" ? { seed: value.seed } : {},