dsh-image-gen 0.6.7 → 0.6.9

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.
Files changed (45) hide show
  1. package/LICENSE +201 -21
  2. package/README.en.md +391 -375
  3. package/README.md +391 -375
  4. package/README.zh-CN.md +3 -3
  5. package/docs/assets/readme/chat-generate.webp +0 -0
  6. package/docs/assets/readme/other-features.webp +0 -0
  7. package/lib/client.js +315 -49
  8. package/lib/client.js.map +1 -1
  9. package/lib/index.js +259 -49
  10. package/lib/types/client/conversation-image-revisions.d.ts +2 -2
  11. package/lib/types/client/gallery-view.d.ts +4 -0
  12. package/lib/types/client/index.d.ts +7 -1
  13. package/lib/types/client/multi-model-compare.d.ts +3 -3
  14. package/lib/types/client/provider-pill.d.ts +1 -1
  15. package/lib/types/client/studio-chat-panel.d.ts +23 -0
  16. package/lib/types/client/studio-view.d.ts +4 -0
  17. package/lib/types/client/tl/diagram-landing.d.ts +75 -0
  18. package/lib/types/client/tl/diagram-layout.d.ts +76 -0
  19. package/lib/types/client/tl/diagram-measure.d.ts +40 -0
  20. package/lib/types/config.d.ts +19 -2
  21. package/lib/types/diagram.d.ts +42 -0
  22. package/lib/types/openai-compatible.d.ts +3 -0
  23. package/lib/types/seedream.d.ts +7 -0
  24. package/lib/types/shared.d.ts +55 -4
  25. package/lib/types/studio-chat.d.ts +48 -0
  26. package/lib/types/studio.d.ts +3 -2
  27. package/lib/types/subscription/manager.d.ts +15 -0
  28. package/lib/types/subscription/vendors/antigravity.d.ts +10 -1
  29. package/lib/types/subscription/vendors/codex.d.ts +2 -0
  30. package/lib/types/subscription/vendors/grok.d.ts +2 -0
  31. package/lib/types/subscription.d.ts +7 -5
  32. package/package.json +11 -9
  33. package/skills/install-dsh-image-gen/SKILL.md +22 -22
  34. package/lib/types/client/inspiration-api.d.ts +0 -10
  35. package/lib/types/client/inspiration-detail.d.ts +0 -14
  36. package/lib/types/client/tldraw/generated/tldraw-assets.d.ts +0 -12
  37. package/lib/types/client/tldraw/generated/tldraw-css.d.ts +0 -1
  38. package/lib/types/client/tldraw/tldraw-style.d.ts +0 -1
  39. package/lib/types/client/tldraw/tldraw-workspace.d.ts +0 -1
  40. package/lib/types/inspiration/cache.d.ts +0 -20
  41. package/lib/types/inspiration/downloader.d.ts +0 -27
  42. package/lib/types/inspiration/routes.d.ts +0 -13
  43. package/lib/types/inspiration/sources.d.ts +0 -33
  44. package/lib/types/inspiration/types.d.ts +0 -65
  45. package/lib/types/inspiration/updater.d.ts +0 -7
package/lib/index.js CHANGED
@@ -6006,6 +6006,15 @@ const CLOUD_IMAGE_PROVIDERS = [
6006
6006
  "zhipu"
6007
6007
  ];
6008
6008
  /**
6009
+ * Providers the browser workbench can drive: the BYOK cloud set plus the
6010
+ * logged-in subscription channels. ComfyUI stays out; it has its own workflow
6011
+ * pipeline and no shared request shape.
6012
+ */
6013
+ const STUDIO_PROVIDERS = [...CLOUD_IMAGE_PROVIDERS, ...SUBSCRIPTION_PROVIDERS];
6014
+ /** Timeout for one subscription image call, shared by the tool path and the
6015
+ * studio route so neither drifts from the other. */
6016
+ const SUBSCRIPTION_TIMEOUT_MS = 3e5;
6017
+ /**
6009
6018
  * Credential references resolved through the DSH Credentials service (BYOK).
6010
6019
  * These are POSIX-style reference names, not environment variables: the host
6011
6020
  * layers the process environment and its managed store behind them, so a name
@@ -6068,6 +6077,33 @@ const DEFAULT_ZHIPU_BASE_URL = "https://open.bigmodel.cn/api/paas/v4";
6068
6077
  const DEFAULT_COMFYUI_BASE_URL = "http://127.0.0.1:8188";
6069
6078
  const DEFAULT_COMFYUI_TIMEOUT_MS = 3e5;
6070
6079
  const DEFAULT_COMFYUI_WORKFLOW_LABEL = "API workflow";
6080
+ /** Content types Ark's Seedream endpoint can return. */
6081
+ const ARK_OUTPUT_FORMATS = ["png", "jpeg"];
6082
+ /** Whether Ark stamps an "AI generated" watermark on the result. */
6083
+ const ARK_BACKGROUND_MODES = ["opaque", "transparent"];
6084
+ /**
6085
+ * Map the Ark output controls onto request-body fields.
6086
+ *
6087
+ * `background` is opt-in per call site because Ark restricts `transparent` to
6088
+ * image-to-image with a single alpha-bearing reference and rejects the whole
6089
+ * request otherwise:
6090
+ *
6091
+ * text-to-image + transparent → 400 InvalidParameter
6092
+ * "transparent background requires exactly one input image"
6093
+ *
6094
+ * So the generation endpoint must never carry it, and that caller passes
6095
+ * `{ background: false }`. `background: opaque` is never emitted anywhere: it
6096
+ * is already Ark's default, so sending it would change nothing while adding a
6097
+ * field only the edit path can act on.
6098
+ */
6099
+ function arkOutputBody(options, { background = true } = {}) {
6100
+ if (options === void 0) return {};
6101
+ const body = {};
6102
+ if (options.outputFormat !== void 0) body.output_format = options.outputFormat;
6103
+ if (options.watermark !== void 0) body.watermark = options.watermark;
6104
+ if (background && options.background === "transparent") body.background = "transparent";
6105
+ return body;
6106
+ }
6071
6107
  /** Default model names. */
