dsh-image-gen 0.8.6 → 0.8.7

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
@@ -5581,6 +5581,111 @@ function toolAbortedBeforeDispatchResult(prior) {
5581
5581
  };
5582
5582
  }
5583
5583
  //#endregion
5584
+ //#region lib/types/watermark.js
5585
+ const WATERMARK_OPTIONS = [
5586
+ {
5587
+ value: "default",
5588
+ label: "渠道默认"
5589
+ },
5590
+ {
5591
+ value: "off",
5592
+ label: "关闭"
5593
+ },
5594
+ {
5595
+ value: "on",
5596
+ label: "开启"
5597
+ }
5598
+ ];
5599
+ function configuredField(extra) {
5600
+ if (typeof extra?.watermark === "boolean") return "watermark";
5601
+ if (typeof extra?.watermark_enabled === "boolean") return "watermark_enabled";
5602
+ const parameters = extra?.parameters;
5603
+ if (parameters && typeof parameters === "object" && !Array.isArray(parameters) && typeof parameters.watermark === "boolean") return "parameters.watermark";
5604
+ }
5605
+ /** Model names alone do not establish the protocol of an unknown relay. */
5606
+ function officialContract(config, mode) {
5607
+ const model = (mode === "generate" ? config.openaiCompatExtraBody : config.openaiCompatEditExtra)?.model ?? config.openaiCompatModel;
5608
+ if (typeof model !== "string") return void 0;
5609
+ try {
5610
+ const url = new URL(config.openaiCompatBaseURL ?? "");
5611
+ if (url.protocol !== "https:" || url.port || url.search || url.hash) return void 0;
5612
+ const path = url.pathname.replace(/\/+$/, "");
5613
+ if (url.hostname === "token.sensenova.cn" && path === "/v1" && model === "sensenova-u1.5-lite") return "sensenova";
5614
+ if (/^[a-z0-9-]+\.(?:cn-beijing|ap-southeast-1|us-east-1|eu-central-1|ap-northeast-1|cn-hongkong)\.maas\.aliyuncs\.com$/.test(url.hostname) && path === "/compatible-mode/v1" && /^qwen-image-(?:3\.0(?:-pro)?|2\.1-(?:pro|turbo))$/.test(model)) return "qwen";
5615
+ } catch {}
5616
+ }
5617
+ /** Known official edit contracts take precedence over relay-specific format settings. */
5618
+ function compatEditFormat(config) {
5619
+ const contract = officialContract(config, "edit");
5620
+ if (contract === "sensenova") return "jsonImageUrlArray";
5621
+ if (contract === "qwen") return "qwenJson";
5622
+ return config.openaiCompatEditFormat ?? "multipart";
5623
+ }
5624
+ function compatWatermarkField(config, mode = "generate") {
5625
+ if (officialContract(config, mode) !== void 0) return "watermark";
5626
+ if (mode === "generate") return configuredField(config.openaiCompatExtraBody);
5627
+ return compatEditFormat(config) === "jsonImageUrlArray" ? configuredField(config.openaiCompatEditExtra) : void 0;
5628
+ }
5629
+ function overrideField(extra, field, selected) {
5630
+ const watermark = selected === "on";
5631
+ return field === "parameters.watermark" ? {
5632
+ ...extra,
5633
+ parameters: {
5634
+ ...extra?.parameters,
5635
+ watermark
5636
+ }
5637
+ } : {
5638
+ ...extra,
5639
+ [field]: watermark
5640
+ };
5641
+ }
5642
+ /** Preserve all advanced fields; only an explicit workbench choice overrides its own watermark. */
5643
+ function compatGenerationExtraBody(config, selected) {
5644
+ const extra = config.openaiCompatExtraBody;
5645
+ if (selected === void 0 || selected === "default") return extra;
5646
+ const field = compatWatermarkField(config);
5647
+ if (field === void 0) throw new Error("该渠道尚未确认文生图水印支持,请刷新工作台");
5648
+ return overrideField(extra, field, selected);
5649
+ }
5650
+ function compatEditExtraBody(config, selected) {
5651
+ if (selected === void 0 || selected === "default") return void 0;
5652
+ const field = compatWatermarkField(config, "edit");
5653
+ if (field === void 0) throw new Error("该渠道尚未确认图生图水印支持,请刷新工作台");
5654
+ return overrideField(config.openaiCompatEditExtra, field, selected);
5655
+ }
5656
+ function selectedWatermark(selected, channelDefault) {
5657
+ return selected === "off" ? false : selected === "on" ? true : channelDefault;
5658
+ }
5659
+ function watermarkProfile(config, provider, model) {
5660
+ const capability = (mode) => {
5661
+ if (provider === "seedream" || provider === "dashscope" && /^qwen-image(?:$|-)/.test(model)) return { status: "supported" };
5662
+ if (provider === "openai-compat") return compatWatermarkField(config, mode) === void 0 ? {
5663
+ status: "unknown",
5664
+ reason: mode === "edit" && configuredField(config.openaiCompatEditExtra) !== void 0 ? "edit-format" : "channel-unconfirmed"
5665
+ } : { status: "supported" };
5666
+ if (provider === "zhipu") return {
5667
+ status: "unknown",
5668
+ reason: "account-unconfirmed"
5669
+ };
5670
+ if (provider === "dashscope") return {
5671
+ status: "unknown",
5672
+ reason: "model-unconfirmed"
5673
+ };
5674
+ return {
5675
+ status: "unavailable",
5676
+ reason: "no-api-control"
5677
+ };
5678
+ };
5679
+ const generate = capability("generate");
5680
+ return {
5681
+ watermarkCapabilities: {
5682
+ generate,
5683
+ edit: capability("edit")
5684
+ },
5685
+ ...generate.status === "supported" ? { watermarkOptions: WATERMARK_OPTIONS } : {}
5686
+ };
5687
+ }
5688
+ //#endregion
5584
5689
  //#region lib/types/shared.js
5585
5690
  /** Browser route used by the generated-image card. */
5586
5691
  const IMAGE_ROUTE = "/plugins/dsh-image-gen/image";
@@ -5717,6 +5822,17 @@ const SUBSCRIPTION_PROVIDER_DISPLAY_NAMES = {
5717
5822
  "grok-sub": "Grok 订阅",
5718
5823
  "google-sub": "Google 订阅"
5719
5824
  };
5825
+ const STUDIO_WATERMARK_VALUES = [
5826
+ "default",
5827
+ "off",
5828
+ "on"
5829
+ ];
5830
+ function studioWatermarkCapability(profile, mode) {
5831
+ return profile?.watermarkCapabilities?.[mode] ?? (mode === "generate" && profile?.watermarkOptions?.length ? { status: "supported" } : {
5832
+ status: "unknown",
5833
+ reason: "channel-unconfirmed"
5834
+ });
5835
+ }
5720
5836
  /** Default endpoints and base URLs. */
5721
5837
  const DEFAULT_GOOGLE_ENDPOINT = "https://generativelanguage.googleapis.com/v1beta/interactions";
5722
5838
  const DEFAULT_OPENAI_BASE_URL = "https://api.openai.com/v1";
