dsh-image-gen 0.6.7 → 0.6.8

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 (40) hide show
  1. package/README.en.md +387 -375
  2. package/README.md +387 -375
  3. package/README.zh-CN.md +3 -3
  4. package/docs/assets/readme/chat-generate.webp +0 -0
  5. package/docs/assets/readme/other-features.webp +0 -0
  6. package/lib/client.js +146 -37
  7. package/lib/client.js.map +1 -1
  8. package/lib/index.js +200 -37
  9. package/lib/types/client/conversation-image-revisions.d.ts +2 -2
  10. package/lib/types/client/gallery-view.d.ts +4 -0
  11. package/lib/types/client/multi-model-compare.d.ts +3 -3
  12. package/lib/types/client/provider-pill.d.ts +1 -1
  13. package/lib/types/client/studio-chat-panel.d.ts +23 -0
  14. package/lib/types/client/studio-view.d.ts +4 -0
  15. package/lib/types/client/tl/diagram-landing.d.ts +75 -0
  16. package/lib/types/client/tl/diagram-layout.d.ts +76 -0
  17. package/lib/types/client/tl/diagram-measure.d.ts +40 -0
  18. package/lib/types/diagram.d.ts +42 -0
  19. package/lib/types/shared.d.ts +16 -4
  20. package/lib/types/studio-chat.d.ts +48 -0
  21. package/lib/types/studio.d.ts +3 -2
  22. package/lib/types/subscription/manager.d.ts +15 -0
  23. package/lib/types/subscription/vendors/antigravity.d.ts +10 -1
  24. package/lib/types/subscription/vendors/codex.d.ts +2 -0
  25. package/lib/types/subscription/vendors/grok.d.ts +2 -0
  26. package/lib/types/subscription.d.ts +7 -5
  27. package/package.json +10 -8
  28. package/skills/install-dsh-image-gen/SKILL.md +22 -22
  29. package/lib/types/client/inspiration-api.d.ts +0 -10
  30. package/lib/types/client/inspiration-detail.d.ts +0 -14
  31. package/lib/types/client/tldraw/generated/tldraw-assets.d.ts +0 -12
  32. package/lib/types/client/tldraw/generated/tldraw-css.d.ts +0 -1
  33. package/lib/types/client/tldraw/tldraw-style.d.ts +0 -1
  34. package/lib/types/client/tldraw/tldraw-workspace.d.ts +0 -1
  35. package/lib/types/inspiration/cache.d.ts +0 -20
  36. package/lib/types/inspiration/downloader.d.ts +0 -27
  37. package/lib/types/inspiration/routes.d.ts +0 -13
  38. package/lib/types/inspiration/sources.d.ts +0 -33
  39. package/lib/types/inspiration/types.d.ts +0 -65
  40. 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