6072
6108
  const DEFAULT_GOOGLE_MODEL = "gemini-3.1-flash-image";
6073
6109
  const DEFAULT_OPENAI_MODEL = "gpt-image-2";
@@ -6161,6 +6197,9 @@ const Config = z.object({
6161
6197
  openaiCompatEditExtra: z.dict(z.any()).default({}),
6162
6198
  seedreamBaseURL: z.string().default(DEFAULT_SEEDREAM_BASE_URL),
6163
6199
  seedreamModel: z.string().default(DEFAULT_SEEDREAM_MODEL),
6200
+ seedreamOutputFormat: z.union(ARK_OUTPUT_FORMATS).default("jpeg"),
6201
+ seedreamWatermark: z.boolean().default(true),
6202
+ seedreamBackground: z.union(ARK_BACKGROUND_MODES).default("opaque"),
6164
6203
  dashscopeEndpoint: z.string().default(DEFAULT_DASHSCOPE_ENDPOINT),
6165
6204
  dashscopeModel: z.string().default(DEFAULT_DASHSCOPE_MODEL),
6166
6205
  xaiBaseURL: z.string().default(DEFAULT_XAI_BASE_URL),
@@ -6205,13 +6244,21 @@ function resolveProvider(config) {
6205
6244
  editExtra: config.openaiCompatEditExtra ?? {}
6206
6245
  };
6207
6246
  }
6208
- case "seedream": return {
6209
- provider: "seedream",
6210
- apiKeyEnv: SEEDREAM_API_KEY_ENV,
6211
- model: config.seedreamModel ?? "doubao-seedream-5-0-260128",
6212
- baseURL: config.seedreamBaseURL ?? "https://ark.cn-beijing.volces.com/api/v3",
6213
- imageSize: "2K"
6214
- };
6247
+ case "seedream": {
6248
+ const seedreamBackground = config.seedreamBackground ?? "opaque";
6249
+ return {
6250
+ provider: "seedream",
6251
+ apiKeyEnv: SEEDREAM_API_KEY_ENV,
6252
+ model: config.seedreamModel ?? "doubao-seedream-5-0-260128",
6253
+ baseURL: config.seedreamBaseURL ?? "https://ark.cn-beijing.volces.com/api/v3",
6254
+ imageSize: "2K",
6255
+ arkOptions: {
6256
+ outputFormat: seedreamBackground === "transparent" ? "png" : config.seedreamOutputFormat ?? "jpeg",
6257
+ watermark: config.seedreamWatermark ?? true,
6258
+ background: seedreamBackground
6259
+ }
6260
+ };
6261
+ }
6215
6262
  case "dashscope": return {
6216
6263
  provider: "dashscope",
6217
6264
  apiKeyEnv: DASHSCOPE_API_KEY_ENV,
@@ -7510,7 +7557,7 @@ async function editDashScopeImage(options) {
7510
7557
  model: options.model,
7511
7558
  input: { messages: [{
7512
7559
  role: "user",
7513
- content: [...options.sourceImages.map((sourceImage) => ({ image: toDataUrl$2(sourceImage) })), { text: options.prompt }]
7560
+ content: [...options.sourceImages.map((sourceImage) => ({ image: toDataUrl$3(sourceImage) })), { text: options.prompt }]
7514
7561
  }] },
7515
7562
  parameters: {
7516
7563
  prompt_extend: true,
@@ -7527,7 +7574,7 @@ function formatSize(size) {
7527
7574
  if (size === void 0 || size.length === 0) return void 0;
7528
7575
  return size.replace("x", "*");
7529
7576
  }
7530
- function toDataUrl$2(image) {
7577
+ function toDataUrl$3(image) {
7531
7578
  return `data:${image.mediaType};base64,${Buffer.from(image.data).toString("base64")}`;
7532
7579
  }
7533
7580
  async function requestQwenImage(options) {
@@ -8109,7 +8156,10 @@ async function generateOpenAICompatibleImage(input) {
8109
8156
  model: input.model,
8110
8157
  prompt: input.prompt,
8111
8158
  size: input.size,
8112
- ...input.provider === "seedream" ? { response_format: "url" } : {}
8159
+ ...input.provider === "seedream" ? {
8160
+ response_format: "url",
8161
+ ...arkOutputBody(input.arkOptions, { background: false })
8162
+ } : {}
8113
8163
  })
8114
8164
  }), input.provider, input);
8115
8165
  }
@@ -8117,7 +8167,7 @@ async function editOpenAICompatibleImage(input) {
8117
8167
  if (input.editFormat === "jsonImageUrlArray") {
8118
8168
  const body = {
8119
8169
  model: input.model,
8120
- images: input.sourceImages.map((sourceImage) => ({ image_url: toDataUrl$1(sourceImage) })),
8170
+ images: input.sourceImages.map((sourceImage) => ({ image_url: toDataUrl$2(sourceImage) })),
8121
8171
  prompt: input.prompt,
8122
8172
  n: 1,
8123
8173
  size: "auto",
@@ -8178,7 +8228,7 @@ function imageEndpoint$1(baseURL, operation) {
8178
8228
  throw new Error("Image endpoint must be an absolute URL");
8179
8229
  }
8180
8230
  }
8181
- function toDataUrl$1(image) {
8231
+ function toDataUrl$2(image) {
8182
8232
  return `data:${image.mediaType};base64,${Buffer.from(image.data).toString("base64")}`;
8183
8233
  }
8184
8234
  function firstImage$1(value) {
@@ -8295,8 +8345,9 @@ async function editSeedreamImage(input) {
8295
8345
  body: JSON.stringify({
8296
8346
  model: input.model,
8297
8347
  prompt: input.prompt,
8298
- image: input.sourceImages.map(toDataUrl),
8348
+ image: input.sourceImages.map(toDataUrl$1),
8299
8349
  ...input.size === void 0 || input.size.length === 0 ? {} : { size: input.size },
8350
+ ...arkOutputBody(input.arkOptions),
8300
8351
  response_format: "b64_json"
8301
8352
  })
8302
8353
  });
@@ -8323,7 +8374,7 @@ function imageEndpoint(baseURL) {
8323
8374
  throw new Error("Seedream image endpoint must be an absolute URL");
8324
8375
  }
8325
8376
  }
8326
- function toDataUrl(image) {
8377
+ function toDataUrl$1(image) {
8327
8378
  return `data:${image.mediaType};base64,${Buffer.from(image.data).toString("base64")}`;
8328
8379
  }
8329
8380
  function firstImage(value) {
@@ -8598,6 +8649,8 @@ const CODEX_TOKEN = "https://auth.openai.com/oauth/token";
8598
8649
  const CODEX_SCOPE = "openid profile email offline_access api.connectors.read api.connectors.invoke";
8599
8650
  /** Where the ChatGPT subscription image request goes. */
8600
8651
  const CODEX_IMAGE_URL = "https://chatgpt.com/backend-api/codex/images/generations";
8652
+ /** Where the ChatGPT subscription image edit request goes. */
8653
+ const CODEX_IMAGE_EDIT_URL = "https://chatgpt.com/backend-api/codex/images/edits";
8601
8654
  /** The model served by this endpoint (probed live: gpt-image-2.5-flare works
8602
8655
  * on the ChatGPT internal generations route as of 2026-09-14). */
8603
8656
  const CODEX_IMAGE_MODEL = "gpt-image-2.5-flare";
@@ -8695,6 +8748,8 @@ const GROK_TOKEN = "https://auth.x.ai/oauth2/token";
8695
8748
  const GROK_SCOPE = "openid profile email offline_access grok-cli:access api:access conversations:read conversations:write";
8696
8749
  /** Where the Grok subscription image request goes. */
8697
8750
  const GROK_IMAGE_URL = "https://api.x.ai/v1/images/generations";
8751
+ /** Where the Grok subscription image edit request goes. */
8752
+ const GROK_IMAGE_EDIT_URL = "https://api.x.ai/v1/images/edits";
8698
8753
  /** The model served by this endpoint. */
8699
8754
  const GROK_IMAGE_MODEL = "grok-imagine-image-2.0";
8700
8755
  /** Public client id of the grok-cli; vendor-fixed redirect on 127.0.0.1:56121. */
@@ -9039,15 +9094,22 @@ function platformOf() {
9039
9094
  * Build the streamGenerateContent request body for one image generation.
9040
9095
  * Ported from the reference buildImageRequest + the wrapping envelope: no
9041
9096
  * tools, no systemInstruction, no thinkingConfig; responseModalities asks
9042
- * for TEXT+IMAGE.
9097
+ * for TEXT+IMAGE. Reference images (edits) ride along as inlineData parts
9098
+ * before the text part, mirroring the reference implementation.
9043
9099
  */
9044
9100
  function antigravityImageBody(options) {
9045
9101
  const imageConfig = { aspectRatio: options.aspectRatio !== void 0 && ASPECT_RATIOS.includes(options.aspectRatio) ? options.aspectRatio : "1:1" };
9046
9102
  if (options.hd === true) imageConfig.imageSize = "4K";
9103
+ const parts = [];
9104
+ for (const image of options.referenceImages ?? []) parts.push({ inlineData: {
9105
+ mimeType: image.mediaType,
9106
+ data: Buffer.from(image.data).toString("base64")
9107
+ } });
9108
+ parts.push({ text: options.prompt });
9047
9109
  return {
9048
9110
  contents: [{
9049
9111
  role: "user",
9050
- parts: [{ text: options.prompt }]
9112
+ parts
9051
9113
  }],
9052
9114
  generationConfig: {
9053
9115
  candidateCount: 1,
@@ -9081,7 +9143,8 @@ async function antigravityGenerateImage(options) {
9081
9143
  const inner = antigravityImageBody({
9082
9144
  prompt: options.prompt,
9083
9145
  ...options.aspectRatio !== void 0 ? { aspectRatio: options.aspectRatio } : {},
9084
- ...options.hd !== void 0 ? { hd: options.hd } : {}
9146
+ ...options.hd !== void 0 ? { hd: options.hd } : {},
9147
+ ...options.referenceImages !== void 0 ? { referenceImages: options.referenceImages } : {}
9085
9148
  });
9086
9149
  const body = antigravityEnvelope(options.projectId, inner);
9087
9150
  let lastError = "no endpoint succeeded";
@@ -9296,6 +9359,8 @@ var SubscriptionManager = class {
9296
9359
  const session = await this.ensureFresh(vendor);
9297
9360
  const text = prompt.trim();
9298
9361
  if (text.length === 0) throw new Error("prompt must be a non-empty string");
9362
+ const references = options.referenceImages ?? [];
9363
+ if (references.length > 5) throw new Error(`订阅生图最多支持 ${String(5)} 张参考图,当前 ${String(references.length)} 张`);
9299
9364
  if (vendor === "antigravity") {
9300
9365
  const projectId = await this.ensureAntigravityProject(session);
9301
9366
  const aspectRatio = antigravityAspectRatioOf(options.size);
@@ -9305,6 +9370,7 @@ var SubscriptionManager = class {
9305
9370
  prompt: text,
9306
9371
  ...aspectRatio !== void 0 ? { aspectRatio } : {},
9307
9372
  hd: options.quality === "hd" || options.quality === "high",
9373
+ ...references.length > 0 ? { referenceImages: references } : {},
9308
9374
  ...options.signal !== void 0 ? { signal: options.signal } : {}
9309
9375
  })).b64 }];
9310
9376
  }
@@ -9312,16 +9378,17 @@ var SubscriptionManager = class {
9312
9378
  let headers;
9313
9379
  let body;
9314
9380
  if (vendor === "codex") {
9315
- url = CODEX_IMAGE_URL;
9381
+ url = references.length > 0 ? CODEX_IMAGE_EDIT_URL : CODEX_IMAGE_URL;
9316
9382
  headers = codexIdentityHeaders(session);
9317
9383
  body = {
9318
9384
  prompt: text,
9319
9385
  model: CODEX_IMAGE_MODEL,
9386
+ ...references.length > 0 ? { images: references.map((image) => ({ image_url: toDataUrl(image) })) } : {},
9320
9387
  ...options.size !== void 0 && options.size.length > 0 ? { size: options.size } : {},
9321
9388
  ...options.quality !== void 0 && options.quality.length > 0 ? { quality: options.quality } : {}
9322
9389
  };
9323
9390
  } else {
9324
- url = GROK_IMAGE_URL;
9391
+ url = references.length > 0 ? GROK_IMAGE_EDIT_URL : GROK_IMAGE_URL;
9325
9392
  headers = grokIdentityHeaders(session);
9326
9393
  const aspect = {
9327
9394
  "1024x1024": "1:1",
@@ -9334,6 +9401,10 @@ var SubscriptionManager = class {
9334
9401
  prompt: text,
9335
9402
  model: GROK_IMAGE_MODEL,
9336
9403
  response_format: "b64_json",
9404
+ ...references.length > 0 ? { images: references.map((image) => ({
9405
+ type: "image_url",
9406
+ image_url: toDataUrl(image)
9407
+ })) } : {},
9337
9408
  ...options.size !== void 0 && aspect[options.size] !== void 0 ? { aspect_ratio: aspect[options.size] } : {},
9338
9409
  ...level !== void 0 ? { quality: level } : {}
9339
9410
  };
@@ -9380,6 +9451,10 @@ function antigravityAspectRatioOf(size) {
9380
9451
  if (mapped !== void 0) return mapped;
9381
9452
  return /^\d+:\d+$/.test(size) ? size : "1:1";
9382
9453
  }
9454
+ /** Encode one reference image as the data URL the edit endpoints accept. */
9455
+ function toDataUrl(image) {
9456
+ return `data:${image.mediaType};base64,${Buffer.from(image.data).toString("base64")}`;
9457
+ }
9383
9458
  /** Response parsing: both vendors reply in the same `{data:[{b64_json}]}` shape. */
9384
9459
  function parseImages(payload) {
9385
9460
  const body = typeof payload === "object" && payload !== null ? payload : {};
@@ -9524,20 +9599,21 @@ function jsonError$3(res, status, code) {
9524
9599
  }
9525
9600
  //#endregion
9526
9601
  //#region lib/types/subscription.js
9527
- /** Timeout for one subscription image call, mirroring the other adapters. */
9528
- const SUBSCRIPTION_TIMEOUT_MS = 3e5;
9529
9602
  /**
9530
9603
  * Generate one image through a logged-in subscription account. Returns the
9531
9604
  * same `{ data, mediaType }` contract as the API-key adapters so the caller
9532
- * can feed it straight into `saveGenerated`.
9605
+ * can feed it straight into `saveGenerated`. With sourceImages the call is
9606
+ * an edit: the vendor layer picks the channel's edit endpoint.
9533
9607
  */
9534
9608
  async function generateSubscriptionImage(options) {
9535
9609
  const { manager, provider, prompt, maxBytes, signal } = options;
9536
9610
  const size = options.size?.trim();
9611
+ const sourceImages = options.sourceImages ?? [];
9537
9612
  const result = await withTimeout(manager.generate({
9538
9613
  vendor: vendorOf(provider),
9539
9614
  prompt,
9540
9615
  ...size !== void 0 && size.length > 0 ? { size } : {},
9616
+ ...sourceImages.length > 0 ? { referenceImages: sourceImages } : {},
9541
9617
  signal
9542
9618
  }), signal);
9543
9619
  const first = Array.isArray(result) ? result[0] : void 0;
@@ -9552,10 +9628,6 @@ async function generateSubscriptionImage(options) {
9552
9628
  ...typeof first.revisedPrompt === "string" && first.revisedPrompt.length > 0 ? { revisedPrompt: first.revisedPrompt } : {}
9553
9629
  };
9554
9630
  }
9555
- /** Subscription channels are prompt-only today; edit_image is rejected outright. */
9556
- function assertSubscriptionEditUnsupported(provider) {
9557
- throw new Error(`edit_image with ${SUBSCRIPTION_PROVIDER_DISPLAY_NAMES[provider]} is not supported yet: the subscription channel only accepts text prompts. Retry with an API-key provider (for example google or openai) through the provider argument.`);
9558
- }
9559
9631
  /** Run one promise with the subscription timeout and the call's abort signal. */
9560
9632
  async function withTimeout(promise, signal) {
9561
9633
  let timer;
@@ -20467,24 +20539,63 @@ const RATIO_LABELS = {
20467
20539
  "9:16": "9:16 竖屏"
20468
20540
  };
20469
20541
  /** Return only browser-safe capability data. */
20470
- async function describeStudio(ctx, config) {
20542
+ async function describeStudio(ctx, config, subscriptions) {
20471
20543
  const configuredEntries = await Promise.all(CLOUD_IMAGE_PROVIDERS.map(async (provider) => {
20472
20544
  return [provider, await resolveApiKey(ctx, provider) !== void 0];
20473
20545
  }));
20474
20546
  const configured = Object.fromEntries(configuredEntries);
20475
- const profiles = CLOUD_IMAGE_PROVIDERS.map((provider) => studioProfile(config, provider, configured[provider]));
20547
+ const cloudProfiles = CLOUD_IMAGE_PROVIDERS.map((provider) => studioProfile(config, provider, configured[provider]));
20548
+ const subStatuses = await Promise.all(SUBSCRIPTION_PROVIDERS.map(async (provider) => {
20549
+ return [provider, subscriptions !== void 0 && (await subscriptions.loginStatus(vendorOf(provider))).state === "logged-in"];
20550
+ }));
20551
+ const subProfiles = SUBSCRIPTION_PROVIDERS.map((provider) => {
20552
+ return subscriptionStudioProfile(provider, subStatuses.find((entry) => entry[0] === provider)?.[1] === true);
20553
+ });
20554
+ const profiles = [...cloudProfiles, ...subProfiles];
20476
20555
  const preferred = config.provider;
20477
20556
  return {
20478
20557
  providers: profiles,
20479
- activeProvider: preferred !== void 0 && cloudProvider$1(preferred) ? preferred : profiles.find((profile) => profile.configured)?.provider ?? "google"
20558
+ activeProvider: isStudioPreferred(preferred) ? preferred : profiles.find((profile) => profile.configured)?.provider ?? "google"
20559
+ };
20560
+ }
20561
+ /** The persisted default provider is usable directly when the workbench can drive it. */
20562
+ function isStudioPreferred(preferred) {
20563
+ return preferred !== void 0 && CLOUD_IMAGE_PROVIDERS.includes(preferred) || preferred !== void 0 && SUBSCRIPTION_PROVIDERS.includes(preferred);
20564
+ }
20565
+ /** Fixed parameter shape for subscription channels: one "channel default" pair. */
20566
+ function subscriptionStudioProfile(provider, signedIn) {
20567
+ const channelDefault = {
20568
+ value: "auto",
20569
+ label: "通道默认"
20570
+ };
20571
+ return {
20572
+ provider,
20573
+ label: SUBSCRIPTION_DISPLAY_LABELS[provider],
20574
+ model: DEFAULT_SUBSCRIPTION_MODELS[provider],
20575
+ configured: signedIn,
20576
+ supportsEditing: true,
20577
+ ratioOptions: [channelDefault],
20578
+ qualityOptions: [channelDefault],
20579
+ defaultRatio: "auto",
20580
+ defaultQuality: "auto"
20480
20581
  };
20481
20582
  }
20583
+ /** Short display names for the workbench subscription rows. */
20584
+ const SUBSCRIPTION_DISPLAY_LABELS = {
20585
+ "chatgpt-sub": "ChatGPT 订阅",
20586
+ "grok-sub": "Grok 订阅",
20587
+ "google-sub": "Google 订阅"
20588
+ };
20482
20589
  /** Execute one validated browser workbench request using the existing provider adapters. */
20483
- async function generateFromStudio(ctx, config, input, signal, fallbackWorkspaceRoot) {
20590
+ async function generateFromStudio(ctx, config, input, signal, fallbackWorkspaceRoot, subscriptions) {
20591
+ if (isSubscriptionProvider(input.provider)) {
20592
+ if (subscriptions === void 0) throw new Error("订阅生图不可用:订阅管理器未初始化");
20593
+ assertAllowed(subscriptionStudioProfile(input.provider, true), input);
20594
+ return generateSubscriptionFromStudio(ctx, subscriptions, input, signal);
20595
+ }
20484
20596
  assertAllowed(studioProfile(config, input.provider, true), input);
20485
20597
  const active = resolveProvider(withProviderOverrides(config, input.provider, input.model));
20486
20598
  if (active.provider === "comfyui") throw new Error("ComfyUI 暂未接入工作台");
20487
- if (isSubscriptionProvider(active.provider)) throw new Error("订阅生图暂未接入工作台,请通过对话或设置切换默认 Provider 使用");
20488
20599
  const wired = active;
20489
20600
  const credential = await requireApiKey(ctx, input.provider);
20490
20601
  const rawRefs = input.references ?? (input.reference ? [input.reference] : []);
@@ -20555,7 +20666,8 @@ async function generateFromStudio(ctx, config, input, signal, fallbackWorkspaceR
20555
20666
  sourceImages,
20556
20667
  size,
20557
20668
  maxBytes: ctx.attachments.imageLimits.maxImageBytes,
20558
- signal
20669
+ signal,
20670
+ arkOptions: wired.arkOptions
20559
20671
  }) : await generateOpenAICompatibleImage({
20560
20672
  provider: "seedream",
20561
20673
  apiKey: credential,
@@ -20564,7 +20676,8 @@ async function generateFromStudio(ctx, config, input, signal, fallbackWorkspaceR
20564
20676
  prompt: input.prompt,
20565
20677
  size,
20566
20678
  maxBytes: ctx.attachments.imageLimits.maxImageBytes,
20567
- signal
20679
+ signal,
20680
+ arkOptions: wired.arkOptions
20568
20681
  });
20569
20682
  output = size;
20570
20683
  } else {
@@ -20672,6 +20785,76 @@ async function runPool(tasks, concurrency = 2) {
20672
20785
  await Promise.all(workers);
20673
20786
  return results;
20674
20787
  }
20788
+ /**
20789
+ * Subscription workbench generation: one prompt, channel-default parameters,
20790
+ * through the shared generateSubscriptionImage wrapper (timeout, b64 decode,
20791
+ * size check) so the downstream attachment flow is identical to API keys.
20792
+ */
20793
+ async function generateSubscriptionFromStudio(ctx, subscriptions, input, signal) {
20794
+ const provider = input.provider;
20795
+ const rawRefs = input.references ?? (input.reference ? [input.reference] : []);
20796
+ if (input.mode === "edit" && rawRefs.length === 0) throw new Error("图生图需要至少一张参考图");
20797
+ const sourceImages = input.mode === "edit" ? await Promise.all(rawRefs.map((ref) => readStudioReference(ctx, ref, signal))) : [];
20798
+ const startedAt = Date.now();
20799
+ const count = input.count ?? 1;
20800
+ const generateSingle = async (index) => {
20801
+ const generated = await generateSubscriptionImage({
20802
+ manager: subscriptions,
20803
+ provider,
20804
+ prompt: input.prompt,
20805
+ ...sourceImages.length > 0 ? { sourceImages } : {},
20806
+ maxBytes: ctx.attachments.imageLimits.maxImageBytes,
20807
+ signal
20808
+ });
20809
+ if (!ctx.attachments.imageLimits.mediaTypes.includes(generated.mediaType)) throw new Error(`当前 DSH 不支持保存 ${generated.mediaType} 图片`);
20810
+ return {
20811
+ attachment: await ctx.attachments.saveImage({
20812
+ data: generated.data,
20813
+ mediaType: generated.mediaType,
20814
+ name: count > 1 ? `studio-image-${index + 1}` : "studio-image"
20815
+ }),
20816
+ output: "通道默认"
20817
+ };
20818
+ };
20819
+ if (count === 1) {
20820
+ const single = await generateSingle(0);
20821
+ return subscriptionResponse(input, startedAt, 1, 0, [single], single);
20822
+ }
20823
+ const poolResults = await runPool(Array.from({ length: count }, (_, i) => () => generateSingle(i)), 2);
20824
+ const successes = [];
20825
+ const errors = [];
20826
+ for (let i = 0; i < poolResults.length; i++) {
20827
+ const r = poolResults[i];
20828
+ if (r.status === "fulfilled") successes.push(r.value);
20829
+ else errors.push({
20830
+ index: i,
20831
+ message: r.reason instanceof Error ? r.reason.message : String(r.reason)
20832
+ });
20833
+ }
20834
+ if (successes.length === 0) {
20835
+ const firstReason = poolResults[0] && poolResults[0].status === "rejected" ? poolResults[0].reason : /* @__PURE__ */ new Error("订阅生图全部失败");
20836
+ throw firstReason instanceof Error ? firstReason : new Error(String(firstReason));
20837
+ }
20838
+ const first = successes[0];
20839
+ return subscriptionResponse(input, startedAt, count, errors.length, successes, first, errors);
20840
+ }
20841
+ /** Assemble the subscription workbench response with exactOptionalPropertyTypes-safe spreads. */
20842
+ function subscriptionResponse(input, startedAt, requestedCount, failedCount, items, primary, errors) {
20843
+ return {
20844
+ attachment: primary.attachment,
20845
+ output: primary.output,
20846
+ provider: input.provider,
20847
+ model: input.model,
20848
+ prompt: input.prompt,
20849
+ createdAt: Date.now(),
20850
+ elapsedMs: Date.now() - startedAt,
20851
+ requestedCount,
20852
+ failedCount,
20853
+ items,
20854
+ ...primary.savedTo ? { savedTo: primary.savedTo } : {},
20855
+ ...errors !== void 0 && errors.length > 0 ? { errors } : {}
20856
+ };
20857
+ }
20675
20858
  function studioProfile(config, provider, configured) {
20676
20859
  let active;
20677
20860
  try {
@@ -20725,7 +20908,7 @@ function studioProfile(config, provider, configured) {
20725
20908
  function profile(provider, model, configured, ratioOptions, qualityOptions, defaultRatio, defaultQuality) {
20726
20909
  return {
20727
20910
  provider,
20728
- label: PROVIDER_DISPLAY_NAMES[provider],
20911
+ label: CLOUD_DISPLAY_LABELS[provider],
20729
20912
  model,
20730
20913
  configured,
20731
20914
  supportsEditing: true,
@@ -20735,6 +20918,16 @@ function profile(provider, model, configured, ratioOptions, qualityOptions, defa
20735
20918
  defaultQuality
20736
20919
  };
20737
20920
  }
20921
+ /** Display names for the BYOK cloud rows, mirroring the settings card. */
20922
+ const CLOUD_DISPLAY_LABELS = {
20923
+ google: "Google Gemini",
20924
+ openai: "OpenAI",
20925
+ "openai-compat": "OpenAI 兼容",
20926
+ seedream: "Seedream",
20927
+ dashscope: "DashScope",
20928
+ xai: "xAI Grok",
20929
+ zhipu: "智谱 GLM"
20930
+ };
20738
20931
  function option(value) {
20739
20932
  return {
20740
20933
  value,
@@ -20788,9 +20981,6 @@ function dashScopeSize(ratio) {
20788
20981
  "9:16": "928*1664"
20789
20982
  }[ratio] ?? "1024*1024";
20790
20983
  }
20791
- function cloudProvider$1(value) {
20792
- return CLOUD_IMAGE_PROVIDERS.includes(value);
20793
- }
20794
20984
  //#endregion
20795
20985
  //#region lib/types/studio-route.js
20796
20986
  /** Serve workbench capabilities and generation requests without exposing provider credentials. */
@@ -20804,8 +20994,10 @@ async function serveStudio(req, res, deps) {
20804
20994
  if (req.method !== "POST") return jsonError$1(res, 405, "method-not-allowed");
20805
20995
  if (!(req.headers["content-type"] ?? "").toLowerCase().startsWith("application/json")) return jsonError$1(res, 415, "json-required");
20806
20996
  const controller = new AbortController();
20807
- const upstreamTimeout = AbortSignal.timeout(15e4);
20808
- const signal = AbortSignal.any([controller.signal, upstreamTimeout]);
20997
+ const watchdog = new AbortController();
20998
+ const CLOUD_WATCHDOG_MS = 15e4;
20999
+ let subscriptionChannel = false;
21000
+ const signal = AbortSignal.any([controller.signal, watchdog.signal]);
20809
21001
  const onConnectionClose = () => {
20810
21002
  if (!res.writableEnded) controller.abort(/* @__PURE__ */ new Error("The browser closed the image generation request."));
20811
21003
  };
@@ -20821,11 +21013,15 @@ async function serveStudio(req, res, deps) {
20821
21013
  } catch (error) {
20822
21014
  return jsonError$1(res, 400, errorMessage(error, "invalid-request"));
20823
21015
  }
21016
+ subscriptionChannel = isSubscriptionProvider(input.provider);
21017
+ setTimeout(() => {
21018
+ watchdog.abort();
21019
+ }, subscriptionChannel ? SUBSCRIPTION_TIMEOUT_MS : CLOUD_WATCHDOG_MS).unref?.();
20824
21020
  const output = await deps.generate(input, signal);
20825
21021
  if (!res.headersSent && !res.writableEnded && !res.destroyed) json$1(res, 200, output);
20826
21022
  } catch (error) {
20827
21023
  if (res.headersSent || res.writableEnded || res.destroyed) return;
20828
- if (upstreamTimeout.aborted) return jsonError$1(res, 504, "上游生成超时(150 秒未返回),请检查网络或代理后重试");
21024
+ if (watchdog.signal.aborted) return jsonError$1(res, 504, subscriptionChannel ? "上游生成超时(订阅通道 300 秒未返回),请稍后重试" : "上游生成超时(150 秒未返回),请检查网络或代理后重试");
20829
21025
  jsonError$1(res, controller.signal.aborted ? 499 : 502, errorMessage(error, "generation-failed"));
20830
21026
  } finally {
20831
21027
  res.off("close", onConnectionClose);
@@ -20837,7 +21033,7 @@ function parseStudioGenerateRequest(value) {
20837
21033
  const input = record$1(value);
20838
21034
  if (input === void 0) throw new Error("请求格式无效");
20839
21035
  if (input.mode !== "generate" && input.mode !== "edit") throw new Error("请选择生成类型");
20840
- if (!cloudProvider(input.provider)) throw new Error("不支持该图像 Provider");
21036
+ if (!studioProvider(input.provider)) throw new Error("不支持该图像 Provider");
20841
21037
  const prompt = requiredText(input.prompt, "请输入提示词", 2e3);
20842
21038
  const model = requiredText(input.model, "请选择模型", 200);
20843
21039
  const ratio = requiredText(input.ratio, "请选择比例", 32);
@@ -20889,8 +21085,8 @@ function requiredText(value, message, maxLength) {
20889
21085
  if (text.length > maxLength) throw new Error(`${message}(最多 ${String(maxLength)} 个字符)`);
20890
21086
  return text;
20891
21087
  }
20892
- function cloudProvider(value) {
20893
- return typeof value === "string" && CLOUD_IMAGE_PROVIDERS.includes(value);
21088
+ function studioProvider(value) {
21089
+ return typeof value === "string" && STUDIO_PROVIDERS.includes(value);
20894
21090
  }
20895
21091
  function imageMediaType(value) {
20896
21092
  return value === "image/png" || value === "image/jpeg" || value === "image/webp" || value === "image/gif";
@@ -21386,7 +21582,7 @@ function apply(ctx, config = {}) {
21386
21582
  path: STUDIO_ROUTE,
21387
21583
  handler: (req, res) => serveStudio(req, res, {
21388
21584
  describe: async () => {
21389
- const base = await describeStudio(ctx, current());
21585
+ const base = await describeStudio(ctx, current(), subscriptionManager);
21390
21586
  const workspaces = await getDshWorkspacesFull().catch(() => []);
21391
21587
  const activeRoot = Array.from(knownWorkspaceRoots)[0] || workspaces[0]?.path || process.cwd();
21392
21588
  return {
@@ -21397,7 +21593,7 @@ function apply(ctx, config = {}) {
21397
21593
  },
21398
21594
  generate: (input, signal) => {
21399
21595
  const fallbackRoot = Array.from(knownWorkspaceRoots)[0] || process.cwd();
21400
- return generateFromStudio(ctx, current(), input, signal, fallbackRoot);
21596
+ return generateFromStudio(ctx, current(), input, signal, fallbackRoot, subscriptionManager);
21401
21597
  },
21402
21598
  maxBodyBytes: Math.ceil(ctx.attachments.imageLimits.maxImageBytes * 1.4 * 5) + 262144
21403
21599
  })
@@ -21523,6 +21719,7 @@ function apply(ctx, config = {}) {
21523
21719
  }), active.provider, active.model, size, current(), exec, knownWorkspaceRoots);
21524
21720
  }
21525
21721
  const size = args.size ?? active.imageSize;
21722
+ const arkOptions = active.provider === "seedream" ? active.arkOptions : void 0;
21526
21723
  return saveGenerated(ctx, await generateOpenAICompatibleImage({
21527
21724
  provider: active.provider,
21528
21725
  apiKey: credential,
@@ -21531,7 +21728,8 @@ function apply(ctx, config = {}) {
21531
21728
  prompt: args.prompt,
21532
21729
  size,
21533
21730
  maxBytes: ctx.attachments.imageLimits.maxImageBytes,
21534
- signal: exec.signal
21731
+ signal: exec.signal,
21732
+ ...arkOptions === void 0 ? {} : { arkOptions }
21535
21733
  }), active.provider, active.model, size, current(), exec, knownWorkspaceRoots);
21536
21734
  },
21537
21735
  presentResult: (_args, result) => imagePresentation(result)
@@ -21560,7 +21758,7 @@ function apply(ctx, config = {}) {
21560
21758
  "grok-sub",
21561
21759
  "google-sub"
21562
21760
  ],
21563
- description: "Optional provider for this call only (for example when the user asks to use a specific provider); omit to use the configured default. chatgpt-sub, grok-sub, and google-sub cannot edit images yet; the subscription channel is text-prompt only, so retry editing with an API-key provider instead."
21761
+ description: "Optional provider for this call only (for example when the user asks to use a specific provider); omit to use the configured default. chatgpt-sub, grok-sub, and google-sub edit images through the logged-in subscription account instead of an API key."
21564
21762
  },
21565
21763
  model: {
21566
21764
  type: "string",
@@ -21623,7 +21821,6 @@ function apply(ctx, config = {}) {
21623
21821
  output: imageOutput("Edited"),
21624
21822
  async execute(args, exec) {
21625
21823
  const active = resolveProvider(withProviderOverrides(current(), providerOverrideOf(args.provider), args.model));
21626
- if (active.provider === "chatgpt-sub" || active.provider === "grok-sub" || active.provider === "google-sub") assertSubscriptionEditUnsupported(active.provider);
21627
21824
  const canvasSelection = args.source === "canvas_selection";
21628
21825
  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");
21629
21826
  const sourceImages = canvasSelection ? await resolveCanvasSelectionReferences({
@@ -21660,6 +21857,18 @@ function apply(ctx, config = {}) {
21660
21857
  signal: exec.signal
21661
21858
  }), active.provider, workflow.name, "API workflow", current(), exec, knownWorkspaceRoots);
21662
21859
  }
21860
+ if (active.provider === "chatgpt-sub" || active.provider === "grok-sub" || active.provider === "google-sub") {
21861
+ if (sourceImages.length === 0) throw new Error("edit_image requires a reference image");
21862
+ return saveGenerated(ctx, await generateSubscriptionImage({
21863
+ manager: subscriptionManager,
21864
+ provider: active.provider,
21865
+ prompt: args.prompt,
21866
+ sourceImages,
21867
+ ...args.size !== void 0 ? { size: args.size } : {},
21868
+ maxBytes: ctx.attachments.imageLimits.maxImageBytes,
21869
+ signal: exec.signal
21870
+ }), active.provider, active.model, "subscription edit", current(), exec, knownWorkspaceRoots);
21871
+ }
21663
21872
  const credential = await requireApiKey(ctx, active.provider, "edit_image");
21664
21873
  if (active.provider === "google") {
21665
21874
  const aspectRatio = args.aspect_ratio ?? active.aspectRatio;
@@ -21699,7 +21908,8 @@ function apply(ctx, config = {}) {
21699
21908
  sourceImages,
21700
21909
  size,
21701
21910
  maxBytes: ctx.attachments.imageLimits.maxImageBytes,
21702
- signal: exec.signal
21911
+ signal: exec.signal,
21912
+ arkOptions: active.arkOptions
21703
21913
  }), active.provider, active.model, size, current(), exec, knownWorkspaceRoots);
21704
21914
  return saveGenerated(ctx, await editDashScopeImage({
21705
21915
  apiKey: credential,
@@ -1,10 +1,10 @@
1
1
  import type { ImageAttachmentRef } from '@deepseek-ai/dsh-attachment';
2
- import { type CloudImageProvider } from '../shared.js';
2
+ import { type StudioProvider } from '../shared.js';
3
3
  /** One regenerated version displayed in place of an original conversation image. */
4
4
  export interface ConversationImageRevision {
5
5
  attachment: ImageAttachmentRef;
6
6
  prompt: string;
7
- provider: CloudImageProvider;
7
+ provider: StudioProvider;
8
8
  model: string;
9
9
  output: string;
10
10
  createdAt: number;
@@ -192,6 +192,10 @@ export interface GalleryViewTabProps {
192
192
  useSession?: (selector: (state: any) => any) => any;
193
193
  useSessions?: (selector: (state: any) => any) => any;
194
194
  useWorkspaces?: (selector: (state: any) => any) => any;
195
+ /** Host credential-change notifier; forwarded to the studio workbench. */
196
+ credentialEvents?: {
197
+ listen(callback: () => void): () => void;
198
+ } | undefined;
195
199
  /**
196
200
  * Right-sidebar variant (DSH official `sidebar.right.pane.tab` seat). The
197
201
  * native conversation stays in the main area, so the composer must NOT be