@@ -5841,8 +5957,18 @@ const Config = z.object({
5841
5957
  googleEndpoint: volatile(z.string().default(DEFAULT_GOOGLE_ENDPOINT)),
5842
5958
  openaiBaseURL: volatile(z.string().default(DEFAULT_OPENAI_BASE_URL)),
5843
5959
  openaiModel: volatile(z.string().default(DEFAULT_OPENAI_MODEL)),
5960
+ openaiSizes: volatile(z.dict(z.dict(z.string())).default({})),
5844
5961
  openaiCompatBaseURL: volatile(z.string().default("")),
5845
5962
  openaiCompatModel: volatile(z.string().default("")),
5963
+ openaiCompatQuality: volatile(z.string().default("")),
5964
+ openaiCompatQualityOptions: volatile(z.array(z.string()).default([
5965
+ "auto",
5966
+ "low",
5967
+ "medium",
5968
+ "high"
5969
+ ])),
5970
+ openaiCompatExtraBody: volatile(z.dict(z.any()).default({})),
5971
+ openaiCompatEditSizeMode: volatile(z.union(["auto", "selected"]).default("auto")),
5846
5972
  openaiCompatEditFormat: volatile(z.union([
5847
5973
  z.const("multipart"),
5848
5974
  z.const("jsonImageUrlArray"),
@@ -5896,7 +6022,7 @@ function resolveProvider(config) {
5896
6022
  model,
5897
6023
  baseURL,
5898
6024
  imageSize: "1024x1024",
5899
- editFormat: config.openaiCompatEditFormat ?? "multipart",
6025
+ editFormat: compatEditFormat(config),
5900
6026
  editExtra: config.openaiCompatEditExtra ?? {}
5901
6027
  };
5902
6028
  }
@@ -7268,7 +7394,10 @@ async function generateDashScopeImage(options) {
7268
7394
  role: "user",
7269
7395
  content: [{ text: options.prompt }]
7270
7396
  }] },
7271
- parameters: { ...formattedSize === void 0 ? {} : { size: formattedSize } }
7397
+ parameters: {
7398
+ ...formattedSize === void 0 ? {} : { size: formattedSize },
7399
+ ...options.watermark === void 0 ? {} : { watermark: options.watermark }
7400
+ }
7272
7401
  },
7273
7402
  operation: "generation"
7274
7403
  });
@@ -7287,7 +7416,8 @@ async function editDashScopeImage(options) {
7287
7416
  }] },
7288
7417
  parameters: {
7289
7418
  prompt_extend: true,
7290
- ...formattedSize === void 0 ? {} : { size: formattedSize }
7419
+ ...formattedSize === void 0 ? {} : { size: formattedSize },
7420
+ ...options.watermark === void 0 ? {} : { watermark: options.watermark }
7291
7421
  }
7292
7422
  },
7293
7423
  operation: "editing"
@@ -7993,6 +8123,24 @@ async function generateOpenAICompatibleImage(input) {
7993
8123
  }), input.provider, input);
7994
8124
  }