@@ -7510,7 +7519,7 @@ async function editDashScopeImage(options) {
7510
7519
  model: options.model,
7511
7520
  input: { messages: [{
7512
7521
  role: "user",
7513
- content: [...options.sourceImages.map((sourceImage) => ({ image: toDataUrl$2(sourceImage) })), { text: options.prompt }]
7522
+ content: [...options.sourceImages.map((sourceImage) => ({ image: toDataUrl$3(sourceImage) })), { text: options.prompt }]
7514
7523
  }] },
7515
7524
  parameters: {
7516
7525
  prompt_extend: true,
@@ -7527,7 +7536,7 @@ function formatSize(size) {
7527
7536
  if (size === void 0 || size.length === 0) return void 0;
7528
7537
  return size.replace("x", "*");
7529
7538
  }
7530
- function toDataUrl$2(image) {
7539
+ function toDataUrl$3(image) {
7531
7540
  return `data:${image.mediaType};base64,${Buffer.from(image.data).toString("base64")}`;
7532
7541
  }
7533
7542
  async function requestQwenImage(options) {
@@ -8117,7 +8126,7 @@ async function editOpenAICompatibleImage(input) {
8117
8126
  if (input.editFormat === "jsonImageUrlArray") {
8118
8127
  const body = {
8119
8128
  model: input.model,
8120
- images: input.sourceImages.map((sourceImage) => ({ image_url: toDataUrl$1(sourceImage) })),
8129
+ images: input.sourceImages.map((sourceImage) => ({ image_url: toDataUrl$2(sourceImage) })),
8121
8130
  prompt: input.prompt,
8122
8131
  n: 1,
8123
8132
  size: "auto",
@@ -8178,7 +8187,7 @@ function imageEndpoint$1(baseURL, operation) {
8178
8187
  throw new Error("Image endpoint must be an absolute URL");
8179
8188
  }
8180
8189
  }
8181
- function toDataUrl$1(image) {
8190
+ function toDataUrl$2(image) {
8182
8191
  return `data:${image.mediaType};base64,${Buffer.from(image.data).toString("base64")}`;
8183
8192
  }
8184
8193
  function firstImage$1(value) {
@@ -8295,7 +8304,7 @@ async function editSeedreamImage(input) {
8295
8304
  body: JSON.stringify({
8296
8305
  model: input.model,
8297
8306
  prompt: input.prompt,
8298
- image: input.sourceImages.map(toDataUrl),
8307
+ image: input.sourceImages.map(toDataUrl$1),
8299
8308
  ...input.size === void 0 || input.size.length === 0 ? {} : { size: input.size },
8300
8309
  response_format: "b64_json"
8301
8310
  })
@@ -8323,7 +8332,7 @@ function imageEndpoint(baseURL) {
8323
8332
  throw new Error("Seedream image endpoint must be an absolute URL");
8324
8333
  }
8325
8334
  }
8326
- function toDataUrl(image) {
8335
+ function toDataUrl$1(image) {
8327
8336
  return `data:${image.mediaType};base64,${Buffer.from(image.data).toString("base64")}`;
8328
8337
  }
8329
8338
  function firstImage(value) {
@@ -8598,6 +8607,8 @@ const CODEX_TOKEN = "https://auth.openai.com/oauth/token";
8598
8607
  const CODEX_SCOPE = "openid profile email offline_access api.connectors.read api.connectors.invoke";
8599
8608
  /** Where the ChatGPT subscription image request goes. */
8600
8609
  const CODEX_IMAGE_URL = "https://chatgpt.com/backend-api/codex/images/generations";
8610
+ /** Where the ChatGPT subscription image edit request goes. */
8611
+ const CODEX_IMAGE_EDIT_URL = "https://chatgpt.com/backend-api/codex/images/edits";
8601
8612
  /** The model served by this endpoint (probed live: gpt-image-2.5-flare works
8602
8613
  * on the ChatGPT internal generations route as of 2026-09-14). */
8603
8614
  const CODEX_IMAGE_MODEL = "gpt-image-2.5-flare";
@@ -8695,6 +8706,8 @@ const GROK_TOKEN = "https://auth.x.ai/oauth2/token";
8695
8706
  const GROK_SCOPE = "openid profile email offline_access grok-cli:access api:access conversations:read conversations:write";
8696
8707
  /** Where the Grok subscription image request goes. */
8697
8708
  const GROK_IMAGE_URL = "https://api.x.ai/v1/images/generations";
8709
+ /** Where the Grok subscription image edit request goes. */
8710
+ const GROK_IMAGE_EDIT_URL = "https://api.x.ai/v1/images/edits";
8698
8711
  /** The model served by this endpoint. */
8699
8712
  const GROK_IMAGE_MODEL = "grok-imagine-image-2.0";
8700
8713
  /** Public client id of the grok-cli; vendor-fixed redirect on 127.0.0.1:56121. */
@@ -9039,15 +9052,22 @@ function platformOf() {
9039
9052
  * Build the streamGenerateContent request body for one image generation.
9040
9053
  * Ported from the reference buildImageRequest + the wrapping envelope: no
9041
9054
  * tools, no systemInstruction, no thinkingConfig; responseModalities asks
9042
- * for TEXT+IMAGE.
9055
+ * for TEXT+IMAGE. Reference images (edits) ride along as inlineData parts
9056
+ * before the text part, mirroring the reference implementation.
9043
9057
  */
9044
9058
  function antigravityImageBody(options) {
9045
9059
  const imageConfig = { aspectRatio: options.aspectRatio !== void 0 && ASPECT_RATIOS.includes(options.aspectRatio) ? options.aspectRatio : "1:1" };
9046
9060
  if (options.hd === true) imageConfig.imageSize = "4K";
9061
+ const parts = [];
9062
+ for (const image of options.referenceImages ?? []) parts.push({ inlineData: {
9063
+ mimeType: image.mediaType,
9064
+ data: Buffer.from(image.data).toString("base64")
9065
+ } });
9066
+ parts.push({ text: options.prompt });
9047
9067
  return {
9048
9068
  contents: [{
9049
9069
  role: "user",
9050
- parts: [{ text: options.prompt }]
9070
+ parts
9051
9071
  }],
9052
9072
  generationConfig: {
9053
9073
  candidateCount: 1,
@@ -9081,7 +9101,8 @@ async function antigravityGenerateImage(options) {
9081
9101
  const inner = antigravityImageBody({
9082
9102
  prompt: options.prompt,
9083
9103
  ...options.aspectRatio !== void 0 ? { aspectRatio: options.aspectRatio } : {},
9084
- ...options.hd !== void 0 ? { hd: options.hd } : {}
9104
+ ...options.hd !== void 0 ? { hd: options.hd } : {},
9105
+ ...options.referenceImages !== void 0 ? { referenceImages: options.referenceImages } : {}
9085
9106
  });
9086
9107
  const body = antigravityEnvelope(options.projectId, inner);
9087
9108
  let lastError = "no endpoint succeeded";
@@ -9296,6 +9317,8 @@ var SubscriptionManager = class {
9296
9317
  const session = await this.ensureFresh(vendor);
9297
9318
  const text = prompt.trim();
9298
9319
  if (text.length === 0) throw new Error("prompt must be a non-empty string");
9320
+ const references = options.referenceImages ?? [];
9321
+ if (references.length > 5) throw new Error(`订阅生图最多支持 ${String(5)} 张参考图,当前 ${String(references.length)} 张`);
9299
9322
  if (vendor === "antigravity") {
9300
9323
  const projectId = await this.ensureAntigravityProject(session);
9301
9324
  const aspectRatio = antigravityAspectRatioOf(options.size);
@@ -9305,6 +9328,7 @@ var SubscriptionManager = class {
9305
9328
  prompt: text,
9306
9329
  ...aspectRatio !== void 0 ? { aspectRatio } : {},
9307
9330
  hd: options.quality === "hd" || options.quality === "high",
9331
+ ...references.length > 0 ? { referenceImages: references } : {},
9308
9332
  ...options.signal !== void 0 ? { signal: options.signal } : {}
9309
9333
  })).b64 }];
9310
9334
  }
@@ -9312,16 +9336,17 @@ var SubscriptionManager = class {
9312
9336
  let headers;
9313
9337
  let body;
9314
9338
  if (vendor === "codex") {
9315
- url = CODEX_IMAGE_URL;
9339
+ url = references.length > 0 ? CODEX_IMAGE_EDIT_URL : CODEX_IMAGE_URL;
9316
9340
  headers = codexIdentityHeaders(session);
9317
9341
  body = {
9318
9342
  prompt: text,
9319
9343
  model: CODEX_IMAGE_MODEL,
9344
+ ...references.length > 0 ? { images: references.map((image) => ({ image_url: toDataUrl(image) })) } : {},
9320
9345
  ...options.size !== void 0 && options.size.length > 0 ? { size: options.size } : {},
9321
9346
  ...options.quality !== void 0 && options.quality.length > 0 ? { quality: options.quality } : {}
9322
9347
  };
9323
9348
  } else {
9324
- url = GROK_IMAGE_URL;
9349
+ url = references.length > 0 ? GROK_IMAGE_EDIT_URL : GROK_IMAGE_URL;
9325
9350
  headers = grokIdentityHeaders(session);
9326
9351
  const aspect = {
9327
9352
  "1024x1024": "1:1",
@@ -9334,6 +9359,10 @@ var SubscriptionManager = class {
9334
9359
  prompt: text,
9335
9360
  model: GROK_IMAGE_MODEL,
9336
9361
  response_format: "b64_json",
9362
+ ...references.length > 0 ? { images: references.map((image) => ({
9363
+ type: "image_url",
9364
+ image_url: toDataUrl(image)
9365
+ })) } : {},
9337
9366
  ...options.size !== void 0 && aspect[options.size] !== void 0 ? { aspect_ratio: aspect[options.size] } : {},
9338
9367
  ...level !== void 0 ? { quality: level } : {}
9339
9368
  };
@@ -9380,6 +9409,10 @@ function antigravityAspectRatioOf(size) {
9380
9409
  if (mapped !== void 0) return mapped;
9381
9410
  return /^\d+:\d+$/.test(size) ? size : "1:1";
9382
9411
  }
9412
+ /** Encode one reference image as the data URL the edit endpoints accept. */
9413
+ function toDataUrl(image) {
9414
+ return `data:${image.mediaType};base64,${Buffer.from(image.data).toString("base64")}`;
9415
+ }
9383
9416
  /** Response parsing: both vendors reply in the same `{data:[{b64_json}]}` shape. */
9384
9417
  function parseImages(payload) {
9385
9418
  const body = typeof payload === "object" && payload !== null ? payload : {};
@@ -9524,20 +9557,21 @@ function jsonError$3(res, status, code) {
9524
9557
  }
9525
9558
  //#endregion
9526
9559
  //#region lib/types/subscription.js
9527
- /** Timeout for one subscription image call, mirroring the other adapters. */
9528
- const SUBSCRIPTION_TIMEOUT_MS = 3e5;
9529
9560
  /**
9530
9561
  * Generate one image through a logged-in subscription account. Returns the
9531
9562
  * same `{ data, mediaType }` contract as the API-key adapters so the caller
9532
- * can feed it straight into `saveGenerated`.
9563
+ * can feed it straight into `saveGenerated`. With sourceImages the call is
9564
+ * an edit: the vendor layer picks the channel's edit endpoint.
9533
9565
  */
9534
9566
  async function generateSubscriptionImage(options) {
9535
9567
  const { manager, provider, prompt, maxBytes, signal } = options;
9536
9568
  const size = options.size?.trim();
9569
+ const sourceImages = options.sourceImages ?? [];
9537
9570
  const result = await withTimeout(manager.generate({
9538
9571
  vendor: vendorOf(provider),
9539
9572
  prompt,
9540
9573
  ...size !== void 0 && size.length > 0 ? { size } : {},
9574
+ ...sourceImages.length > 0 ? { referenceImages: sourceImages } : {},
9541
9575
  signal
9542
9576
  }), signal);
9543
9577
  const first = Array.isArray(result) ? result[0] : void 0;
@@ -9552,10 +9586,6 @@ async function generateSubscriptionImage(options) {
9552
9586
  ...typeof first.revisedPrompt === "string" && first.revisedPrompt.length > 0 ? { revisedPrompt: first.revisedPrompt } : {}
9553
9587
  };
9554
9588
  }
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
9589
  /** Run one promise with the subscription timeout and the call's abort signal. */
9560
9590
  async function withTimeout(promise, signal) {
9561
9591
  let timer;
@@ -20467,24 +20497,63 @@ const RATIO_LABELS = {
20467
20497
  "9:16": "9:16 竖屏"
20468
20498
  };
20469
20499
  /** Return only browser-safe capability data. */
20470
- async function describeStudio(ctx, config) {
20500
+ async function describeStudio(ctx, config, subscriptions) {
20471
20501
  const configuredEntries = await Promise.all(CLOUD_IMAGE_PROVIDERS.map(async (provider) => {
20472
20502
  return [provider, await resolveApiKey(ctx, provider) !== void 0];
20473
20503
  }));
20474
20504
  const configured = Object.fromEntries(configuredEntries);
20475
- const profiles = CLOUD_IMAGE_PROVIDERS.map((provider) => studioProfile(config, provider, configured[provider]));
20505
+ const cloudProfiles = CLOUD_IMAGE_PROVIDERS.map((provider) => studioProfile(config, provider, configured[provider]));
20506
+ const subStatuses = await Promise.all(SUBSCRIPTION_PROVIDERS.map(async (provider) => {
20507
+ return [provider, subscriptions !== void 0 && (await subscriptions.loginStatus(vendorOf(provider))).state === "logged-in"];
20508
+ }));
20509
+ const subProfiles = SUBSCRIPTION_PROVIDERS.map((provider) => {
20510
+ return subscriptionStudioProfile(provider, subStatuses.find((entry) => entry[0] === provider)?.[1] === true);
20511
+ });
20512
+ const profiles = [...cloudProfiles, ...subProfiles];
20476
20513
  const preferred = config.provider;
20477
20514
  return {
20478
20515
  providers: profiles,
20479
- activeProvider: preferred !== void 0 && cloudProvider$1(preferred) ? preferred : profiles.find((profile) => profile.configured)?.provider ?? "google"
20516
+ activeProvider: isStudioPreferred(preferred) ? preferred : profiles.find((profile) => profile.configured)?.provider ?? "google"
20517
+ };
20518
+ }
20519
+ /** The persisted default provider is usable directly when the workbench can drive it. */
20520
+ function isStudioPreferred(preferred) {
20521
+ return preferred !== void 0 && CLOUD_IMAGE_PROVIDERS.includes(preferred) || preferred !== void 0 && SUBSCRIPTION_PROVIDERS.includes(preferred);
20522
+ }
20523
+ /** Fixed parameter shape for subscription channels: one "channel default" pair. */
20524
+ function subscriptionStudioProfile(provider, signedIn) {
20525
+ const channelDefault = {
20526
+ value: "auto",
20527
+ label: "通道默认"
20528
+ };
20529
+ return {
20530
+ provider,
20531
+ label: SUBSCRIPTION_DISPLAY_LABELS[provider],
20532
+ model: DEFAULT_SUBSCRIPTION_MODELS[provider],
20533
+ configured: signedIn,
20534
+ supportsEditing: true,
20535
+ ratioOptions: [channelDefault],
20536
+ qualityOptions: [channelDefault],
20537
+ defaultRatio: "auto",
20538
+ defaultQuality: "auto"
20480
20539
  };
20481
20540
  }
20541
+ /** Short display names for the workbench subscription rows. */
20542
+ const SUBSCRIPTION_DISPLAY_LABELS = {
20543
+ "chatgpt-sub": "ChatGPT 订阅",
20544
+ "grok-sub": "Grok 订阅",
20545
+ "google-sub": "Google 订阅"
20546
+ };
20482
20547
  /** Execute one validated browser workbench request using the existing provider adapters. */
20483
- async function generateFromStudio(ctx, config, input, signal, fallbackWorkspaceRoot) {
20548
+ async function generateFromStudio(ctx, config, input, signal, fallbackWorkspaceRoot, subscriptions) {
20549
+ if (isSubscriptionProvider(input.provider)) {
20550
+ if (subscriptions === void 0) throw new Error("订阅生图不可用:订阅管理器未初始化");
20551
+ assertAllowed(subscriptionStudioProfile(input.provider, true), input);
20552
+ return generateSubscriptionFromStudio(ctx, subscriptions, input, signal);
20553
+ }
20484
20554
  assertAllowed(studioProfile(config, input.provider, true), input);
20485
20555
  const active = resolveProvider(withProviderOverrides(config, input.provider, input.model));
20486
20556
  if (active.provider === "comfyui") throw new Error("ComfyUI 暂未接入工作台");
20487
- if (isSubscriptionProvider(active.provider)) throw new Error("订阅生图暂未接入工作台,请通过对话或设置切换默认 Provider 使用");
20488
20557
  const wired = active;
20489
20558
  const credential = await requireApiKey(ctx, input.provider);
20490
20559
  const rawRefs = input.references ?? (input.reference ? [input.reference] : []);
@@ -20672,6 +20741,76 @@ async function runPool(tasks, concurrency = 2) {
20672
20741
  await Promise.all(workers);
20673
20742
  return results;
20674
20743
  }
20744
+ /**
20745
+ * Subscription workbench generation: one prompt, channel-default parameters,
20746
+ * through the shared generateSubscriptionImage wrapper (timeout, b64 decode,
20747
+ * size check) so the downstream attachment flow is identical to API keys.
20748
+ */
20749
+ async function generateSubscriptionFromStudio(ctx, subscriptions, input, signal) {
20750
+ const provider = input.provider;
20751
+ const rawRefs = input.references ?? (input.reference ? [input.reference] : []);
20752
+ if (input.mode === "edit" && rawRefs.length === 0) throw new Error("图生图需要至少一张参考图");
20753
+ const sourceImages = input.mode === "edit" ? await Promise.all(rawRefs.map((ref) => readStudioReference(ctx, ref, signal))) : [];
20754
+ const startedAt = Date.now();
20755
+ const count = input.count ?? 1;
20756
+ const generateSingle = async (index) => {
20757
+ const generated = await generateSubscriptionImage({
20758
+ manager: subscriptions,
20759
+ provider,
20760
+ prompt: input.prompt,
20761
+ ...sourceImages.length > 0 ? { sourceImages } : {},
20762
+ maxBytes: ctx.attachments.imageLimits.maxImageBytes,
20763
+ signal
20764
+ });
20765
+ if (!ctx.attachments.imageLimits.mediaTypes.includes(generated.mediaType)) throw new Error(`当前 DSH 不支持保存 ${generated.mediaType} 图片`);
20766
+ return {
20767
+ attachment: await ctx.attachments.saveImage({
20768
+ data: generated.data,
20769
+ mediaType: generated.mediaType,
20770
+ name: count > 1 ? `studio-image-${index + 1}` : "studio-image"
20771
+ }),
20772
+ output: "通道默认"
20773
+ };
20774
+ };
20775
+ if (count === 1) {
20776
+ const single = await generateSingle(0);
20777
+ return subscriptionResponse(input, startedAt, 1, 0, [single], single);
20778
+ }
20779
+ const poolResults = await runPool(Array.from({ length: count }, (_, i) => () => generateSingle(i)), 2);
20780
+ const successes = [];
20781
+ const errors = [];
20782
+ for (let i = 0; i < poolResults.length; i++) {
20783
+ const r = poolResults[i];
20784
+ if (r.status === "fulfilled") successes.push(r.value);
20785
+ else errors.push({
20786
+ index: i,
20787
+ message: r.reason instanceof Error ? r.reason.message : String(r.reason)
20788
+ });
20789
+ }
20790
+ if (successes.length === 0) {
20791
+ const firstReason = poolResults[0] && poolResults[0].status === "rejected" ? poolResults[0].reason : /* @__PURE__ */ new Error("订阅生图全部失败");
20792
+ throw firstReason instanceof Error ? firstReason : new Error(String(firstReason));
20793
+ }
20794
+ const first = successes[0];
20795
+ return subscriptionResponse(input, startedAt, count, errors.length, successes, first, errors);
20796
+ }
20797
+ /** Assemble the subscription workbench response with exactOptionalPropertyTypes-safe spreads. */
20798
+ function subscriptionResponse(input, startedAt, requestedCount, failedCount, items, primary, errors) {
20799
+ return {
20800
+ attachment: primary.attachment,
20801
+ output: primary.output,
20802
+ provider: input.provider,
20803
+ model: input.model,
20804
+ prompt: input.prompt,
20805
+ createdAt: Date.now(),
20806
+ elapsedMs: Date.now() - startedAt,
20807
+ requestedCount,
20808
+ failedCount,
20809
+ items,
20810
+ ...primary.savedTo ? { savedTo: primary.savedTo } : {},
20811
+ ...errors !== void 0 && errors.length > 0 ? { errors } : {}
20812
+ };
20813
+ }
20675
20814
  function studioProfile(config, provider, configured) {
20676
20815
  let active;
20677
20816
  try {
@@ -20725,7 +20864,7 @@ function studioProfile(config, provider, configured) {
20725
20864
  function profile(provider, model, configured, ratioOptions, qualityOptions, defaultRatio, defaultQuality) {
20726
20865
  return {
20727
20866
  provider,
20728
- label: PROVIDER_DISPLAY_NAMES[provider],
20867
+ label: CLOUD_DISPLAY_LABELS[provider],
20729
20868
  model,
20730
20869
  configured,
20731
20870
  supportsEditing: true,
@@ -20735,6 +20874,16 @@ function profile(provider, model, configured, ratioOptions, qualityOptions, defa
20735
20874
  defaultQuality
20736
20875
  };
20737
20876
  }
20877
+ /** Display names for the BYOK cloud rows, mirroring the settings card. */
20878
+ const CLOUD_DISPLAY_LABELS = {
20879
+ google: "Google Gemini",
20880
+ openai: "OpenAI",
20881
+ "openai-compat": "OpenAI 兼容",
20882
+ seedream: "Seedream",
20883
+ dashscope: "DashScope",
20884
+ xai: "xAI Grok",
20885
+ zhipu: "智谱 GLM"
20886
+ };
20738
20887
  function option(value) {
20739
20888
  return {
20740
20889
  value,
@@ -20788,9 +20937,6 @@ function dashScopeSize(ratio) {
20788
20937
  "9:16": "928*1664"
20789
20938
  }[ratio] ?? "1024*1024";
20790
20939
  }
20791
- function cloudProvider$1(value) {
20792
- return CLOUD_IMAGE_PROVIDERS.includes(value);
20793
- }
20794
20940
  //#endregion
20795
20941
  //#region lib/types/studio-route.js
20796
20942
  /** Serve workbench capabilities and generation requests without exposing provider credentials. */
@@ -20804,8 +20950,10 @@ async function serveStudio(req, res, deps) {
20804
20950
  if (req.method !== "POST") return jsonError$1(res, 405, "method-not-allowed");
20805
20951
  if (!(req.headers["content-type"] ?? "").toLowerCase().startsWith("application/json")) return jsonError$1(res, 415, "json-required");
20806
20952
  const controller = new AbortController();
20807
- const upstreamTimeout = AbortSignal.timeout(15e4);
20808
- const signal = AbortSignal.any([controller.signal, upstreamTimeout]);
20953
+ const watchdog = new AbortController();
20954
+ const CLOUD_WATCHDOG_MS = 15e4;
20955
+ let subscriptionChannel = false;
20956
+ const signal = AbortSignal.any([controller.signal, watchdog.signal]);
20809
20957
  const onConnectionClose = () => {
20810
20958
  if (!res.writableEnded) controller.abort(/* @__PURE__ */ new Error("The browser closed the image generation request."));
20811
20959
  };
@@ -20821,11 +20969,15 @@ async function serveStudio(req, res, deps) {
20821
20969
  } catch (error) {
20822
20970
  return jsonError$1(res, 400, errorMessage(error, "invalid-request"));
20823
20971
  }
20972
+ subscriptionChannel = isSubscriptionProvider(input.provider);
20973
+ setTimeout(() => {
20974
+ watchdog.abort();
20975
+ }, subscriptionChannel ? SUBSCRIPTION_TIMEOUT_MS : CLOUD_WATCHDOG_MS).unref?.();
20824
20976
  const output = await deps.generate(input, signal);
20825
20977
  if (!res.headersSent && !res.writableEnded && !res.destroyed) json$1(res, 200, output);
20826
20978
  } catch (error) {
20827
20979
  if (res.headersSent || res.writableEnded || res.destroyed) return;
20828
- if (upstreamTimeout.aborted) return jsonError$1(res, 504, "上游生成超时(150 秒未返回),请检查网络或代理后重试");
20980
+ if (watchdog.signal.aborted) return jsonError$1(res, 504, subscriptionChannel ? "上游生成超时(订阅通道 300 秒未返回),请稍后重试" : "上游生成超时(150 秒未返回),请检查网络或代理后重试");
20829
20981
  jsonError$1(res, controller.signal.aborted ? 499 : 502, errorMessage(error, "generation-failed"));
20830
20982
  } finally {
20831
20983
  res.off("close", onConnectionClose);
@@ -20837,7 +20989,7 @@ function parseStudioGenerateRequest(value) {
20837
20989
  const input = record$1(value);
20838
20990
  if (input === void 0) throw new Error("请求格式无效");
20839
20991
  if (input.mode !== "generate" && input.mode !== "edit") throw new Error("请选择生成类型");
20840
- if (!cloudProvider(input.provider)) throw new Error("不支持该图像 Provider");
20992
+ if (!studioProvider(input.provider)) throw new Error("不支持该图像 Provider");
20841
20993
  const prompt = requiredText(input.prompt, "请输入提示词", 2e3);
20842
20994
  const model = requiredText(input.model, "请选择模型", 200);
20843
20995
  const ratio = requiredText(input.ratio, "请选择比例", 32);
@@ -20889,8 +21041,8 @@ function requiredText(value, message, maxLength) {
20889
21041
  if (text.length > maxLength) throw new Error(`${message}(最多 ${String(maxLength)} 个字符)`);
20890
21042
  return text;
20891
21043
  }
20892
- function cloudProvider(value) {
20893
- return typeof value === "string" && CLOUD_IMAGE_PROVIDERS.includes(value);
21044
+ function studioProvider(value) {
21045
+ return typeof value === "string" && STUDIO_PROVIDERS.includes(value);
20894
21046
  }
20895
21047
  function imageMediaType(value) {
20896
21048
  return value === "image/png" || value === "image/jpeg" || value === "image/webp" || value === "image/gif";
@@ -21386,7 +21538,7 @@ function apply(ctx, config = {}) {
21386
21538
  path: STUDIO_ROUTE,
21387
21539
  handler: (req, res) => serveStudio(req, res, {
21388
21540
  describe: async () => {
21389
- const base = await describeStudio(ctx, current());
21541
+ const base = await describeStudio(ctx, current(), subscriptionManager);
21390
21542
  const workspaces = await getDshWorkspacesFull().catch(() => []);
21391
21543
  const activeRoot = Array.from(knownWorkspaceRoots)[0] || workspaces[0]?.path || process.cwd();
21392
21544
  return {
@@ -21397,7 +21549,7 @@ function apply(ctx, config = {}) {
21397
21549
  },
21398
21550
  generate: (input, signal) => {
21399
21551
  const fallbackRoot = Array.from(knownWorkspaceRoots)[0] || process.cwd();
21400
- return generateFromStudio(ctx, current(), input, signal, fallbackRoot);
21552
+ return generateFromStudio(ctx, current(), input, signal, fallbackRoot, subscriptionManager);
21401
21553
  },
21402
21554
  maxBodyBytes: Math.ceil(ctx.attachments.imageLimits.maxImageBytes * 1.4 * 5) + 262144
21403
21555
  })
@@ -21560,7 +21712,7 @@ function apply(ctx, config = {}) {
21560
21712
  "grok-sub",
21561
21713
  "google-sub"
21562
21714
  ],
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."
21715
+ 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
21716
  },
21565
21717
  model: {
21566
21718
  type: "string",
@@ -21623,7 +21775,6 @@ function apply(ctx, config = {}) {
21623
21775
  output: imageOutput("Edited"),
21624
21776
  async execute(args, exec) {
21625
21777
  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
21778
  const canvasSelection = args.source === "canvas_selection";
21628
21779
  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
21780
  const sourceImages = canvasSelection ? await resolveCanvasSelectionReferences({
@@ -21660,6 +21811,18 @@ function apply(ctx, config = {}) {
21660
21811
  signal: exec.signal
21661
21812
  }), active.provider, workflow.name, "API workflow", current(), exec, knownWorkspaceRoots);
21662
21813
  }
21814
+ if (active.provider === "chatgpt-sub" || active.provider === "grok-sub" || active.provider === "google-sub") {
21815
+ if (sourceImages.length === 0) throw new Error("edit_image requires a reference image");
21816
+ return saveGenerated(ctx, await generateSubscriptionImage({
21817
+ manager: subscriptionManager,
21818
+ provider: active.provider,
21819
+ prompt: args.prompt,
21820
+ sourceImages,
21821
+ ...args.size !== void 0 ? { size: args.size } : {},
21822
+ maxBytes: ctx.attachments.imageLimits.maxImageBytes,
21823
+ signal: exec.signal
21824
+ }), active.provider, active.model, "subscription edit", current(), exec, knownWorkspaceRoots);
21825
+ }
21663
21826
  const credential = await requireApiKey(ctx, active.provider, "edit_image");
21664
21827
  if (active.provider === "google") {
21665
21828
  const aspectRatio = args.aspect_ratio ?? active.aspectRatio;
@@ -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
@@ -1,4 +1,4 @@
1
- import type { CloudImageProvider, StudioProviderProfile } from '../shared.js';
1
+ import type { StudioProvider, StudioProviderProfile } from '../shared.js';
2
2
  export interface ComparisonTarget {
3
3
  profile: StudioProviderProfile;
4
4
  ratio: string;
@@ -6,6 +6,6 @@ export interface ComparisonTarget {
6
6
  adjusted: boolean;
7
7
  }
8
8
  /** Map one shared output intent to settings accepted by every target model. */
9
- export declare function buildComparisonTargets(profiles: readonly StudioProviderProfile[], selectedProviders: readonly CloudImageProvider[], ratio: string, quality: string): ComparisonTarget[];
9
+ export declare function buildComparisonTargets(profiles: readonly StudioProviderProfile[], selectedProviders: readonly StudioProvider[], ratio: string, quality: string): ComparisonTarget[];
10
10
  /** Start with two models, not every configured API, to avoid surprise spend. */
11
- export declare function initialComparisonProviders(profiles: readonly StudioProviderProfile[], activeProvider: CloudImageProvider): CloudImageProvider[];
11
+ export declare function initialComparisonProviders(profiles: readonly StudioProviderProfile[], activeProvider: StudioProvider): StudioProvider[];
@@ -45,5 +45,5 @@ export declare function pillModelOf(provider: ImageProvider, value: PillSettings
45
45
  /** One fixed-position provider menu anchored to the pill button. */
46
46
  export declare function ImageProviderPill(props: ProviderPillFace): import("react").JSX.Element | null;
47
47
  /** Styles for the pill and its fixed-position menu; injected by the client entry. */
48
- export declare const PROVIDER_PILL_STYLE = "\n.dsh-ig-pill-root{position:relative;display:inline-flex;align-items:center;height:26px}\n.dsh-ig-pill-button{display:inline-flex;align-items:center;gap:5px;height:26px;padding:0 8px 0 7px;border:1px solid var(--dsw-alias-border-l2,#e5e7eb);border-radius:13px;background:var(--dsw-alias-bg-layer-3,#fff);color:var(--dsw-alias-label-secondary,#4b5563);font:inherit;font-size:12px;line-height:1;cursor:pointer;transition:border-color .15s,background .15s,color .15s;-webkit-user-select:none;user-select:none}\n.dsh-ig-pill-button:hover:not(:disabled){border-color:var(--dsw-alias-label-dimmed,#9ca3af);color:var(--dsw-alias-label-primary,#111827)}\n.dsh-ig-pill-button:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#4c78ff);outline-offset:1px}\n.dsh-ig-pill-button:disabled{opacity:.5;cursor:not-allowed}\n.dsh-ig-pill-button-open{border-color:var(--dsw-alias-brand-primary,#4c78ff);color:var(--dsw-alias-label-primary,#111827)}\n.dsh-ig-pill-icon{flex:none;color:var(--dsw-alias-brand-primary,#4c78ff)}\n.dsh-ig-pill-provider{max-width:96px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.dsh-ig-pill-caret{display:inline-flex;flex:none;transition:transform .15s}\n.dsh-ig-pill-caret-open{transform:rotate(180deg)}\n.dsh-ig-pill-menu{position:fixed;z-index:1000;min-width:264px;max-width:min(320px,calc(100vw - 24px));padding:6px;border:1px solid var(--dsw-alias-border-l2,#e5e7eb);border-radius:12px;background:var(--dsw-alias-bg-layer-3,#fff);box-shadow:0 12px 32px rgba(0,0,0,.16),0 2px 8px rgba(0,0,0,.08);max-height:min(320px,calc(100vh - 48px));overflow-y:auto}\n.dsh-ig-pill-menu-title{padding:6px 8px 4px;font-size:11px;font-weight:600;color:var(--dsw-alias-label-tertiary,#6b7280)}\n.dsh-ig-pill-option{display:flex;width:100%;align-items:center;gap:9px;padding:7px 8px;border:0;border-radius:8px;background:none;font:inherit;color:inherit;text-align:left;cursor:pointer}\n.dsh-ig-pill-option:hover:not(:disabled){background:var(--dsw-alias-bg-layer-2,#f3f4f6)}\n.dsh-ig-pill-option:disabled{opacity:.6;cursor:default}\n.dsh-ig-pill-option:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#4c78ff);outline-offset:-2px}\n.dsh-ig-pill-option-active{background:var(--dsw-alias-brand-primary-soft,rgba(76,120,255,.10))}\n.dsh-ig-pill-dot{flex:none;width:7px;height:7px;border-radius:50%}\n.dsh-ig-pill-dot-ok{background:#22c55e}\n.dsh-ig-pill-dot-missing{background:#9ca3af}\n.dsh-ig-pill-dot-neutral{background:#4c78ff}\n.dsh-ig-pill-option-text{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}\n.dsh-ig-pill-option-name{font-size:12.5px;font-weight:500;color:var(--dsw-alias-label-primary,#111827);line-height:1.2}\n.dsh-ig-pill-option-sub{font-size:11px;color:var(--dsw-alias-label-tertiary,#6b7280);line-height:1.2;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.dsh-ig-pill-check{flex:none;color:var(--dsw-alias-brand-primary,#4c78ff)}\n.dsh-ig-pill-error{margin-top:4px;padding:6px 8px;border-radius:8px;background:rgba(239,68,68,.08);color:#ef4444;font-size:11.5px;line-height:1.4;word-break:break-all}\n";
48
+ export declare const PROVIDER_PILL_STYLE = "\n.dsh-ig-pill-root{position:relative;display:inline-flex;align-items:center;height:28px}\n.dsh-ig-pill-button{display:inline-flex;align-items:center;gap:5px;height:28px;padding:0 9px 0 8px;border:1px solid var(--dsw-alias-border-l2,#e5e7eb);border-radius:14px;background:var(--dsw-alias-bg-layer-3,#fff);color:var(--dsw-alias-label-secondary,#4b5563);font:inherit;font-size:13px;line-height:20px;cursor:pointer;transition:border-color .15s,background .15s,color .15s;-webkit-user-select:none;user-select:none}\n.dsh-ig-pill-button:hover:not(:disabled){border-color:var(--dsw-alias-label-dimmed,#9ca3af);color:var(--dsw-alias-label-primary,#111827)}\n.dsh-ig-pill-button:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#4c78ff);outline-offset:1px}\n.dsh-ig-pill-button:disabled{opacity:.5;cursor:not-allowed}\n.dsh-ig-pill-button-open{border-color:var(--dsw-alias-brand-primary,#4c78ff);color:var(--dsw-alias-label-primary,#111827)}\n.dsh-ig-pill-icon{flex:none;color:var(--dsw-alias-brand-primary,#4c78ff)}\n.dsh-ig-pill-provider{max-width:150px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.dsh-ig-pill-caret{display:inline-flex;flex:none;transition:transform .15s}\n.dsh-ig-pill-caret-open{transform:rotate(180deg)}\n@container (max-width:700px){\n .dsh-ig-pill-caret{display:none}\n .dsh-ig-pill-button{padding:0 7px 0 6px}\n}\n.dsh-ig-pill-menu{position:fixed;z-index:1000;min-width:264px;max-width:min(320px,calc(100vw - 24px));padding:6px;border:1px solid var(--dsw-alias-border-l2,#e5e7eb);border-radius:12px;background:var(--dsw-alias-bg-layer-3,#fff);box-shadow:0 12px 32px rgba(0,0,0,.16),0 2px 8px rgba(0,0,0,.08);max-height:min(320px,calc(100vh - 48px));overflow-y:auto}\n.dsh-ig-pill-menu-title{padding:6px 8px 4px;font-size:11px;font-weight:600;color:var(--dsw-alias-label-tertiary,#6b7280)}\n.dsh-ig-pill-option{display:flex;width:100%;align-items:center;gap:9px;padding:7px 8px;border:0;border-radius:8px;background:none;font:inherit;color:inherit;text-align:left;cursor:pointer}\n.dsh-ig-pill-option:hover:not(:disabled){background:var(--dsw-alias-bg-layer-2,#f3f4f6)}\n.dsh-ig-pill-option:disabled{opacity:.6;cursor:default}\n.dsh-ig-pill-option:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#4c78ff);outline-offset:-2px}\n.dsh-ig-pill-option-active{background:var(--dsw-alias-brand-primary-soft,rgba(76,120,255,.10))}\n.dsh-ig-pill-dot{flex:none;width:7px;height:7px;border-radius:50%}\n.dsh-ig-pill-dot-ok{background:#22c55e}\n.dsh-ig-pill-dot-missing{background:#9ca3af}\n.dsh-ig-pill-dot-neutral{background:#4c78ff}\n.dsh-ig-pill-option-text{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}\n.dsh-ig-pill-option-name{font-size:12.5px;font-weight:500;color:var(--dsw-alias-label-primary,#111827);line-height:1.2}\n.dsh-ig-pill-option-sub{font-size:11px;color:var(--dsw-alias-label-tertiary,#6b7280);line-height:1.2;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.dsh-ig-pill-check{flex:none;color:var(--dsw-alias-brand-primary,#4c78ff)}\n.dsh-ig-pill-error{margin-top:4px;padding:6px 8px;border-radius:8px;background:rgba(239,68,68,.08);color:#ef4444;font-size:11.5px;line-height:1.4;word-break:break-all}\n";
49
49
  export {};
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Workbench chat panel: the right-side DSH conversation surface.
3
+ *
4
+ * A dedicated archived agent session lives on the host side (studio-chat.ts);
5
+ * this panel pulls its projected event feed through CHAT_ROUTE with adaptive
6
+ * polling (?since=<seq> increments; faster while a turn is running, slower
7
+ * when idle, paused while the webview is hidden) and submits user turns with
8
+ * POST. Tool-generated images are mirrored onto the tldraw canvas through the
9
+ * landing queue and upserted into the gallery (the recent list refreshes via
10
+ * its subscription). The panel is intentionally unmount-friendly: re-pulls
11
+ * from seq 0 on remount, and both landing and gallery writes are deduped by
12
+ * attachmentId, so tab switches are safe.
13
+ */
14
+ import { type FC } from 'react';
15
+ /** Workspace scoping mirrored onto gallery records; shape-compatible with StudioWorkspaceProps. */
16
+ export interface ChatWorkspaceScope {
17
+ workspaceId?: string | undefined;
18
+ path?: string | undefined;
19
+ }
20
+ export declare const StudioChatPanel: FC<{
21
+ lang: 'zh' | 'en';
22
+ workspace?: ChatWorkspaceScope | null | undefined;
23
+ }>;
@@ -15,6 +15,10 @@ export interface StudioWorkspaceProps {
15
15
  }
16
16
  export declare const StudioView: FC<{
17
17
  locale?: LocaleService | undefined;
18
+ /** Host credential-change notifier; re-reads provider sign-in state silently. */
19
+ credentialEvents?: {
20
+ listen(callback: () => void): () => void;
21
+ } | undefined;
18
22
  workspace?: StudioWorkspaceProps | null | undefined;
19
23
  initialPrompt?: string | undefined;
20
24
  /**