7995
8125
  async function editOpenAICompatibleImage(input) {
8126
+ if (input.editFormat === "qwenJson") return parseImageResponse(await fetch(imageEndpoint$1(input.baseURL, "generations"), {
8127
+ method: "POST",
8128
+ redirect: "error",
8129
+ signal: input.signal,
8130
+ headers: {
8131
+ authorization: `Bearer ${input.apiKey}`,
8132
+ "content-type": "application/json"
8133
+ },
8134
+ body: JSON.stringify({
8135
+ model: input.model,
8136
+ prompt: input.prompt,
8137
+ image: input.sourceImages.map(toDataUrl$2),
8138
+ ...input.size === void 0 ? {} : { size: input.size },
8139
+ ...input.quality === void 0 ? {} : { quality: input.quality },
8140
+ ...input.editExtra ?? {},
8141
+ ...input.extraBody ?? {}
8142
+ })
8143
+ }), "openai-compat", input);
7996
8144
  if (input.editFormat === "xaiJson") {
7997
8145
  const references = input.sourceImages.map((image) => ({
7998
8146
  type: "image_url",
@@ -8021,7 +8169,7 @@ async function editOpenAICompatibleImage(input) {
8021
8169
  images: input.sourceImages.map((sourceImage) => ({ image_url: toDataUrl$2(sourceImage) })),
8022
8170
  prompt: input.prompt,
8023
8171
  n: 1,
8024
- size: "auto",
8172
+ size: input.editSizeMode === "selected" ? input.size ?? "auto" : "auto",
8025
8173
  response_format: "url",
8026
8174
  ...input.quality === void 0 ? {} : { quality: input.quality },
8027
8175
  ...input.editExtra ?? {},
@@ -8749,19 +8897,22 @@ const ANTIGRAVITY_CLIENT_ID = "1071006060591-tmhssin2h21lcre235vtolojh4g403ep.ap
8749
8897
  const ANTIGRAVITY_CLIENT_SECRET = "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";
8750
8898
  const ANTIGRAVITY_REDIRECT_URI = "http://localhost:51121/oauth-callback";
8751
8899
  /**
8752
- * Endpoint fallback order for generation, mirroring the reference (daily
8753
- * sandbox first, then autopush, then prod).
8900
+ * Preserve the working sandbox routes, then try ordinary daily (consumer
8901
+ * accounts) before the production compatibility fallback. A Gmail address
8902
+ * alone does not establish which backend an account can use.
8754
8903
  */
8755
8904
  const ANTIGRAVITY_ENDPOINTS = [
8756
8905
  "https://daily-cloudcode-pa.sandbox.googleapis.com",
8757
8906
  "https://autopush-cloudcode-pa.sandbox.googleapis.com",
8907
+ "https://daily-cloudcode-pa.googleapis.com",
8758
8908
  "https://cloudcode-pa.googleapis.com"
8759
8909
  ];
8760
8910
  /** loadCodeAssist is best supported on prod; discovery tries prod first. */
8761
8911
  const LOAD_ENDPOINTS = [
8762
8912
  "https://cloudcode-pa.googleapis.com",
8763
8913
  "https://daily-cloudcode-pa.sandbox.googleapis.com",
8764
- "https://autopush-cloudcode-pa.sandbox.googleapis.com"
8914
+ "https://autopush-cloudcode-pa.sandbox.googleapis.com",
8915
+ "https://daily-cloudcode-pa.googleapis.com"
8765
8916
  ];
8766
8917
  /** The image model served through this channel: Nano Banana 2. */
8767
8918
  const ANTIGRAVITY_IMAGE_MODEL = DEFAULT_SUBSCRIPTION_MODELS["google-sub"];
@@ -8803,7 +8954,7 @@ async function fetchVersionText(url, maxChars) {
8803
8954
  return;
8804
8955
  }
8805
8956
  }
8806
- /** Hardcoded fallback project when Antigravity returns none (workspace accounts). */
8957
+ /** Legacy compatibility project: not an account project or proof of entitlement. */
8807
8958
  const DEFAULT_PROJECT_ID = "rising-fact-p41fc";
8808
8959
  /** Generation timeout mirroring the reference implementation. */
8809
8960
  const IMAGE_TIMEOUT_MS = 12e4;
@@ -8869,16 +9020,10 @@ function antigravityAuthorizeUrl(cfg, pkce) {
8869
9020
  * plain SDK identity here, mirroring its project.js loadHeaders.
8870
9021
  */
8871
9022
  function loadHeaders(projectId) {
8872
- const platform = process.platform === "win32" ? "WINDOWS" : process.platform === "darwin" ? "MACOS" : "LINUX";
8873
9023
  return {
8874
9024
  "User-Agent": "google-api-nodejs-client/9.15.1",
8875
9025
  "X-Goog-Api-Client": "google-cloud-sdk vscode_cloudshelleditor/0.1",
8876
- "Client-Metadata": JSON.stringify({
8877
- ideType: "ANTIGRAVITY",
8878
- platform,
8879
- pluginType: "GEMINI",
8880
- ...projectId.length > 0 ? { duetProject: projectId } : {}
8881
- })
9026
+ "Client-Metadata": JSON.stringify(clientMetadata(projectId))
8882
9027
  };
8883
9028
  }
8884
9029
  /**
@@ -8888,17 +9033,10 @@ function loadHeaders(projectId) {
8888
9033
  * SDK UA here is what produced 403 #3501 on personal accounts.
8889
9034
  */
8890
9035
  function contentHeaders(projectId) {
8891
- const uaPlatform = process.platform === "darwin" ? "Macintosh; Intel Mac OS X 10_15_7" : "Windows NT 10.0; Win64; x64";
8892
- const platform = process.platform === "win32" ? "WINDOWS" : process.platform === "darwin" ? "MACOS" : "LINUX";
8893
9036
  return {
8894
- "User-Agent": `Mozilla/5.0 (${uaPlatform}) AppleWebKit/537.36 (KHTML, like Gecko) Antigravity/${getAntigravityVersion()} Chrome/138.0.7204.235 Electron/37.3.1 Safari/537.36`,
9037
+ "User-Agent": `Mozilla/5.0 (${process.platform === "darwin" ? "Macintosh; Intel Mac OS X 10_15_7" : "Windows NT 10.0; Win64; x64"}) AppleWebKit/537.36 (KHTML, like Gecko) Antigravity/${getAntigravityVersion()} Chrome/138.0.7204.235 Electron/37.3.1 Safari/537.36`,
8895
9038
  "X-Goog-Api-Client": "google-cloud-sdk vscode_cloudshelleditor/0.1",
8896
- "Client-Metadata": JSON.stringify({
8897
- ideType: "ANTIGRAVITY",
8898
- platform,
8899
- pluginType: "GEMINI",
8900
- ...projectId.length > 0 ? { duetProject: projectId } : {}
8901
- })
9039
+ "Client-Metadata": JSON.stringify(clientMetadata(projectId))
8902
9040
  };
8903
9041
  }
8904
9042
  function tokenBlobFromOAuth(json) {
@@ -8953,41 +9091,113 @@ async function antigravityRefresh(blob) {
8953
9091
  };
8954
9092
  }
8955
9093
  /**
8956
- * Resolve the managed project id for the logged-in account. Most accounts
8957
- * have one; when loadCodeAssist reports none, the hardcoded fallback keeps
8958
- * generation working (mirrors the reference's ensureProjectContext).
8959
- * Returns '' only when every endpoint fails, letting generate() throw then.
9094
+ * Discover an actual account project, preserving HTTP and eligibility failures.
9095
+ * Keep the legacy fallback for accounts whose image route still accepts it;
9096
+ * UNSUPPORTED_CLIENT here describes Code Assist, not a universal image ban.
9097
+ * Do not invoke onboarding or accept account terms during image generation.
8960
9098
  */
8961
- async function antigravityResolveProject(blob) {
8962
- const metadata = {
9099
+ async function antigravityResolveProjectContext(blob, signal) {
9100
+ signal?.throwIfAborted();
9101
+ const metadata = clientMetadata("");
9102
+ const diagnostics = [];
9103
+ for (const base of LOAD_ENDPOINTS) {
9104
+ const label = `loadCodeAssist ${new URL(base).hostname}`;
9105
+ try {
9106
+ const response = await fetch(`${base}/v1internal:loadCodeAssist`, {
9107
+ method: "POST",
9108
+ headers: {
9109
+ "Content-Type": "application/json",
9110
+ Authorization: `Bearer ${blob.accessToken}`,
9111
+ ...loadHeaders("")
9112
+ },
9113
+ body: JSON.stringify({ metadata }),
9114
+ signal: signal === void 0 ? AbortSignal.timeout(25e3) : AbortSignal.any([signal, AbortSignal.timeout(25e3)])
9115
+ });
9116
+ if (!response.ok) {
9117
+ diagnostics.push(`${label}: ${httpDiagnostic(response.status, await response.text().catch(() => ""), blob)}`);
9118
+ continue;
9119
+ }
9120
+ const payload = recordOf(await response.json().catch(() => null));
9121
+ if (payload === void 0) {
9122
+ diagnostics.push(`${label}: HTTP ${String(response.status)}, invalid response`);
9123
+ continue;
9124
+ }
9125
+ const project = payload.cloudaicompanionProject;
9126
+ const projectRecord = recordOf(project);
9127
+ const id = typeof project === "string" ? project : typeof projectRecord?.id === "string" ? projectRecord.id : "";
9128
+ if (id.trim().length > 0) return {
9129
+ projectId: id.trim(),
9130
+ source: "account",
9131
+ diagnostics
9132
+ };
9133
+ diagnostics.push(`${label}: HTTP ${String(response.status)}, ${missingProjectDiagnostic(payload, blob)}`);
9134
+ } catch (error) {
9135
+ if (signal?.aborted === true) throw error;
9136
+ diagnostics.push(`${label}: ${diagnosticText(error instanceof Error ? error.message : "request failed", blob)}`);
9137
+ }
9138
+ }
9139
+ return {
9140
+ projectId: DEFAULT_PROJECT_ID,
9141
+ source: "fallback",
9142
+ diagnostics
9143
+ };
9144
+ }
9145
+ /** Google ClientMetadata.Platform enum, shared by request body and headers. */
9146
+ function platformOf() {
9147
+ if (process.platform === "win32" && process.arch === "x64") return "WINDOWS_AMD64";
9148
+ if (process.platform === "darwin" && process.arch === "x64") return "DARWIN_AMD64";
9149
+ if (process.platform === "darwin" && process.arch === "arm64") return "DARWIN_ARM64";
9150
+ if (process.platform === "linux" && process.arch === "x64") return "LINUX_AMD64";
9151
+ if (process.platform === "linux" && process.arch === "arm64") return "LINUX_ARM64";
9152
+ return "PLATFORM_UNSPECIFIED";
9153
+ }
9154
+ function clientMetadata(projectId) {
9155
+ return {
8963
9156
  ideType: "ANTIGRAVITY",
8964
9157
  platform: platformOf(),
8965
- pluginType: "GEMINI"
9158
+ pluginType: "GEMINI",
9159
+ ...projectId.length > 0 ? { duetProject: projectId } : {}
8966
9160
  };
8967
- for (const base of LOAD_ENDPOINTS) try {
8968
- const response = await fetch(`${base}/v1internal:loadCodeAssist`, {
8969
- method: "POST",
8970
- headers: {
8971
- "Content-Type": "application/json",
8972
- Authorization: `Bearer ${blob.accessToken}`,
8973
- ...loadHeaders("")
8974
- },
8975
- body: JSON.stringify({ metadata }),
8976
- signal: AbortSignal.timeout(25e3)
8977
- });
8978
- if (!response.ok) continue;
8979
- const payload = await response.json().catch(() => null);
8980
- if (payload === null) continue;
8981
- const project = payload.cloudaicompanionProject;
8982
- const id = typeof project === "string" ? project : typeof project === "object" && project !== null && typeof project.id === "string" ? project.id : "";
8983
- if (id.length > 0) return id;
8984
- } catch {}
8985
- return DEFAULT_PROJECT_ID;
8986
9161
  }
8987
- function platformOf() {
8988
- if (process.platform === "win32") return "WINDOWS";
8989
- if (process.platform === "darwin") return "MACOS";
8990
- return "LINUX";
9162
+ function recordOf(value) {
9163
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
9164
+ }
9165
+ /** Upstream errors may echo request identity; redact before bounding the text. */
9166
+ function diagnosticText(value, blob) {
9167
+ let text = typeof value === "string" ? value : "";
9168
+ for (const secret of [
9169
+ blob.accessToken,
9170
+ blob.refreshToken,
9171
+ blob.accountId,
9172
+ blob.email
9173
+ ]) if (secret.length > 0) text = text.replaceAll(secret, "[redacted]");
9174
+ return text.replace(/Bearer\s+\S+/gi, "Bearer [redacted]").replace(/\s+/g, " ").trim().slice(0, 200);
9175
+ }
9176
+ function httpDiagnostic(status, text, blob) {
9177
+ let detail = text;
9178
+ try {
9179
+ const error = recordOf(recordOf(JSON.parse(text))?.error);
9180
+ if (typeof error?.message === "string") detail = `${typeof error.status === "string" ? `${error.status}: ` : ""}${error.message}`;
9181
+ } catch {}
9182
+ const safe = diagnosticText(detail, blob);
9183
+ return `HTTP ${String(status)}${safe.length > 0 ? `: ${safe}` : ""}`;
9184
+ }
9185
+ function missingProjectDiagnostic(payload, blob) {
9186
+ const reasons = Array.isArray(payload.ineligibleTiers) ? payload.ineligibleTiers.flatMap((value) => {
9187
+ const tier = recordOf(value);
9188
+ return typeof tier?.reasonCode === "string" ? [`${tier.reasonCode}${typeof tier.reasonMessage === "string" ? `: ${tier.reasonMessage}` : ""}`] : [];
9189
+ }) : [];
9190
+ const allowed = Array.isArray(payload.allowedTiers) ? payload.allowedTiers.flatMap((value) => {
9191
+ const tier = recordOf(value);
9192
+ return typeof tier?.id === "string" ? [tier.id] : [];
9193
+ }) : [];
9194
+ const current = recordOf(payload.paidTier)?.id ?? recordOf(payload.currentTier)?.id;
9195
+ const detail = [
9196
+ ...reasons,
9197
+ ...typeof current === "string" ? [`tier=${current}`] : [],
9198
+ ...allowed.length > 0 ? [`allowedTiers=${allowed.join(",")}`] : []
9199
+ ];
9200
+ return `未返回账号项目${detail.length > 0 ? `; ${diagnosticText(detail.join("; "), blob)}` : ""}`;
8991
9201
  }
8992
9202
  /**
8993
9203
  * Build the streamGenerateContent request body for one image generation.
@@ -9039,6 +9249,7 @@ function antigravityEnvelope(projectId, request) {
9039
9249
  * parse the SSE stream for the inlineData image part.
9040
9250
  */
9041
9251
  async function antigravityGenerateImage(options) {
9252
+ options.signal?.throwIfAborted();
9042
9253
  await initAntigravityVersion();
9043
9254
  const inner = antigravityImageBody({
9044
9255
  prompt: options.prompt,
@@ -9047,7 +9258,7 @@ async function antigravityGenerateImage(options) {
9047
9258
  ...options.referenceImages !== void 0 ? { referenceImages: options.referenceImages } : {}
9048
9259
  });
9049
9260
  const body = antigravityEnvelope(options.projectId, inner);
9050
- let lastError = "no endpoint succeeded";
9261
+ const failures = [];
9051
9262
  for (const base of ANTIGRAVITY_ENDPOINTS) {
9052
9263
  const url = `${base}/v1internal:streamGenerateContent?alt=sse`;
9053
9264
  const timeoutSignal = AbortSignal.timeout(IMAGE_TIMEOUT_MS);
@@ -9066,23 +9277,23 @@ async function antigravityGenerateImage(options) {
9066
9277
  });
9067
9278
  if (!response.ok) {
9068
9279
  const text = await response.text().catch(() => "");
9069
- lastError = `HTTP ${String(response.status)}: ${text.slice(0, 200)}`;
9070
- if (response.status === 403 || response.status === 404 || response.status === 429 || response.status >= 500) continue;
9071
- throw new Error(`antigravity ${lastError}`);
9280
+ failures.push(`${new URL(base).hostname}: ${httpDiagnostic(response.status, text, options.blob)}`);
9281
+ continue;
9072
9282
  }
9073
9283
  const text = await response.text();
9074
9284
  const parsed = parseSseImage(text);
9075
9285
  if (parsed !== void 0) return parsed;
9076
9286
  const reason = parseSseError(text);
9077
- lastError = reason !== void 0 ? reason : "response contained no image";
9287
+ failures.push(`${new URL(base).hostname}: ${diagnosticText(reason ?? "response contained no image", options.blob)}`);
9078
9288
  continue;
9079
9289
  } catch (error) {
9080
9290
  if (options.signal?.aborted === true) throw error;
9081
- lastError = error instanceof Error ? error.message : String(error);
9291
+ failures.push(`${new URL(base).hostname}: ${diagnosticText(error instanceof Error ? error.message : "request failed", options.blob)}`);
9082
9292
  continue;
9083
9293
  }
9084
9294
  }
9085
- throw new Error(`antigravity image generation failed: ${lastError}`);
9295
+ const discovery = options.projectContext?.source === "fallback" ? ["项目发现未返回账号项目,已尝试兼容项目。", ...options.projectContext.diagnostics] : [];
9296
+ throw new Error(`antigravity image generation failed: ${[...discovery, ...failures].join(" | ")}`);
9086
9297
  }
9087
9298
  /** Scan an SSE body for the first image part in any data: line. */
9088
9299
  function parseSseImage(text) {
@@ -9310,11 +9521,12 @@ var SubscriptionManager = class {
9310
9521
  const references = options.referenceImages ?? [];
9311
9522
  if (references.length > 5) throw new Error(`订阅生图最多支持 ${String(5)} 张参考图,当前 ${String(references.length)} 张`);
9312
9523
  if (vendor === "antigravity") {
9313
- const projectId = await this.ensureAntigravityProject(session);
9524
+ const projectContext = await this.ensureAntigravityProject(session, options.signal);
9314
9525
  const aspectRatio = antigravityAspectRatioOf(options.size);
9315
9526
  return [{ b64_json: (await antigravityGenerateImage({
9316
9527
  blob: session,
9317
- projectId,
9528
+ projectId: projectContext.projectId,
9529
+ projectContext,
9318
9530
  prompt: text,
9319
9531
  ...aspectRatio !== void 0 ? { aspectRatio } : {},
9320
9532
  hd: options.quality === "hd" || options.quality === "high",
@@ -9376,13 +9588,13 @@ var SubscriptionManager = class {
9376
9588
  * Antigravity generation envelope needs. Re-resolved when the account
9377
9589
  * signs out or a different account logs in.
9378
9590
  */
9379
- async ensureAntigravityProject(blob) {
9591
+ async ensureAntigravityProject(blob, signal) {
9380
9592
  const cacheKey = blob.refreshToken.length > 0 ? blob.refreshToken : blob.accessToken.slice(0, 32);
9381
9593
  const cached = this.projectCache.get(cacheKey);
9382
9594
  if (cached !== void 0) return cached;
9383
- const projectId = await antigravityResolveProject(blob);
9384
- this.projectCache.set(cacheKey, projectId);
9385
- return projectId;
9595
+ const context = await antigravityResolveProjectContext(blob, signal);
9596
+ if (context.source === "account") this.projectCache.set(cacheKey, context);
9597
+ return context;
9386
9598
  }
9387
9599
  };
9388
9600
  /** The private Codex backend can ignore size while following a canvas ratio in
@@ -27187,6 +27399,103 @@ async function clearDiskCache() {
27187
27399
  } catch {}
27188
27400
  }
27189
27401
  //#endregion
27402
+ //#region lib/types/openai-output.js
27403
+ const PROVIDER_DEFAULT_QUALITY = "__default__";
27404
+ const DEFAULT_COMPAT_QUALITIES = [
27405
+ "auto",
27406
+ "low",
27407
+ "medium",
27408
+ "high"
27409
+ ];
27410
+ const LEGACY_SIZES = [
27411
+ "1024x1024",
27412
+ "1536x1024",
27413
+ "1024x1536"
27414
+ ];
27415
+ const OPENAI_SIZES = {
27416
+ "1:1": {
27417
+ "1K": "1024x1024",
27418
+ "2K": "2048x2048"
27419
+ },
27420
+ "3:2": {
27421
+ "1K": "1536x1024",
27422
+ "2K": "2304x1536"
27423
+ },
27424
+ "2:3": {
27425
+ "1K": "1024x1536",
27426
+ "2K": "1536x2304"
27427
+ },
27428
+ "4:3": {
27429
+ "1K": "1152x864",
27430
+ "2K": "2048x1536"
27431
+ },
27432
+ "3:4": {
27433
+ "1K": "864x1152",
27434
+ "2K": "1536x2048"
27435
+ },
27436
+ "16:9": {
27437
+ "1K": "1536x864",
27438
+ "2K": "2048x1152",
27439
+ "4K": "3840x2160"
27440
+ },
27441
+ "9:16": {
27442
+ "1K": "864x1536",
27443
+ "2K": "1152x2048",
27444
+ "4K": "2160x3840"
27445
+ }
27446
+ };
27447
+ function flexibleSizeModel(model) {
27448
+ return /^gpt-image-2(?:$|[-.])/.test(model);
27449
+ }
27450
+ function officialSizeTable(config, model) {
27451
+ if (config.openaiSizes && Object.keys(config.openaiSizes).length > 0) return config.openaiSizes;
27452
+ if (flexibleSizeModel(model)) return OPENAI_SIZES;
27453
+ return {
27454
+ "1:1": { "1K": LEGACY_SIZES[0] },
27455
+ "3:2": { "1K": LEGACY_SIZES[1] },
27456
+ "2:3": { "1K": LEGACY_SIZES[2] }
27457
+ };
27458
+ }
27459
+ /** Check the official model contract before spending a request. Relays own their size contract. */
27460
+ function validateOfficialSize(model, size) {
27461
+ if (size === "auto") return;
27462
+ if (!flexibleSizeModel(model)) {
27463
+ if (!LEGACY_SIZES.includes(size)) throw new Error(`模型 ${model} 不支持尺寸 ${size};请使用 1024x1024、1536x1024 或 1024x1536`);
27464
+ return;
27465
+ }
27466
+ const match = /^(\d+)x(\d+)$/.exec(size);
27467
+ const width = Number(match?.[1]);
27468
+ const height = Number(match?.[2]);
27469
+ const pixels = width * height;
27470
+ if (!match || width % 16 !== 0 || height % 16 !== 0 || Math.max(width, height) > 3840 || pixels < 655360 || pixels > 8294400 || Math.max(width, height) > 3 * Math.min(width, height)) throw new Error(`OpenAI 尺寸 ${size} 无效:边长须为 16 的倍数且不超过 3840,比例在 1:3 到 3:1,像素数在 655360 到 8294400`);
27471
+ }
27472
+ function officialQualityOptions(model) {
27473
+ return [
27474
+ "auto",
27475
+ "low",
27476
+ "medium",
27477
+ "high",
27478
+ .../^gpt-image-2\.5-(sunburst|flare)(?:$|-)/.test(model) ? ["xhigh", "max"] : []
27479
+ ].map((value) => ({
27480
+ value,
27481
+ label: value === "auto" ? "自动" : value
27482
+ }));
27483
+ }
27484
+ function compatQualityOptions(config) {
27485
+ const values = [...new Set([...config.openaiCompatQualityOptions ?? DEFAULT_COMPAT_QUALITIES, config.openaiCompatQuality ?? ""].map((value) => value.trim()).filter((value) => value && value !== "__default__"))];
27486
+ return [{
27487
+ value: PROVIDER_DEFAULT_QUALITY,
27488
+ label: "渠道默认(不发送)"
27489
+ }, ...values.map((value) => ({
27490
+ value,
27491
+ label: value
27492
+ }))];
27493
+ }
27494
+ function compatRequestQuality(config, selected) {
27495
+ const value = (selected ?? config.openaiCompatQuality)?.trim();
27496
+ return value && value !== "__default__" ? value : void 0;
27497
+ }
27498
+ //#endregion
27190
27499
  //#region lib/types/studio.js
27191
27500
  const LEGACY_OPENAI_TABLE = {
27192
27501
  "1:1": { "1K": "1024x1024" },
@@ -27333,7 +27642,8 @@ function subscriptionStudioProfile(provider, signedIn) {
27333
27642
  ratioOptions: cap.ratioOptions,
27334
27643
  qualityOptions: cap.qualityOptions,
27335
27644
  defaultRatio: cap.defaultRatio,
27336
- defaultQuality: cap.defaultQuality
27645
+ defaultQuality: cap.defaultQuality,
27646
+ ...watermarkProfile({}, provider, DEFAULT_SUBSCRIPTION_MODELS[provider])
27337
27647
  };
27338
27648
  }
27339
27649
  /** Short display names for the workbench subscription rows. */
@@ -27352,6 +27662,7 @@ async function generateFromStudio(ctx, config, input, signal, fallbackWorkspaceR
27352
27662
  assertAllowed(studioProfile(config, input.provider, true), input);
27353
27663
  const active = resolveProvider(withProviderOverrides(config, input.provider, input.model));
27354
27664
  if (active.provider === "comfyui") throw new Error("ComfyUI 暂未接入工作台");
27665
+ if (isSubscriptionProvider(active.provider)) throw new Error("订阅生图应通过订阅渠道执行");
27355
27666
  const wired = active;
27356
27667
  const credential = await requireApiKey(ctx, input.provider);
27357
27668
  const rawRefs = input.references ?? (input.reference ? [input.reference] : []);
@@ -27388,12 +27699,16 @@ async function generateFromStudio(ctx, config, input, signal, fallbackWorkspaceR
27388
27699
  output = `${aspectRatio}, ${imageSize}`;
27389
27700
  } else if (wired.provider === "openai" || wired.provider === "openai-compat" || wired.provider === "xai" || wired.provider === "zhipu") {
27390
27701
  const p = wired.provider;
27391
- const size = p === "openai-compat" ? openAIRequestSize(config, input.ratio, input.quality) : p === "xai" ? void 0 : CLOUD_CAPABILITIES[p].sizeFor?.(input.ratio, input.quality);
27392
- const quality = p === "openai" ? input.quality === "auto" ? void 0 : input.quality : p === "zhipu" ? "hd" : void 0;
27702
+ const officialTable = p === "openai" ? officialSizeTable(config, wired.model) : void 0;
27703
+ const officialResolution = input.resolution ?? (officialTable ? openAICompatTable({ openaiCompatSizes: officialTable }).find((group) => group.ratio === input.ratio)?.tiers[0]?.tier : void 0) ?? "1K";
27704
+ 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);
27705
+ if (p === "openai" && size !== void 0) validateOfficialSize(wired.model, size);
27706
+ const requestedQuality = input.generationQuality ?? input.quality;
27707
+ const quality = p === "openai" ? requestedQuality === "auto" ? void 0 : requestedQuality : p === "openai-compat" ? compatRequestQuality(config, input.generationQuality) : p === "zhipu" ? "hd" : void 0;
27393
27708
  const extraBody = p === "xai" ? {
27394
27709
  aspect_ratio: input.ratio,
27395
27710
  resolution: input.quality
27396
- } : void 0;
27711
+ } : p === "openai-compat" ? input.mode === "generate" ? compatGenerationExtraBody(config, input.watermark) : compatEditExtraBody(config, input.watermark) : void 0;
27397
27712
  generated = input.mode === "edit" ? await editOpenAICompatibleImage({
27398
27713
  apiKey: credential,
27399
27714
  baseURL: wired.baseURL,
@@ -27407,7 +27722,8 @@ async function generateFromStudio(ctx, config, input, signal, fallbackWorkspaceR
27407
27722
  ...extraBody === void 0 ? {} : { extraBody },
27408
27723
  ...p === "openai-compat" ? {
27409
27724
  editFormat: wired.editFormat,
27410
- editExtra: wired.editExtra
27725
+ editExtra: wired.editExtra,
27726
+ editSizeMode: config.openaiCompatEditSizeMode ?? "auto"
27411
27727
  } : p === "xai" ? { editFormat: "xaiJson" } : {}
27412
27728
  }) : await generateOpenAICompatibleImage({
27413
27729
  provider: p,
@@ -27424,6 +27740,10 @@ async function generateFromStudio(ctx, config, input, signal, fallbackWorkspaceR
27424
27740
  output = size ?? `${input.ratio}, ${input.quality}`;
27425
27741
  } else if (wired.provider === "seedream") {
27426
27742
  const size = CLOUD_CAPABILITIES.seedream.sizeFor?.(input.ratio, input.quality) ?? input.quality;
27743
+ const arkOptions = {
27744
+ ...wired.arkOptions,
27745
+ watermark: selectedWatermark(input.watermark, wired.arkOptions.watermark)
27746
+ };
27427
27747
  generated = input.mode === "edit" ? await editSeedreamImage({
27428
27748
  apiKey: credential,
27429
27749
  baseURL: wired.baseURL,
@@ -27433,7 +27753,7 @@ async function generateFromStudio(ctx, config, input, signal, fallbackWorkspaceR
27433
27753
  size,
27434
27754
  maxBytes: ctx.attachments.imageLimits.maxImageBytes,
27435
27755
  signal,
27436
- arkOptions: wired.arkOptions
27756
+ arkOptions
27437
27757
  }) : await generateOpenAICompatibleImage({
27438
27758
  provider: "seedream",
27439
27759
  apiKey: credential,
@@ -27443,12 +27763,13 @@ async function generateFromStudio(ctx, config, input, signal, fallbackWorkspaceR
27443
27763
  size,
27444
27764
  maxBytes: ctx.attachments.imageLimits.maxImageBytes,
27445
27765
  signal,
27446
- arkOptions: wired.arkOptions
27766
+ arkOptions
27447
27767
  });
27448
27768
  output = size;
27449
- } else {
27769
+ } else if (wired.provider === "dashscope") {
27450
27770
  if (input.mode === "edit" && sourceImages.length > 3) throw new Error("DashScope (通义万相) 图生图目前最多支持 3 张参考图,请精简后重试");
27451
27771
  const size = CLOUD_CAPABILITIES.dashscope.sizeFor?.(input.ratio, input.quality) ?? "1024x1024";
27772
+ const watermark = selectedWatermark(input.watermark);
27452
27773
  generated = input.mode === "edit" ? await editDashScopeImage({
27453
27774
  apiKey: credential,
27454
27775
  endpoint: wired.endpoint,
@@ -27457,7 +27778,8 @@ async function generateFromStudio(ctx, config, input, signal, fallbackWorkspaceR
27457
27778
  sourceImages,
27458
27779
  size,
27459
27780
  maxBytes: ctx.attachments.imageLimits.maxImageBytes,
27460
- signal
27781
+ signal,
27782
+ ...watermark === void 0 ? {} : { watermark }
27461
27783
  }) : await generateDashScopeImage({
27462
27784
  apiKey: credential,
27463
27785
  endpoint: wired.endpoint,
@@ -27465,10 +27787,11 @@ async function generateFromStudio(ctx, config, input, signal, fallbackWorkspaceR
27465
27787
  prompt: input.prompt,
27466
27788
  size,
27467
27789
  maxBytes: ctx.attachments.imageLimits.maxImageBytes,
27468
- signal
27790
+ signal,
27791
+ ...watermark === void 0 ? {} : { watermark }
27469
27792
  });
27470
27793
  output = size;
27471
- }
27794
+ } else throw new Error("当前渠道未接入 API 工作台");
27472
27795
  if (!ctx.attachments.imageLimits.mediaTypes.includes(generated.mediaType)) throw new Error(`当前 DSH 不支持保存 ${generated.mediaType} 图片`);
27473
27796
  return {
27474
27797
  attachment: await ctx.attachments.saveImage({
@@ -27639,18 +27962,19 @@ function subscriptionResponse(input, startedAt, requestedCount, failedCount, ite
27639
27962
  };
27640
27963
  }
27641
27964
  function studioProfile(config, provider, configured) {
27965
+ const result = cloudStudioProfile(config, provider, configured);
27966
+ return {
27967
+ ...result,
27968
+ ...watermarkProfile(config, provider, result.model)
27969
+ };
27970
+ }
27971
+ function cloudStudioProfile(config, provider, configured) {
27642
27972
  let active;
27643
27973
  try {
27644
27974
  active = resolveProvider(withProviderOverrides(config, provider));
27645
- } catch {
27646
- return profile(provider, "", configured, [
27647
- "1:1",
27648
- "3:2",
27649
- "2:3"
27650
- ].map(option), [{
27651
- value: "standard",
27652
- label: "标准(推荐)"
27653
- }], "1:1", "standard");
27975
+ } catch (cause) {
27976
+ if (provider !== "openai-compat") throw cause;
27977
+ return compatibleStudioProfile(config, "", false);
27654
27978
  }
27655
27979
  if (active.provider === "comfyui") throw new Error("Invalid cloud provider profile");
27656
27980
  const model = active.model;
@@ -27658,19 +27982,55 @@ function studioProfile(config, provider, configured) {
27658
27982
  const cap = CLOUD_CAPABILITIES.google;
27659
27983
  return profile(provider, model, configured, cap.ratioOptions, cap.qualityOptions, cap.defaultRatio, cap.defaultQuality);
27660
27984
  }
27661
- if (provider === "openai-compat") {
27662
- if (config.openaiCompatSizes === void 0 || Object.keys(config.openaiCompatSizes).length === 0) return profile(provider, model, configured, [
27985
+ if (provider === "openai-compat") return compatibleStudioProfile(config, model, configured);
27986
+ if (provider === "openai") {
27987
+ const table = officialSizeTable(config, model);
27988
+ const order = Object.keys(table);
27989
+ const groups = openAICompatTable({ openaiCompatSizes: table }).sort((a, b) => order.indexOf(a.ratio) - order.indexOf(b.ratio));
27990
+ const resolutionOptions = Object.fromEntries(groups.map((group) => [group.ratio, group.tiers.map((entry) => ({
27991
+ value: entry.tier,
27992
+ label: `${entry.tier} · ${entry.size}`
27993
+ }))]));
27994
+ const defaultRatio = groups.some((group) => group.ratio === "1:1") ? "1:1" : groups[0]?.ratio ?? "1:1";
27995
+ return {
27996
+ ...profile(provider, model, configured, groups.map((group) => option(group.ratio)), officialQualityOptions(model), defaultRatio, "auto"),
27997
+ resolutionOptions,
27998
+ defaultResolution: resolutionOptions[defaultRatio]?.[0]?.value ?? "1K"
27999
+ };
28000
+ }
28001
+ const cap = CLOUD_CAPABILITIES[provider];
28002
+ return profile(provider, model, configured, cap.ratioOptions, cap.qualityOptions, cap.defaultRatio, cap.defaultQuality);
28003
+ }
28004
+ /** Output capabilities remain visible before credentials, address or model are configured. */
28005
+ function compatibleStudioProfile(config, model, configured) {
28006
+ const groups = openAICompatTable(config);
28007
+ const qualityProfile = {
28008
+ generationQualityOptions: compatQualityOptions(config),
28009
+ defaultGenerationQuality: compatRequestQuality(config) ?? "__default__",
28010
+ resolutionOptions: Object.fromEntries(groups.map((group) => [group.ratio, group.tiers.map((entry) => ({
28011
+ value: entry.tier,
28012
+ label: `${entry.tier} · ${entry.size}`
28013
+ }))]))
28014
+ };
28015
+ if (config.openaiCompatSizes === void 0 || Object.keys(config.openaiCompatSizes).length === 0) return {
28016
+ ...profile("openai-compat", model, configured, [
27663
28017
  "1:1",
27664
28018
  "3:2",
27665
28019
  "2:3"
27666
28020
  ].map(option), [{
27667
28021
  value: "standard",
27668
28022
  label: "标准(推荐)"
27669
- }], "1:1", "standard");
27670
- return openAICompatStudioProfile(config, model, configured);
27671
- }
27672
- const cap = CLOUD_CAPABILITIES[provider];
27673
- return profile(provider, model, configured, cap.ratioOptions, cap.qualityOptions, cap.defaultRatio, cap.defaultQuality);
28023
+ }], "1:1", "standard"),
28024
+ ...qualityProfile,
28025
+ resolutionOptions: Object.fromEntries(groups.map((group) => [group.ratio, [{
28026
+ value: "standard",
28027
+ label: `标准 · ${group.tiers[0].size}`
28028
+ }]]))
28029
+ };
28030
+ return {
28031
+ ...openAICompatStudioProfile(config, model, configured),
28032
+ ...qualityProfile
28033
+ };
27674
28034
  }
27675
28035
  function profile(provider, model, configured, ratioOptions, qualityOptions, defaultRatio, defaultQuality) {
27676
28036
  return {
@@ -27704,7 +28064,12 @@ function option(value) {
27704
28064
  function assertAllowed(profile, input) {
27705
28065
  if (input.model !== profile.model) throw new Error("模型配置已变化,请刷新工作台后重试");
27706
28066
  if (!profile.ratioOptions.some((option) => option.value === input.ratio)) throw new Error("该 Provider 不支持所选比例");
27707
- if (!profile.qualityOptions.some((option) => option.value === input.quality)) throw new Error("该 Provider 不支持所选清晰度");
28067
+ const selectedQuality = input.provider === "openai" ? input.generationQuality ?? input.quality : input.resolution ?? input.quality;
28068
+ if (!profile.qualityOptions.some((option) => option.value === selectedQuality)) throw new Error("该 Provider 不支持所选清晰度");
28069
+ if (input.resolution !== void 0 && profile.resolutionOptions !== void 0 && !profile.resolutionOptions[input.ratio]?.some((option) => option.value === input.resolution)) throw new Error("该比例不支持所选分辨率");
28070
+ if (input.generationQuality !== void 0 && input.provider === "openai-compat" && !profile.generationQualityOptions?.some((option) => option.value === input.generationQuality)) throw new Error("该 Provider 不支持所选生成质量");
28071
+ if ((input.resolution !== void 0 || input.generationQuality !== void 0) && input.provider !== "openai" && input.provider !== "openai-compat") throw new Error("该 Provider 不支持独立的 OpenAI 输出参数");
28072
+ if (input.watermark !== void 0 && input.watermark !== "default" && studioWatermarkCapability(profile, input.mode).status !== "supported") throw new Error("该渠道尚未确认当前操作的水印控制,请刷新工作台");
27708
28073
  if (input.mode === "edit" && !profile.supportsEditing) throw new Error("该 Provider 暂不支持图生图");
27709
28074
  }
27710
28075
  async function readStudioReference(ctx, reference, signal) {
@@ -27808,6 +28173,10 @@ function parseStudioGenerateRequest(value) {
27808
28173
  const model = requiredText(input.model, "请选择模型", 200);
27809
28174
  const ratio = requiredText(input.ratio, "请选择比例", 32);
27810
28175
  const quality = requiredText(input.quality, "请选择清晰度", 32);
28176
+ const resolution = input.resolution === void 0 ? void 0 : requiredText(input.resolution, "请选择分辨率", 32);
28177
+ const generationQuality = input.generationQuality === void 0 ? void 0 : requiredText(input.generationQuality, "请选择生成质量", 100);
28178
+ const watermark = input.watermark === void 0 ? void 0 : STUDIO_WATERMARK_VALUES.find((value) => value === input.watermark);
28179
+ if (input.watermark !== void 0 && watermark === void 0) throw new Error("请选择有效的水印选项");
27811
28180
  const references = (Array.isArray(input.references) ? input.references : input.reference !== void 0 ? [input.reference] : []).map(parseReference);
27812
28181
  if (input.mode === "edit" && references.length === 0) throw new Error("图生图需要至少一张参考图");
27813
28182
  if (references.length > 5) throw new Error("最多支持上传 5 张参考图");
@@ -27824,6 +28193,9 @@ function parseStudioGenerateRequest(value) {
27824
28193
  prompt,
27825
28194
  ratio,
27826
28195
  quality,
28196
+ ...resolution === void 0 ? {} : { resolution },
28197
+ ...generationQuality === void 0 ? {} : { generationQuality },
28198
+ ...watermark === void 0 ? {} : { watermark },
27827
28199
  ...count !== void 0 ? { count } : {},
27828
28200
  ...references.length > 0 ? {
27829
28201
  references,
@@ -28501,6 +28873,9 @@ function apply(ctx, config = {}) {
28501
28873
  }
28502
28874
  const size = args.size ?? active.imageSize;
28503
28875
  const arkOptions = active.provider === "seedream" ? active.arkOptions : void 0;
28876
+ if (active.provider === "openai") validateOfficialSize(active.model, size);
28877
+ const quality = active.provider === "openai-compat" ? compatRequestQuality(current(), args.quality) : active.provider === "openai" ? args.quality : void 0;
28878
+ if (active.provider === "openai" && quality !== void 0 && !officialQualityOptions(active.model).some((option) => option.value === quality)) throw new Error("该 OpenAI 模型不支持所选生成质量");
28504
28879
  return saveGenerated(ctx, await generateOpenAICompatibleImage({
28505
28880
  provider: active.provider,
28506
28881
  apiKey: credential,
@@ -28510,8 +28885,10 @@ function apply(ctx, config = {}) {
28510
28885
  size,
28511
28886
  maxBytes: ctx.attachments.imageLimits.maxImageBytes,
28512
28887
  signal: exec.signal,
28888
+ ...quality === void 0 ? {} : { quality },
28889
+ ...active.provider === "openai-compat" ? { extraBody: current().openaiCompatExtraBody ?? {} } : {},
28513
28890
  ...arkOptions === void 0 ? {} : { arkOptions }
28514
- }), active.provider, active.model, size, current(), exec, knownWorkspaceRoots);
28891
+ }), active.provider, active.model, quality === void 0 ? size : `${size}, quality=${quality}`, current(), exec, knownWorkspaceRoots);
28515
28892
  };
28516
28893
  ctx.tools.register(defineTool({
28517
28894
  name: "generate_image",
@@ -28572,6 +28949,10 @@ function apply(ctx, config = {}) {
28572
28949
  type: "string",
28573
28950
  description: "Optional dimensions or size tier for OpenAI, Seedream, or DashScope; WIDTHxHEIGHT maps to aspect_ratio for xAI Grok."
28574
28951
  },
28952
+ quality: {
28953
+ type: "string",
28954
+ description: "Optional generation quality for OpenAI or compatible APIs, independent of pixel size (e.g. auto, low, medium, high). Omit to use the configured default."
28955
+ },
28575
28956
  workflow: {
28576
28957
  type: "string",
28577
28958
  description: "Optional name of the ComfyUI workflow to run; omit to use the active workflow from settings. Only meaningful when the ComfyUI provider is selected."
@@ -28643,6 +29024,10 @@ function apply(ctx, config = {}) {
28643
29024
  type: "string",
28644
29025
  description: "Optional dimensions or size tier for OpenAI, Seedream, or DashScope; WIDTHxHEIGHT maps to aspect_ratio for xAI Grok."
28645
29026
  },
29027
+ quality: {
29028
+ type: "string",
29029
+ description: "Optional OpenAI or compatible generation quality, applied to every item independently of size."
29030
+ },
28646
29031
  workflow: {
28647
29032
  type: "string",
28648
29033
  description: "Optional name of the ComfyUI workflow to run; omit to use the active workflow from settings."
@@ -28780,6 +29165,7 @@ function apply(ctx, config = {}) {
28780
29165
  ...args.aspect_ratio !== void 0 ? { aspect_ratio: args.aspect_ratio } : {},
28781
29166
  ...args.image_size !== void 0 ? { image_size: args.image_size } : {},
28782
29167
  ...args.size !== void 0 ? { size: args.size } : {},
29168
+ ...args.quality !== void 0 ? { quality: args.quality } : {},
28783
29169
  ...args.workflow !== void 0 ? { workflow: args.workflow } : {}
28784
29170
  }, exec);
28785
29171
  images.push({
@@ -28883,6 +29269,10 @@ function apply(ctx, config = {}) {
28883
29269
  type: "string",
28884
29270
  description: "Optional output size for OpenAI, Seedream, or DashScope; WIDTHxHEIGHT maps to aspect_ratio for xAI Grok."
28885
29271
  },
29272
+ quality: {
29273
+ type: "string",
29274
+ description: "Optional generation quality for OpenAI or compatible edits, independent of size."
29275
+ },
28886
29276
  workflow: {
28887
29277
  type: "string",
28888
29278
  description: "Optional name of the ComfyUI workflow to run; omit to use the active workflow from settings. Only meaningful when the ComfyUI provider is selected."
@@ -28962,6 +29352,9 @@ function apply(ctx, config = {}) {
28962
29352
  }
28963
29353
  const size = args.size ?? active.imageSize;
28964
29354
  if (active.provider === "openai" || active.provider === "openai-compat" || active.provider === "xai" || active.provider === "zhipu") {
29355
+ if (active.provider === "openai") validateOfficialSize(active.model, size);
29356
+ const quality = active.provider === "openai-compat" ? compatRequestQuality(current(), args.quality) : active.provider === "openai" ? args.quality : void 0;
29357
+ if (active.provider === "openai" && quality !== void 0 && !officialQualityOptions(active.model).some((option) => option.value === quality)) throw new Error("该 OpenAI 模型不支持所选生成质量");
28965
29358
  const xaiExtraBody = active.provider === "xai" ? xaiToolParameters({
28966
29359
  ...args.aspect_ratio !== void 0 ? { aspectRatio: args.aspect_ratio } : {},
28967
29360
  ...args.image_size !== void 0 ? { imageSize: args.image_size } : {},
@@ -28974,16 +29367,18 @@ function apply(ctx, config = {}) {
28974
29367
  prompt: args.prompt,
28975
29368
  sourceImages,
28976
29369
  ...active.provider === "xai" ? {} : { size },
29370
+ ...quality === void 0 ? {} : { quality },
28977
29371
  maxBytes: ctx.attachments.imageLimits.maxImageBytes,
28978
29372
  signal: exec.signal,
28979
29373
  ...active.provider === "openai-compat" ? {
28980
29374
  editFormat: active.editFormat,
28981
- editExtra: active.editExtra
29375
+ editExtra: active.editExtra,
29376
+ editSizeMode: current().openaiCompatEditSizeMode ?? "auto"
28982
29377
  } : active.provider === "xai" ? {
28983
29378
  editFormat: "xaiJson",
28984
29379
  ...xaiExtraBody === void 0 ? {} : { extraBody: xaiExtraBody }
28985
29380
  } : {}
28986
- }), active.provider, active.model, active.provider === "xai" ? xaiExtraBody?.aspect_ratio ?? "auto" : size, current(), exec, knownWorkspaceRoots);
29381
+ }), active.provider, active.model, active.provider === "xai" ? xaiExtraBody?.aspect_ratio ?? "auto" : quality === void 0 ? size : `${size}, quality=${quality}`, current(), exec, knownWorkspaceRoots);
28987
29382
  }
28988
29383
  if (active.provider === "seedream") return saveGenerated(ctx, await editSeedreamImage({
28989
29384
  apiKey: credential,