dsh-image-gen 0.6.0 → 0.6.5

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 (32) hide show
  1. package/README.en.md +12 -2
  2. package/README.md +13 -3
  3. package/lib/client.js +1086 -133
  4. package/lib/client.js.map +1 -1
  5. package/lib/index.js +1425 -103
  6. package/lib/types/client/image-result-node.d.ts +11 -1
  7. package/lib/types/client/index.d.ts +5 -0
  8. package/lib/types/client/provider-pill.d.ts +1 -1
  9. package/lib/types/client/studio-style.d.ts +1 -1
  10. package/lib/types/client/studio-view.d.ts +6 -0
  11. package/lib/types/client/tl/css-supports-shim.d.ts +21 -0
  12. package/lib/types/client/tl/diagram-landing.d.ts +75 -0
  13. package/lib/types/client/tl/diagram-layout.d.ts +76 -0
  14. package/lib/types/client/tl/diagram-measure.d.ts +40 -0
  15. package/lib/types/client/tl/tl-brand-theme.d.ts +3 -0
  16. package/lib/types/client/tl/tl-brand-tokens.d.ts +24 -0
  17. package/lib/types/client/tl/tl-theme-css.d.ts +17 -0
  18. package/lib/types/config.d.ts +24 -0
  19. package/lib/types/diagram.d.ts +42 -0
  20. package/lib/types/openai-compatible.d.ts +15 -0
  21. package/lib/types/shared.d.ts +16 -1
  22. package/lib/types/subscription/blob.d.ts +23 -0
  23. package/lib/types/subscription/loopback.d.ts +11 -0
  24. package/lib/types/subscription/manager.d.ts +94 -0
  25. package/lib/types/subscription/oauth.d.ts +25 -0
  26. package/lib/types/subscription/subscription-route.d.ts +5 -0
  27. package/lib/types/subscription/vendors/antigravity.d.ts +66 -0
  28. package/lib/types/subscription/vendors/codex.d.ts +30 -0
  29. package/lib/types/subscription/vendors/grok.d.ts +32 -0
  30. package/lib/types/subscription.d.ts +42 -0
  31. package/lib/types/test-route.d.ts +9 -0
  32. package/package.json +10 -8
package/lib/index.js CHANGED
@@ -3,7 +3,8 @@ import { Context, Service } from "@deepseek-ai/cordis";
3
3
  import z from "@deepseek-ai/schemastery";
4
4
  import { lstat, mkdir, readFile, readdir, realpath, rename, stat, unlink, writeFile } from "node:fs/promises";
5
5
  import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
6
- import { createHash, randomUUID } from "node:crypto";
6
+ import { createHash, randomBytes, randomUUID } from "node:crypto";
7
+ import http from "node:http";
7
8
  //#region \0rolldown/runtime.js
8
9
  var __defProp = Object.defineProperty;
9
10
  var __exportAll = (all, no_symbols) => {
@@ -5951,6 +5952,8 @@ const SAVE_WORKSPACE_ROUTE = "/plugins/dsh-image-gen/save-workspace";
5951
5952
  const TEST_CONNECTION_ROUTE = "/plugins/dsh-image-gen/test";
5952
5953
  /** Browser route the workbench infinite canvas pushes live state through. */
5953
5954
  const CANVAS_STATE_ROUTE = "/plugins/dsh-image-gen/canvas-state";
5955
+ const SUBSCRIPTION_LOGIN_ROUTE = "/plugins/dsh-image-gen/subscription-login";
5956
+ const SUBSCRIPTION_STATUS_ROUTE = "/plugins/dsh-image-gen/subscription-status";
5954
5957
  /** Every accepted canvas node kind, for validating untrusted pushes. */
5955
5958
  const CANVAS_NODE_KINDS = [
5956
5959
  "image",
@@ -5973,8 +5976,25 @@ const IMAGE_PROVIDERS = [
5973
5976
  "dashscope",
5974
5977
  "xai",
5975
5978
  "zhipu",
5976
- "comfyui"
5979
+ "comfyui",
5980
+ "chatgpt-sub",
5981
+ "grok-sub",
5982
+ "google-sub"
5977
5983
  ];
5984
+ /**
5985
+ * Providers that generate through a logged-in subscription account instead
5986
+ * of an API key. They use no credential reference and never join the
5987
+ * cloud/BYOK sets.
5988
+ */
5989
+ const SUBSCRIPTION_PROVIDERS = [
5990
+ "chatgpt-sub",
5991
+ "grok-sub",
5992
+ "google-sub"
5993
+ ];
5994
+ /** True when the provider generates through a logged-in subscription account. */
5995
+ function isSubscriptionProvider(provider) {
5996
+ return SUBSCRIPTION_PROVIDERS.includes(provider);
5997
+ }
5978
5998
  /** Providers supported by the first browser workbench release. */
5979
5999
  const CLOUD_IMAGE_PROVIDERS = [
5980
6000
  "google",
@@ -6027,7 +6047,16 @@ const PROVIDER_DISPLAY_NAMES = {
6027
6047
  dashscope: "DashScope",
6028
6048
  xai: "xAI Grok",
6029
6049
  zhipu: "智谱 GLM",
6030
- comfyui: "ComfyUI"
6050
+ comfyui: "ComfyUI",
6051
+ "chatgpt-sub": "ChatGPT 订阅",
6052
+ "grok-sub": "Grok 订阅",
6053
+ "google-sub": "Google 订阅"
6054
+ };
6055
+ /** Display names for the subscription providers, separate from the BYOK table. */
6056
+ const SUBSCRIPTION_PROVIDER_DISPLAY_NAMES = {
6057
+ "chatgpt-sub": "ChatGPT 订阅",
6058
+ "grok-sub": "Grok 订阅",
6059
+ "google-sub": "Google 订阅"
6031
6060
  };
6032
6061
  /** Default endpoints and base URLs. */
6033
6062
  const DEFAULT_GOOGLE_ENDPOINT = "https://generativelanguage.googleapis.com/v1beta/interactions";
@@ -6092,13 +6121,20 @@ function mergeComfyUIPrompt(preset, user) {
6092
6121
  if (userText.length === 0) return presetText;
6093
6122
  return `${presetText}, ${userText}`;
6094
6123
  }
6124
+ /** Default models for the subscription channels; fixed by the bridge protocol. */
6125
+ const DEFAULT_SUBSCRIPTION_MODELS = {
6126
+ "chatgpt-sub": "gpt-image-2.5-flare",
6127
+ "grok-sub": "grok-imagine-image-2.0",
6128
+ "google-sub": "gemini-3-pro-image"
6129
+ };
6130
+ DEFAULT_SUBSCRIPTION_MODELS["chatgpt-sub"], DEFAULT_SUBSCRIPTION_MODELS["grok-sub"], DEFAULT_SUBSCRIPTION_MODELS["google-sub"];
6095
6131
  //#endregion
6096
6132
  //#region lib/types/config.js
6097
6133
  /** User-facing configuration for supported image providers. */
6098
6134
  /** Default workspace subfolder that receives generated image files. */
6099
6135
  const DEFAULT_WORKSPACE_FOLDER = "dsh-image-gen";
6100
6136
  /** Google tool-level controls. */
6101
- const ASPECT_RATIOS = [
6137
+ const ASPECT_RATIOS$1 = [
6102
6138
  "1:1",
6103
6139
  "3:2",
6104
6140
  "2:3",
@@ -6121,6 +6157,8 @@ const Config = z.object({
6121
6157
  openaiModel: z.string().default(DEFAULT_OPENAI_MODEL),
6122
6158
  openaiCompatBaseURL: z.string().default(""),
6123
6159
  openaiCompatModel: z.string().default(""),
6160
+ openaiCompatEditFormat: z.union([z.const("multipart"), z.const("jsonImageUrlArray")]).default("multipart"),
6161
+ openaiCompatEditExtra: z.dict(z.any()).default({}),
6124
6162
  seedreamBaseURL: z.string().default(DEFAULT_SEEDREAM_BASE_URL),
6125
6163
  seedreamModel: z.string().default(DEFAULT_SEEDREAM_MODEL),
6126
6164
  dashscopeEndpoint: z.string().default(DEFAULT_DASHSCOPE_ENDPOINT),
@@ -6162,7 +6200,9 @@ function resolveProvider(config) {
6162
6200
  apiKeyEnv: OPENAI_COMPAT_API_KEY_ENV,
6163
6201
  model,
6164
6202
  baseURL,
6165
- imageSize: "1024x1024"
6203
+ imageSize: "1024x1024",
6204
+ editFormat: config.openaiCompatEditFormat ?? "multipart",
6205
+ editExtra: config.openaiCompatEditExtra ?? {}
6166
6206
  };
6167
6207
  }
6168
6208
  case "seedream": return {
@@ -6204,6 +6244,18 @@ function resolveProvider(config) {
6204
6244
  timeoutMs: config.comfyuiTimeoutMs ?? 3e5
6205
6245
  };
6206
6246
  }
6247
+ case "chatgpt-sub": return {
6248
+ provider: "chatgpt-sub",
6249
+ model: DEFAULT_SUBSCRIPTION_MODELS["chatgpt-sub"]
6250
+ };
6251
+ case "grok-sub": return {
6252
+ provider: "grok-sub",
6253
+ model: DEFAULT_SUBSCRIPTION_MODELS["grok-sub"]
6254
+ };
6255
+ case "google-sub": return {
6256
+ provider: "google-sub",
6257
+ model: DEFAULT_SUBSCRIPTION_MODELS["google-sub"]
6258
+ };
6207
6259
  case "google": return {
6208
6260
  provider: "google",
6209
6261
  apiKeyEnv: GOOGLE_API_KEY_ENV,
@@ -6255,6 +6307,9 @@ function withProviderOverrides(config, provider, model) {
6255
6307
  ...base,
6256
6308
  zhipuModel: trimmed
6257
6309
  };
6310
+ case "chatgpt-sub": return base;
6311
+ case "grok-sub": return base;
6312
+ case "google-sub": return base;
6258
6313
  case "comfyui": return base;
6259
6314
  }
6260
6315
  }
@@ -6495,24 +6550,24 @@ const MAX_NODE_COUNT = 1e5;
6495
6550
  const MAX_SELECTION_COUNT = 1e3;
6496
6551
  /** Serve one canvas-state push. */
6497
6552
  async function serveCanvasState(req, res, deps) {
6498
- if (req.method !== "POST") return jsonError$4(res, 405, "method-not-allowed");
6499
- if (!(req.headers["content-type"] ?? "").toLowerCase().startsWith("application/json")) return jsonError$4(res, 415, "json-required");
6553
+ if (req.method !== "POST") return jsonError$5(res, 405, "method-not-allowed");
6554
+ if (!(req.headers["content-type"] ?? "").toLowerCase().startsWith("application/json")) return jsonError$5(res, 415, "json-required");
6500
6555
  const origin = req.headers.origin;
6501
6556
  const host = req.headers.host;
6502
- if (origin !== void 0 && host !== void 0 && origin !== `http://${host}` && origin !== `https://${host}`) return jsonError$4(res, 403, "origin-rejected");
6557
+ if (origin !== void 0 && host !== void 0 && origin !== `http://${host}` && origin !== `https://${host}`) return jsonError$5(res, 403, "origin-rejected");
6503
6558
  let body;
6504
6559
  try {
6505
- body = JSON.parse(await readBody$3(req, deps.maxBodyBytes));
6560
+ body = JSON.parse(await readBody$4(req, deps.maxBodyBytes));
6506
6561
  } catch (error) {
6507
- return jsonError$4(res, error instanceof Error && error.message === "request-too-large" ? 413 : 400, "invalid-request");
6562
+ return jsonError$5(res, error instanceof Error && error.message === "request-too-large" ? 413 : 400, "invalid-request");
6508
6563
  }
6509
6564
  const push = parseCanvasStatePush(body);
6510
- if (push === void 0) return jsonError$4(res, 400, "invalid-canvas-state");
6565
+ if (push === void 0) return jsonError$5(res, 400, "invalid-canvas-state");
6511
6566
  let selectionImage;
6512
6567
  if (push.selectionImage !== void 0) {
6513
6568
  const decoded = decodeSelectionImage(push.selectionImage);
6514
- if (decoded === void 0) return jsonError$4(res, 400, "invalid-selection-image");
6515
- if (decoded.data.byteLength > deps.maxImageBytes) return jsonError$4(res, 413, "selection-image-too-large");
6569
+ if (decoded === void 0) return jsonError$5(res, 400, "invalid-selection-image");
6570
+ if (decoded.data.byteLength > deps.maxImageBytes) return jsonError$5(res, 413, "selection-image-too-large");
6516
6571
  selectionImage = decoded;
6517
6572
  }
6518
6573
  deps.mirror.apply(push, selectionImage);
@@ -6668,7 +6723,7 @@ function optionalDimension(value) {
6668
6723
  function nonNegativeInteger$1(value) {
6669
6724
  return typeof value === "number" && Number.isInteger(value) && value >= 0;
6670
6725
  }
6671
- async function readBody$3(req, maxBytes) {
6726
+ async function readBody$4(req, maxBytes) {
6672
6727
  const chunks = [];
6673
6728
  let bytes = 0;
6674
6729
  for await (const chunk of req) {
@@ -6679,7 +6734,7 @@ async function readBody$3(req, maxBytes) {
6679
6734
  }
6680
6735
  return Buffer.concat(chunks).toString("utf8");
6681
6736
  }
6682
- function jsonError$4(res, status, code) {
6737
+ function jsonError$5(res, status, code) {
6683
6738
  res.writeHead(status, {
6684
6739
  "content-type": "application/json",
6685
6740
  "cache-control": "no-store"
@@ -6793,7 +6848,7 @@ function findReferenceImage(messages, sourceAttachmentId) {
6793
6848
  }
6794
6849
  /** Validate an untrusted serialized image reference at an HTTP/UI boundary. */
6795
6850
  function parseImageAttachmentRef(value) {
6796
- const ref = record$7(value);
6851
+ const ref = record$8(value);
6797
6852
  if (ref === void 0) return void 0;
6798
6853
  if (typeof ref.attachmentId !== "string" || !imageMediaType$4(ref.mediaType)) return void 0;
6799
6854
  if (!nonNegativeInteger(ref.bytes) || !positiveInteger(ref.width) || !positiveInteger(ref.height)) return void 0;
@@ -6825,7 +6880,7 @@ function findInBlocks(blocks, sourceAttachmentId) {
6825
6880
  }
6826
6881
  }
6827
6882
  function parseDimensions(value) {
6828
- const dimensions = record$7(value);
6883
+ const dimensions = record$8(value);
6829
6884
  if (dimensions === void 0 || !positiveInteger(dimensions.width) || !positiveInteger(dimensions.height)) return void 0;
6830
6885
  return {
6831
6886
  width: dimensions.width,
@@ -6897,7 +6952,7 @@ function positiveInteger(value) {
6897
6952
  function nonNegativeInteger(value) {
6898
6953
  return typeof value === "number" && Number.isInteger(value) && value >= 0;
6899
6954
  }
6900
- function record$7(value) {
6955
+ function record$8(value) {
6901
6956
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
6902
6957
  }
6903
6958
  //#endregion
@@ -7115,8 +7170,8 @@ function prepareComfyUIWorkflow(workflowJson, prompt, seed = randomSeed(), image
7115
7170
  };
7116
7171
  let imageInputs = 0;
7117
7172
  const prepared = Object.fromEntries(Object.entries(workflow).map(([nodeId, value]) => {
7118
- const node = record$6(value);
7119
- const inputs = node === void 0 ? void 0 : record$6(node.inputs);
7173
+ const node = record$7(value);
7174
+ const inputs = node === void 0 ? void 0 : record$7(node.inputs);
7120
7175
  if (inputs === void 0) return [nodeId, value];
7121
7176
  const nextInputs = inject(inputs);
7122
7177
  if (isImagePlaceholder(nextInputs[IMAGE_INPUT_KEY])) {
@@ -7152,7 +7207,7 @@ function parseWorkflow(workflowJson) {
7152
7207
  }
7153
7208
  if (!isRecord(value) || Object.keys(value).length === 0) throw new Error("ComfyUI workflow must be a non-empty API-format JSON object");
7154
7209
  if (!Object.values(value).some((node) => {
7155
- const inputs = record$6(node)?.inputs;
7210
+ const inputs = record$7(node)?.inputs;
7156
7211
  return inputs !== void 0 && containsPromptPlaceholder(inputs);
7157
7212
  })) throw new Error(`ComfyUI workflow must contain ${COMFYUI_PROMPT_PLACEHOLDER} (or ${LEGACY_PROMPT_PLACEHOLDER}) in a text input`);
7158
7213
  return value;
@@ -7168,7 +7223,7 @@ function containsPromptPlaceholder(value) {
7168
7223
  function isRecord(value) {
7169
7224
  return typeof value === "object" && value !== null && !Array.isArray(value);
7170
7225
  }
7171
- function record$6(value) {
7226
+ function record$7(value) {
7172
7227
  return isRecord(value) ? value : void 0;
7173
7228
  }
7174
7229
  //#endregion
@@ -7302,9 +7357,9 @@ async function waitForOutput(baseURL, promptId, signal) {
7302
7357
  });
7303
7358
  const text = await readBoundedText$3(response, MAX_HISTORY_BYTES);
7304
7359
  if (!response.ok) throw new Error(`ComfyUI history request failed (${response.status}): ${redactSecrets(text).slice(0, ERROR_LIMIT$3)}`);
7305
- const entry = record$5(parseJsonRecord(text, "ComfyUI history returned invalid JSON")[promptId]);
7360
+ const entry = record$6(parseJsonRecord(text, "ComfyUI history returned invalid JSON")[promptId]);
7306
7361
  if (entry !== void 0) {
7307
- const status = record$5(entry.status);
7362
+ const status = record$6(entry.status);
7308
7363
  if (status?.status_str === "error") throw new Error(`ComfyUI workflow failed: ${redactSecrets(JSON.stringify(status.messages ?? status)).slice(0, ERROR_LIMIT$3)}`);
7309
7364
  const output = firstOutputImage(entry.outputs);
7310
7365
  if (output !== void 0) return output;
@@ -7314,13 +7369,13 @@ async function waitForOutput(baseURL, promptId, signal) {
7314
7369
  }
7315
7370
  }
7316
7371
  function firstOutputImage(value) {
7317
- const outputs = record$5(value);
7372
+ const outputs = record$6(value);
7318
7373
  if (outputs === void 0) return void 0;
7319
7374
  for (const nodeOutput of Object.values(outputs)) {
7320
- const images = record$5(nodeOutput)?.images;
7375
+ const images = record$6(nodeOutput)?.images;
7321
7376
  if (!Array.isArray(images)) continue;
7322
7377
  for (const image of images) {
7323
- const item = record$5(image);
7378
+ const item = record$6(image);
7324
7379
  if (typeof item?.filename !== "string" || item.filename.length === 0) continue;
7325
7380
  const output = {
7326
7381
  filename: item.filename,
@@ -7361,7 +7416,7 @@ function comfyUIBaseURL(value) {
7361
7416
  function endpoint(baseURL, path) {
7362
7417
  return new URL(path, baseURL);
7363
7418
  }
7364
- function record$5(value) {
7419
+ function record$6(value) {
7365
7420
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
7366
7421
  }
7367
7422
  function parseJsonRecord(text, message) {
@@ -7371,7 +7426,7 @@ function parseJsonRecord(text, message) {
7371
7426
  } catch {
7372
7427
  throw new Error(message);
7373
7428
  }
7374
- const parsed = record$5(value);
7429
+ const parsed = record$6(value);
7375
7430
  if (parsed === void 0) throw new Error(message);
7376
7431
  return parsed;
7377
7432
  }
@@ -7455,7 +7510,7 @@ async function editDashScopeImage(options) {
7455
7510
  model: options.model,
7456
7511
  input: { messages: [{
7457
7512
  role: "user",
7458
- content: [...options.sourceImages.map((sourceImage) => ({ image: toDataUrl$1(sourceImage) })), { text: options.prompt }]
7513
+ content: [...options.sourceImages.map((sourceImage) => ({ image: toDataUrl$2(sourceImage) })), { text: options.prompt }]
7459
7514
  }] },
7460
7515
  parameters: {
7461
7516
  prompt_extend: true,
@@ -7472,7 +7527,7 @@ function formatSize(size) {
7472
7527
  if (size === void 0 || size.length === 0) return void 0;
7473
7528
  return size.replace("x", "*");
7474
7529
  }
7475
- function toDataUrl$1(image) {
7530
+ function toDataUrl$2(image) {
7476
7531
  return `data:${image.mediaType};base64,${Buffer.from(image.data).toString("base64")}`;
7477
7532
  }
7478
7533
  async function requestQwenImage(options) {
@@ -7596,13 +7651,13 @@ async function requestGoogleImage(input) {
7596
7651
  };
7597
7652
  }
7598
7653
  function outputImage(value) {
7599
- const interaction = record$4(value);
7654
+ const interaction = record$5(value);
7600
7655
  if (interaction === void 0) return void 0;
7601
7656
  const direct = imageContent(interaction.output_image, false);
7602
7657
  if (direct !== void 0) return direct;
7603
7658
  if (!Array.isArray(interaction.steps)) return void 0;
7604
7659
  for (const step of interaction.steps) {
7605
- const modelOutput = record$4(step);
7660
+ const modelOutput = record$5(step);
7606
7661
  if (modelOutput?.type !== "model_output" || !Array.isArray(modelOutput.content)) continue;
7607
7662
  for (const content of modelOutput.content) {
7608
7663
  const image = imageContent(content, true);
@@ -7611,14 +7666,14 @@ function outputImage(value) {
7611
7666
  }
7612
7667
  }
7613
7668
  function imageContent(value, requiresImageType) {
7614
- const image = record$4(value);
7669
+ const image = record$5(value);
7615
7670
  if (image === void 0 || requiresImageType && image.type !== "image" || typeof image.data !== "string") return void 0;
7616
7671
  return {
7617
7672
  data: image.data,
7618
7673
  mime_type: image.mime_type
7619
7674
  };
7620
7675
  }
7621
- function record$4(value) {
7676
+ function record$5(value) {
7622
7677
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
7623
7678
  }
7624
7679
  function mediaTypeOf(value) {
@@ -7881,22 +7936,22 @@ async function deleteImageFromWorkspace(filePath, allowedWorkspaceRoots) {
7881
7936
  }
7882
7937
  //#endregion
7883
7938
  //#region lib/types/image-route.js
7884
- const MAX_BODY_BYTES$1 = 65536;
7939
+ const MAX_BODY_BYTES$2 = 65536;
7885
7940
  /** Serve one verified durable image reference to a same-origin browser request. */
7886
7941
  async function serveImage(req, res, deps) {
7887
- if (req.method !== "POST") return jsonError$3(res, 405, "method-not-allowed");
7888
- if (!(req.headers["content-type"] ?? "").toLowerCase().startsWith("application/json")) return jsonError$3(res, 415, "json-required");
7942
+ if (req.method !== "POST") return jsonError$4(res, 405, "method-not-allowed");
7943
+ if (!(req.headers["content-type"] ?? "").toLowerCase().startsWith("application/json")) return jsonError$4(res, 415, "json-required");
7889
7944
  const origin = req.headers.origin;
7890
7945
  const host = req.headers.host;
7891
- if (origin !== void 0 && host !== void 0 && origin !== `http://${host}` && origin !== `https://${host}`) return jsonError$3(res, 403, "origin-rejected");
7946
+ if (origin !== void 0 && host !== void 0 && origin !== `http://${host}` && origin !== `https://${host}`) return jsonError$4(res, 403, "origin-rejected");
7892
7947
  let body;
7893
7948
  try {
7894
- body = JSON.parse(await readBody$2(req));
7949
+ body = JSON.parse(await readBody$3(req));
7895
7950
  } catch {
7896
- return jsonError$3(res, 400, "invalid-request");
7951
+ return jsonError$4(res, 400, "invalid-request");
7897
7952
  }
7898
7953
  const attachment = attachmentFromRequest(body);
7899
- if (attachment === void 0) return jsonError$3(res, 400, "invalid-attachment");
7954
+ if (attachment === void 0) return jsonError$4(res, 400, "invalid-attachment");
7900
7955
  try {
7901
7956
  const stored = await deps.readImage(attachment);
7902
7957
  res.writeHead(200, {
@@ -7907,10 +7962,10 @@ async function serveImage(req, res, deps) {
7907
7962
  });
7908
7963
  res.end(stored.data);
7909
7964
  } catch {
7910
- jsonError$3(res, 404, "image-unavailable");
7965
+ jsonError$4(res, 404, "image-unavailable");
7911
7966
  }
7912
7967
  }
7913
- function sameOrigin$3(req) {
7968
+ function sameOrigin$4(req) {
7914
7969
  const origin = req.headers.origin;
7915
7970
  const host = req.headers.host;
7916
7971
  if (origin !== void 0 && host !== void 0 && origin !== `http://${host}` && origin !== `https://${host}`) return false;
@@ -7918,14 +7973,14 @@ function sameOrigin$3(req) {
7918
7973
  }
7919
7974
  /** Safely delete one or more generated image files from the workspace disk. */
7920
7975
  async function serveDelete(req, res, deps) {
7921
- if (req.method !== "POST") return jsonError$3(res, 405, "method-not-allowed");
7922
- if (!(req.headers["content-type"] ?? "").toLowerCase().startsWith("application/json")) return jsonError$3(res, 415, "json-required");
7923
- if (!sameOrigin$3(req)) return jsonError$3(res, 403, "origin-rejected");
7976
+ if (req.method !== "POST") return jsonError$4(res, 405, "method-not-allowed");
7977
+ if (!(req.headers["content-type"] ?? "").toLowerCase().startsWith("application/json")) return jsonError$4(res, 415, "json-required");
7978
+ if (!sameOrigin$4(req)) return jsonError$4(res, 403, "origin-rejected");
7924
7979
  let body;
7925
7980
  try {
7926
- body = JSON.parse(await readBody$2(req));
7981
+ body = JSON.parse(await readBody$3(req));
7927
7982
  } catch {
7928
- return jsonError$3(res, 400, "invalid-request");
7983
+ return jsonError$4(res, 400, "invalid-request");
7929
7984
  }
7930
7985
  const rawPaths = typeof body === "object" && body !== null && "paths" in body && Array.isArray(body.paths) ? body.paths : [];
7931
7986
  const deletedFiles = [];
@@ -7955,13 +8010,13 @@ async function serveDelete(req, res, deps) {
7955
8010
  }
7956
8011
  /** Serve on-demand image persistence into the workspace. */
7957
8012
  async function serveSaveWorkspace(req, res, deps) {
7958
- if (!sameOrigin$3(req)) return jsonError$3(res, 403, "origin-rejected");
7959
- if (req.method !== "POST") return jsonError$3(res, 405, "method-not-allowed");
7960
- if (!(req.headers["content-type"] ?? "").toLowerCase().startsWith("application/json")) return jsonError$3(res, 415, "json-required");
8013
+ if (!sameOrigin$4(req)) return jsonError$4(res, 403, "origin-rejected");
8014
+ if (req.method !== "POST") return jsonError$4(res, 405, "method-not-allowed");
8015
+ if (!(req.headers["content-type"] ?? "").toLowerCase().startsWith("application/json")) return jsonError$4(res, 415, "json-required");
7961
8016
  try {
7962
- const raw = JSON.parse(await readBody$2(req));
8017
+ const raw = JSON.parse(await readBody$3(req));
7963
8018
  const attachment = parseImageAttachmentRef(raw.attachment);
7964
- if (!attachment) return jsonError$3(res, 400, "invalid-attachment");
8019
+ if (!attachment) return jsonError$4(res, 400, "invalid-attachment");
7965
8020
  if (!deps.isSaveEnabled()) {
7966
8021
  res.writeHead(200, {
7967
8022
  "content-type": "application/json",
@@ -7979,10 +8034,10 @@ async function serveSaveWorkspace(req, res, deps) {
7979
8034
  try {
7980
8035
  targetRoot = await assertWorkspaceAllowed(rawTarget, allowedRoots);
7981
8036
  } catch {
7982
- return jsonError$3(res, 403, "workspace-root-not-allowed");
8037
+ return jsonError$4(res, 403, "workspace-root-not-allowed");
7983
8038
  }
7984
8039
  const stored = await deps.readImage(attachment);
7985
- if (!stored || !stored.data) return jsonError$3(res, 404, "image-not-found");
8040
+ if (!stored || !stored.data) return jsonError$4(res, 404, "image-not-found");
7986
8041
  const savedTo = await deps.saveToWorkspace({
7987
8042
  workspaceRoot: targetRoot,
7988
8043
  attachmentId: String(attachment.attachmentId),
@@ -8010,28 +8065,28 @@ async function serveSaveWorkspace(req, res, deps) {
8010
8065
  }
8011
8066
  /** Validate the persisted reference carried by a tool presentation. */
8012
8067
  function imageAttachmentFromMeta(meta) {
8013
- const value = record$3(meta);
8068
+ const value = record$4(meta);
8014
8069
  if (value?.kind !== "dsh-image-gen") return void 0;
8015
8070
  return parseImageAttachmentRef(value.attachment);
8016
8071
  }
8017
8072
  function attachmentFromRequest(value) {
8018
- return parseImageAttachmentRef(record$3(value)?.attachment);
8073
+ return parseImageAttachmentRef(record$4(value)?.attachment);
8019
8074
  }
8020
- function record$3(value) {
8075
+ function record$4(value) {
8021
8076
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
8022
8077
  }
8023
- async function readBody$2(req) {
8078
+ async function readBody$3(req) {
8024
8079
  const chunks = [];
8025
8080
  let bytes = 0;
8026
8081
  for await (const chunk of req) {
8027
8082
  const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
8028
8083
  bytes += buffer.byteLength;
8029
- if (bytes > MAX_BODY_BYTES$1) throw new Error("request too large");
8084
+ if (bytes > MAX_BODY_BYTES$2) throw new Error("request too large");
8030
8085
  chunks.push(buffer);
8031
8086
  }
8032
8087
  return Buffer.concat(chunks).toString("utf8");
8033
8088
  }
8034
- function jsonError$3(res, status, code) {
8089
+ function jsonError$4(res, status, code) {
8035
8090
  res.writeHead(status, {
8036
8091
  "content-type": "application/json",
8037
8092
  "cache-control": "no-store"
@@ -8059,6 +8114,27 @@ async function generateOpenAICompatibleImage(input) {
8059
8114
  }), input.provider, input);
8060
8115
  }
8061
8116
  async function editOpenAICompatibleImage(input) {
8117
+ if (input.editFormat === "jsonImageUrlArray") {
8118
+ const body = {
8119
+ model: input.model,
8120
+ images: input.sourceImages.map((sourceImage) => ({ image_url: toDataUrl$1(sourceImage) })),
8121
+ prompt: input.prompt,
8122
+ n: 1,
8123
+ size: "auto",
8124
+ response_format: "url",
8125
+ ...input.editExtra ?? {}
8126
+ };
8127
+ return parseImageResponse(await fetch(imageEndpoint$1(input.baseURL, "edits"), {
8128
+ method: "POST",
8129
+ redirect: "error",
8130
+ signal: input.signal,
8131
+ headers: {
8132
+ authorization: `Bearer ${input.apiKey}`,
8133
+ "content-type": "application/json"
8134
+ },
8135
+ body: JSON.stringify(body)
8136
+ }), "openai", input);
8137
+ }
8062
8138
  const form = new FormData();
8063
8139
  const imageField = input.sourceImages.length > 1 ? "image[]" : "image";
8064
8140
  input.sourceImages.forEach((sourceImage, index) => {
@@ -8102,6 +8178,9 @@ function imageEndpoint$1(baseURL, operation) {
8102
8178
  throw new Error("Image endpoint must be an absolute URL");
8103
8179
  }
8104
8180
  }
8181
+ function toDataUrl$1(image) {
8182
+ return `data:${image.mediaType};base64,${Buffer.from(image.data).toString("base64")}`;
8183
+ }
8105
8184
  function firstImage$1(value) {
8106
8185
  if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
8107
8186
  const record = value;
@@ -8111,10 +8190,10 @@ function firstImage$1(value) {
8111
8190
  if (typeof candidate !== "object" || candidate === null || Array.isArray(candidate)) return void 0;
8112
8191
  const item = candidate;
8113
8192
  const mime = typeof item.mime_type === "string" ? item.mime_type : typeof item.mime === "string" ? item.mime : void 0;
8114
- return typeof item.b64_json === "string" ? {
8193
+ return typeof item.b64_json === "string" && item.b64_json.length > 0 ? {
8115
8194
  b64_json: item.b64_json,
8116
8195
  ...mime === void 0 ? {} : { mime_type: mime }
8117
- } : typeof item.url === "string" ? {
8196
+ } : typeof item.url === "string" && item.url.length > 0 ? {
8118
8197
  url: item.url,
8119
8198
  ...mime === void 0 ? {} : { mime_type: mime }
8120
8199
  } : void 0;
@@ -8129,11 +8208,15 @@ async function downloadImage$1(url, provider, input) {
8129
8208
  mediaType: imageMediaType$2(parsed.mediaType) ?? "image/png"
8130
8209
  };
8131
8210
  }
8132
- const response = await fetch(url, {
8211
+ let response = await fetch(url, {
8133
8212
  redirect: "follow",
8134
8213
  signal: input.signal,
8135
8214
  ...input.apiKey === void 0 ? {} : { headers: { authorization: `Bearer ${input.apiKey}` } }
8136
8215
  });
8216
+ if (!response.ok && input.apiKey !== void 0) response = await fetch(url, {
8217
+ redirect: "follow",
8218
+ signal: input.signal
8219
+ });
8137
8220
  if (!response.ok) throw new Error(`${provider} image download failed (${response.status})`);
8138
8221
  const data = await readBoundedBytes$1(response, input.maxBytes);
8139
8222
  const mediaType = detectImageMediaType(data) ?? imageMediaType$2(response.headers.get("content-type"));
@@ -8252,10 +8335,10 @@ function firstImage(value) {
8252
8335
  if (typeof candidate !== "object" || candidate === null || Array.isArray(candidate)) return void 0;
8253
8336
  const item = candidate;
8254
8337
  const mime = typeof item.mime_type === "string" ? item.mime_type : typeof item.mime === "string" ? item.mime : void 0;
8255
- return typeof item.b64_json === "string" ? {
8338
+ return typeof item.b64_json === "string" && item.b64_json.length > 0 ? {
8256
8339
  b64_json: item.b64_json,
8257
8340
  ...mime === void 0 ? {} : { mime_type: mime }
8258
- } : typeof item.url === "string" ? {
8341
+ } : typeof item.url === "string" && item.url.length > 0 ? {
8259
8342
  url: item.url,
8260
8343
  ...mime === void 0 ? {} : { mime_type: mime }
8261
8344
  } : void 0;
@@ -8313,6 +8396,1193 @@ async function readBoundedBytes(response, maxBytes) {
8313
8396
  return joined;
8314
8397
  }
8315
8398
  //#endregion
8399
+ //#region lib/types/subscription/oauth.js
8400
+ /**
8401
+ * OAuth primitives shared by the subscription vendors.
8402
+ *
8403
+ * Adapted from @goodandready/dsh-subscriptions (MIT, (c) 2026 GooDAnDReaDY)
8404
+ * lib/oauth.js, lib/pkce.js, lib/jwt.js — PKCE generation, authorize-URL
8405
+ * building and the JWT payload reads the vendors need for identity headers.
8406
+ */
8407
+ async function createPkce() {
8408
+ const verifier = Buffer.from(randomBytes(32)).toString("base64url");
8409
+ return {
8410
+ verifier,
8411
+ challenge: Buffer.from(createHash("sha256").update(verifier).digest()).toString("base64url"),
8412
+ state: Buffer.from(randomBytes(16)).toString("base64url")
8413
+ };
8414
+ }
8415
+ /** Build an authorization-code URL with PKCE and optional extra params. */
8416
+ function buildAuthorizeUrl(input) {
8417
+ const url = new URL(input.authUrl);
8418
+ url.searchParams.set("response_type", "code");
8419
+ url.searchParams.set("client_id", input.clientId);
8420
+ url.searchParams.set("redirect_uri", input.redirectUri);
8421
+ url.searchParams.set("code_challenge", input.challenge);
8422
+ url.searchParams.set("code_challenge_method", "S256");
8423
+ url.searchParams.set("state", input.state);
8424
+ if (input.scope !== void 0 && input.scope.length > 0) url.searchParams.set("scope", input.scope);
8425
+ if (input.extra !== void 0) for (const [key, value] of Object.entries(input.extra)) {
8426
+ if (value === null || value === void 0 || value === "") continue;
8427
+ url.searchParams.set(key, String(value));
8428
+ }
8429
+ return url.toString();
8430
+ }
8431
+ /** Decode a JWT payload without signature verification (identity read only). */
8432
+ function decodeJwtPayload(token) {
8433
+ const parts = String(token ?? "").split(".");
8434
+ if (parts.length < 2) return void 0;
8435
+ try {
8436
+ const json = Buffer.from(parts[1], "base64url").toString("utf8");
8437
+ const parsed = JSON.parse(json);
8438
+ return typeof parsed === "object" && parsed !== null ? parsed : void 0;
8439
+ } catch {
8440
+ return;
8441
+ }
8442
+ }
8443
+ /** The chatgpt-account-id a Codex API call must carry. */
8444
+ function chatgptAccountId(token) {
8445
+ const payload = decodeJwtPayload(token);
8446
+ if (payload === void 0) return "";
8447
+ if (typeof payload.chatgpt_account_id === "string") return payload.chatgpt_account_id;
8448
+ const nested = payload["https://api.openai.com/auth"];
8449
+ if (typeof nested === "object" && nested !== null && typeof nested.chatgpt_account_id === "string") return nested.chatgpt_account_id;
8450
+ const orgs = payload.organizations;
8451
+ if (Array.isArray(orgs) && orgs[0] !== null && typeof orgs[0] === "object" && typeof orgs[0].id === "string") return orgs[0].id;
8452
+ return "";
8453
+ }
8454
+ /** The account email for badges and login confirmation. */
8455
+ function emailFromToken(token) {
8456
+ const payload = decodeJwtPayload(token);
8457
+ if (payload === void 0) return "";
8458
+ const email = payload.email;
8459
+ const preferred = payload.preferred_username;
8460
+ if (typeof email === "string" && email.length > 0) return email;
8461
+ if (typeof preferred === "string") return preferred;
8462
+ return "";
8463
+ }
8464
+ /** POST an OAuth token endpoint with form encoding; 25s cap like the source. */
8465
+ async function formTokenRequest(url, params, fetchImpl) {
8466
+ const response = await fetchImpl(url, {
8467
+ method: "POST",
8468
+ headers: {
8469
+ "Content-Type": "application/x-www-form-urlencoded",
8470
+ Accept: "application/json"
8471
+ },
8472
+ body: new URLSearchParams(params).toString(),
8473
+ signal: AbortSignal.timeout(25e3)
8474
+ });
8475
+ if (!response.ok) {
8476
+ const text = await response.text().catch(() => "");
8477
+ throw new Error(`token endpoint HTTP ${String(response.status)}${text.length > 0 ? `: ${text.slice(0, 200)}` : ""}`);
8478
+ }
8479
+ try {
8480
+ const parsed = await response.json();
8481
+ return typeof parsed === "object" && parsed !== null ? parsed : {};
8482
+ } catch {
8483
+ return {};
8484
+ }
8485
+ }
8486
+ //#endregion
8487
+ //#region lib/types/subscription/loopback.js
8488
+ /**
8489
+ * Temporary loopback server that catches the OAuth redirect on a vendor-fixed
8490
+ * local port and hands the code to a handler.
8491
+ *
8492
+ * Adapted from @goodandready/dsh-subscriptions (MIT, (c) 2026 GooDAnDReaDY)
8493
+ * lib/loopback.js — same protocol (first matching request wins, server shuts
8494
+ * down, 10-minute cap), restated in TypeScript with this bundle's style.
8495
+ */
8496
+ const OK_HTML = "<!doctype html><meta charset=\"utf-8\"><title>dsh-image-gen</title><p>登录成功,可以关闭此页返回设置。</p>";
8497
+ const ERR_HTML = "<!doctype html><meta charset=\"utf-8\"><title>dsh-image-gen</title><p>登录失败,请返回设置重试。</p>";
8498
+ /**
8499
+ * Listen on the redirect_uri's port and resolve when the provider calls back.
8500
+ * Rejects on port conflicts, network errors, or when nothing arrives in time.
8501
+ */
8502
+ function startLoopback(options) {
8503
+ const parsed = new URL(options.redirectUri);
8504
+ if (parsed.hostname !== "localhost" && parsed.hostname !== "127.0.0.1") return Promise.reject(/* @__PURE__ */ new Error(`loopback redirect requires localhost, got ${parsed.hostname}`));
8505
+ const port = Number(parsed.port) || 80;
8506
+ const path = parsed.pathname;
8507
+ const timeoutMs = options.timeoutMs ?? 6e5;
8508
+ const state = { done: false };
8509
+ return new Promise((resolve, reject) => {
8510
+ const server = http.createServer((req, res) => {
8511
+ const url = new URL(req.url ?? "/", `http://127.0.0.1:${String(port)}`);
8512
+ if (!url.pathname.startsWith(path)) {
8513
+ res.writeHead(404, { "Content-Type": "text/html; charset=utf-8" });
8514
+ res.end(ERR_HTML);
8515
+ return;
8516
+ }
8517
+ if (state.done) return;
8518
+ state.done = true;
8519
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
8520
+ Promise.resolve(options.onCode(url.searchParams)).then((html) => {
8521
+ res.end(html.length > 0 ? html : OK_HTML);
8522
+ }).catch(() => {
8523
+ res.end(ERR_HTML);
8524
+ }).finally(() => {
8525
+ clearTimeout(timer);
8526
+ server.close();
8527
+ resolve({ ok: true });
8528
+ });
8529
+ });
8530
+ server.on("error", (error) => {
8531
+ if (state.done) return;
8532
+ state.done = true;
8533
+ clearTimeout(timer);
8534
+ reject(error);
8535
+ });
8536
+ const timer = setTimeout(() => {
8537
+ if (state.done) return;
8538
+ state.done = true;
8539
+ server.close();
8540
+ reject(/* @__PURE__ */ new Error("loopback timeout: no callback received"));
8541
+ }, timeoutMs);
8542
+ server.listen(port, parsed.hostname);
8543
+ });
8544
+ }
8545
+ //#endregion
8546
+ //#region lib/types/subscription/blob.js
8547
+ /**
8548
+ * OAuth token blob persistence for subscription accounts.
8549
+ *
8550
+ * Adapted from @goodandready/dsh-subscriptions (MIT, (c) 2026 GooDAnDReaDY)
8551
+ * lib/blob.js — trimmed to the two image vendors this plugin ships. The blob
8552
+ * is stored through the DSH Credentials service under a provider-scoped ref
8553
+ * (see refs.ts); it never reaches the browser side.
8554
+ */
8555
+ function asString(value) {
8556
+ return value === null || value === void 0 ? "" : String(value);
8557
+ }
8558
+ /** Serialize a blob for storage; at least one token must be present. */
8559
+ function serializeBlob(obj) {
8560
+ const accessToken = asString(obj.accessToken);
8561
+ const refreshToken = asString(obj.refreshToken);
8562
+ if (accessToken.length === 0 && refreshToken.length === 0) throw new Error("oauth blob needs accessToken or refreshToken");
8563
+ return JSON.stringify({
8564
+ accessToken,
8565
+ refreshToken,
8566
+ expiresAt: Number(obj.expiresAt) || 0,
8567
+ label: asString(obj.label),
8568
+ email: asString(obj.email),
8569
+ accountId: asString(obj.accountId)
8570
+ });
8571
+ }
8572
+ /** Parse a stored blob back; invalid input throws. */
8573
+ function parseBlob(text) {
8574
+ const obj = typeof text === "string" ? JSON.parse(text) : text;
8575
+ if (typeof obj !== "object" || obj === null) throw new Error("invalid oauth blob");
8576
+ const row = obj;
8577
+ return {
8578
+ accessToken: asString(row.accessToken),
8579
+ refreshToken: asString(row.refreshToken),
8580
+ expiresAt: Number(row.expiresAt) || 0,
8581
+ label: asString(row.label),
8582
+ email: asString(row.email),
8583
+ accountId: asString(row.accountId)
8584
+ };
8585
+ }
8586
+ //#endregion
8587
+ //#region lib/types/subscription/vendors/codex.js
8588
+ /**
8589
+ * ChatGPT (Codex) subscription image vendor.
8590
+ *
8591
+ * Adapted from @goodandready/dsh-subscriptions (MIT, (c) 2026 GooDAnDReaDY)
8592
+ * lib/vendors/codex.js and lib/images.js — only the pieces the image channel
8593
+ * needs: authorize URL, code exchange, refresh, and the generations call.
8594
+ * The device-code flow and chat streaming stay out of this bundle.
8595
+ */
8596
+ const CODEX_AUTH = "https://auth.openai.com/oauth/authorize";
8597
+ const CODEX_TOKEN = "https://auth.openai.com/oauth/token";
8598
+ const CODEX_SCOPE = "openid profile email offline_access api.connectors.read api.connectors.invoke";
8599
+ /** Where the ChatGPT subscription image request goes. */
8600
+ const CODEX_IMAGE_URL = "https://chatgpt.com/backend-api/codex/images/generations";
8601
+ /** The model served by this endpoint (probed live: gpt-image-2.5-flare works
8602
+ * on the ChatGPT internal generations route as of 2026-09-14). */
8603
+ const CODEX_IMAGE_MODEL = "gpt-image-2.5-flare";
8604
+ /** Public client id of the Codex CLI; vendor-fixed redirect on port 1455. */
8605
+ const CODEX_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
8606
+ const CODEX_REDIRECT_URI = "http://localhost:1455/auth/callback";
8607
+ function codexConfig() {
8608
+ return {
8609
+ clientId: CODEX_CLIENT_ID,
8610
+ redirectUri: CODEX_REDIRECT_URI
8611
+ };
8612
+ }
8613
+ function codexAuthorizeUrl(cfg, pkce) {
8614
+ return buildAuthorizeUrl({
8615
+ authUrl: CODEX_AUTH,
8616
+ clientId: cfg.clientId,
8617
+ redirectUri: cfg.redirectUri,
8618
+ challenge: pkce.challenge,
8619
+ state: pkce.state,
8620
+ scope: CODEX_SCOPE,
8621
+ extra: {
8622
+ id_token_add_organizations: "true",
8623
+ codex_cli_simplified_flow: "true",
8624
+ originator: "codex_cli_rs"
8625
+ }
8626
+ });
8627
+ }
8628
+ /** Identity fields every Codex API call must carry. */
8629
+ function codexIdentityHeaders(blob) {
8630
+ return {
8631
+ authorization: `Bearer ${blob.accessToken}`,
8632
+ "chatgpt-account-id": blob.accountId,
8633
+ originator: "codex_cli_rs",
8634
+ "content-type": "application/json",
8635
+ accept: "application/json"
8636
+ };
8637
+ }
8638
+ function decorate(blob, json) {
8639
+ const idToken = typeof json.id_token === "string" ? json.id_token : "";
8640
+ return {
8641
+ ...blob,
8642
+ accountId: blob.accountId.length > 0 ? blob.accountId : chatgptAccountId(idToken) || chatgptAccountId(blob.accessToken),
8643
+ email: blob.email.length > 0 ? blob.email : emailFromToken(idToken) || emailFromToken(blob.accessToken),
8644
+ label: blob.label.length > 0 ? blob.label : emailFromToken(idToken) || emailFromToken(blob.accessToken) || "ChatGPT"
8645
+ };
8646
+ }
8647
+ function tokenBlobFromOAuth$2(json) {
8648
+ const access = json.access_token;
8649
+ const refresh = json.refresh_token;
8650
+ return {
8651
+ accessToken: typeof access === "string" ? access : "",
8652
+ refreshToken: typeof refresh === "string" ? refresh : "",
8653
+ expiresAt: Date.now() + (Number(json.expires_in) || 3600) * 1e3,
8654
+ label: "",
8655
+ email: "",
8656
+ accountId: ""
8657
+ };
8658
+ }
8659
+ async function codexExchangeCode(cfg, pkce, code) {
8660
+ const json = await formTokenRequest(CODEX_TOKEN, {
8661
+ grant_type: "authorization_code",
8662
+ client_id: cfg.clientId,
8663
+ code,
8664
+ redirect_uri: cfg.redirectUri,
8665
+ code_verifier: pkce.verifier
8666
+ }, fetch);
8667
+ return decorate(tokenBlobFromOAuth$2(json), json);
8668
+ }
8669
+ async function codexRefresh(blob) {
8670
+ const next = tokenBlobFromOAuth$2(await formTokenRequest(CODEX_TOKEN, {
8671
+ grant_type: "refresh_token",
8672
+ client_id: CODEX_CLIENT_ID,
8673
+ refresh_token: blob.refreshToken
8674
+ }, fetch));
8675
+ return {
8676
+ ...next,
8677
+ refreshToken: next.refreshToken.length > 0 ? next.refreshToken : blob.refreshToken,
8678
+ label: blob.label,
8679
+ email: blob.email,
8680
+ accountId: blob.accountId.length > 0 ? blob.accountId : next.accountId
8681
+ };
8682
+ }
8683
+ //#endregion
8684
+ //#region lib/types/subscription/vendors/grok.js
8685
+ /**
8686
+ * Grok subscription image vendor.
8687
+ *
8688
+ * Adapted from @goodandready/dsh-subscriptions (MIT, (c) 2026 GooDAnDReaDY)
8689
+ * lib/vendors/grok.js and lib/images.js — only the pieces the image channel
8690
+ * needs: authorize URL, code exchange, refresh, and the generations call
8691
+ * (grok-cli token, not an xAI API key).
8692
+ */
8693
+ const GROK_AUTH = "https://auth.x.ai/oauth2/authorize";
8694
+ const GROK_TOKEN = "https://auth.x.ai/oauth2/token";
8695
+ const GROK_SCOPE = "openid profile email offline_access grok-cli:access api:access conversations:read conversations:write";
8696
+ /** Where the Grok subscription image request goes. */
8697
+ const GROK_IMAGE_URL = "https://api.x.ai/v1/images/generations";
8698
+ /** The model served by this endpoint. */
8699
+ const GROK_IMAGE_MODEL = "grok-imagine-image-2.0";
8700
+ /** Public client id of the grok-cli; vendor-fixed redirect on 127.0.0.1:56121. */
8701
+ const GROK_CLIENT_ID = "b1a00492-073a-47ea-816f-4c329264a828";
8702
+ const GROK_REDIRECT_URI = "http://127.0.0.1:56121/callback";
8703
+ function grokConfig() {
8704
+ return {
8705
+ clientId: GROK_CLIENT_ID,
8706
+ redirectUri: GROK_REDIRECT_URI
8707
+ };
8708
+ }
8709
+ function grokAuthorizeUrl(cfg, pkce) {
8710
+ return buildAuthorizeUrl({
8711
+ authUrl: GROK_AUTH,
8712
+ clientId: cfg.clientId,
8713
+ redirectUri: cfg.redirectUri,
8714
+ challenge: pkce.challenge,
8715
+ state: pkce.state,
8716
+ scope: GROK_SCOPE
8717
+ });
8718
+ }
8719
+ /** Identity fields every grok.com call must carry (the API host ignores them). */
8720
+ function grokIdentityHeaders(blob) {
8721
+ return {
8722
+ authorization: `Bearer ${blob.accessToken}`,
8723
+ "X-XAI-Token-Auth": "xai-grok-cli",
8724
+ "x-grok-client-identifier": "grok-shell",
8725
+ "x-grok-client-version": "0.2.103",
8726
+ "User-Agent": "xai-grok-cli",
8727
+ "content-type": "application/json",
8728
+ accept: "application/json"
8729
+ };
8730
+ }
8731
+ function tokenBlobFromOAuth$1(json) {
8732
+ const access = json.access_token;
8733
+ const refresh = json.refresh_token;
8734
+ return {
8735
+ accessToken: typeof access === "string" ? access : "",
8736
+ refreshToken: typeof refresh === "string" ? refresh : "",
8737
+ expiresAt: Date.now() + (Number(json.expires_in) || 3600) * 1e3,
8738
+ label: "",
8739
+ email: "",
8740
+ accountId: ""
8741
+ };
8742
+ }
8743
+ async function grokExchangeCode(cfg, pkce, code) {
8744
+ const blob = tokenBlobFromOAuth$1(await formTokenRequest(GROK_TOKEN, {
8745
+ grant_type: "authorization_code",
8746
+ client_id: cfg.clientId,
8747
+ code,
8748
+ redirect_uri: cfg.redirectUri,
8749
+ code_verifier: pkce.verifier
8750
+ }, fetch));
8751
+ return {
8752
+ ...blob,
8753
+ email: blob.email.length > 0 ? blob.email : emailFromToken(blob.accessToken),
8754
+ label: "Grok"
8755
+ };
8756
+ }
8757
+ async function grokRefresh(blob) {
8758
+ const next = tokenBlobFromOAuth$1(await formTokenRequest(GROK_TOKEN, {
8759
+ grant_type: "refresh_token",
8760
+ client_id: GROK_CLIENT_ID,
8761
+ refresh_token: blob.refreshToken
8762
+ }, fetch));
8763
+ return {
8764
+ ...next,
8765
+ refreshToken: next.refreshToken.length > 0 ? next.refreshToken : blob.refreshToken,
8766
+ label: blob.label.length > 0 ? blob.label : "Grok",
8767
+ email: blob.email.length > 0 ? blob.email : next.email
8768
+ };
8769
+ }
8770
+ //#endregion
8771
+ //#region lib/types/subscription/vendors/antigravity.js
8772
+ /**
8773
+ * Google Antigravity (Nano Banana Pro) subscription image vendor.
8774
+ *
8775
+ * Protocol adapted from opencode-antigravity-auth-remix (MIT,
8776
+ * (c) 2026 Darkstarrd-dev, based on NoeFabris/opencode-antigravity-auth):
8777
+ * Google OAuth with the Antigravity CLI's public client credentials, managed
8778
+ * project discovery via loadCodeAssist, and image generation through the
8779
+ * v1internal:streamGenerateContent endpoint with the gemini-3-pro-image
8780
+ * model. Reference: dist/src/constants.js, dist/src/plugin/image.js,
8781
+ * dist/src/plugin/project.js.
8782
+ *
8783
+ * The dsh-subscriptions antigravity vendor left the OAuth client empty
8784
+ * (Google's is confidential); the remix package ships the Antigravity CLI's
8785
+ * own embedded client id/secret, which the community treats as public the
8786
+ * same way Codex CLI's and grok-cli's are.
8787
+ */
8788
+ const AUTH = "https://accounts.google.com/o/oauth2/v2/auth";
8789
+ const TOKEN = "https://oauth2.googleapis.com/token";
8790
+ const SCOPE = [
8791
+ "https://www.googleapis.com/auth/cloud-platform",
8792
+ "https://www.googleapis.com/auth/userinfo.email",
8793
+ "https://www.googleapis.com/auth/userinfo.profile",
8794
+ "https://www.googleapis.com/auth/cclog",
8795
+ "https://www.googleapis.com/auth/experimentsandconfigs"
8796
+ ].join(" ");
8797
+ /** Antigravity CLI's embedded OAuth client, treated as public like Codex's. */
8798
+ const ANTIGRAVITY_CLIENT_ID = "1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com";
8799
+ const ANTIGRAVITY_CLIENT_SECRET = "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";
8800
+ const ANTIGRAVITY_REDIRECT_URI = "http://localhost:51121/oauth-callback";
8801
+ /**
8802
+ * Endpoint fallback order for generation, mirroring the reference (daily
8803
+ * sandbox first, then autopush, then prod).
8804
+ */
8805
+ const ANTIGRAVITY_ENDPOINTS = [
8806
+ "https://daily-cloudcode-pa.sandbox.googleapis.com",
8807
+ "https://autopush-cloudcode-pa.sandbox.googleapis.com",
8808
+ "https://cloudcode-pa.googleapis.com"
8809
+ ];
8810
+ /** loadCodeAssist is best supported on prod; discovery tries prod first. */
8811
+ const LOAD_ENDPOINTS = [
8812
+ "https://cloudcode-pa.googleapis.com",
8813
+ "https://daily-cloudcode-pa.sandbox.googleapis.com",
8814
+ "https://autopush-cloudcode-pa.sandbox.googleapis.com"
8815
+ ];
8816
+ /** The image model served through this channel: Nano Banana Pro. */
8817
+ const ANTIGRAVITY_IMAGE_MODEL = "gemini-3-pro-image";
8818
+ /**
8819
+ * Antigravity client version carried in the browser User-Agent. Google's
8820
+ * backend routes by this: a non-Antigravity or outdated UA lands in the
8821
+ * enterprise-license check and fails with the misleading 403 #3501
8822
+ * "You do not have a valid license of this product". The official
8823
+ * auto-updater publishes the current version; changelog scrape and the
8824
+ * hardcoded fallback keep the header sane when the updater is unreachable
8825
+ * (same strategy as the reference implementation's version.js).
8826
+ */
8827
+ const ANTIGRAVITY_VERSION_FALLBACK = "2.0.6";
8828
+ const VERSION_URL = "https://antigravity-auto-updater-974169037036.us-central1.run.app";
8829
+ const CHANGELOG_URL = "https://antigravity.google/changelog";
8830
+ const VERSION_FETCH_TIMEOUT_MS = 5e3;
8831
+ const CHANGELOG_SCAN_CHARS = 5e3;
8832
+ const VERSION_REGEX = /\d+\.\d+\.\d+/;
8833
+ let antigravityVersion;
8834
+ /** Current client version: the fetched one, or the fallback until fetched. */
8835
+ function getAntigravityVersion() {
8836
+ return antigravityVersion ?? ANTIGRAVITY_VERSION_FALLBACK;
8837
+ }
8838
+ /** Fetch and cache the current version once; failures keep the fallback. */
8839
+ async function initAntigravityVersion() {
8840
+ if (antigravityVersion !== void 0) return;
8841
+ const parsed = await fetchVersionText(VERSION_URL) ?? await fetchVersionText(CHANGELOG_URL, CHANGELOG_SCAN_CHARS);
8842
+ if (parsed !== void 0) antigravityVersion = parsed;
8843
+ }
8844
+ async function fetchVersionText(url, maxChars) {
8845
+ try {
8846
+ const response = await fetch(url, { signal: AbortSignal.timeout(VERSION_FETCH_TIMEOUT_MS) });
8847
+ if (!response.ok) return void 0;
8848
+ let text = await response.text();
8849
+ if (maxChars !== void 0) text = text.slice(0, maxChars);
8850
+ const match = text.match(VERSION_REGEX);
8851
+ return match === null ? void 0 : match[0];
8852
+ } catch {
8853
+ return;
8854
+ }
8855
+ }
8856
+ /** Hardcoded fallback project when Antigravity returns none (workspace accounts). */
8857
+ const DEFAULT_PROJECT_ID = "rising-fact-p41fc";
8858
+ /** Generation timeout mirroring the reference implementation. */
8859
+ const IMAGE_TIMEOUT_MS = 12e4;
8860
+ /** Aspect ratios the generationConfig.imageConfig accepts. */
8861
+ const ASPECT_RATIOS = [
8862
+ "1:1",
8863
+ "16:9",
8864
+ "9:16",
8865
+ "4:3",
8866
+ "3:4",
8867
+ "21:9"
8868
+ ];
8869
+ /** Safety settings the image endpoint requires to not block benign prompts. */
8870
+ const SAFETY_SETTINGS_OFF = [
8871
+ {
8872
+ category: "HARM_CATEGORY_HARASSMENT",
8873
+ threshold: "OFF"
8874
+ },
8875
+ {
8876
+ category: "HARM_CATEGORY_HATE_SPEECH",
8877
+ threshold: "OFF"
8878
+ },
8879
+ {
8880
+ category: "HARM_CATEGORY_SEXUALLY_EXPLICIT",
8881
+ threshold: "OFF"
8882
+ },
8883
+ {
8884
+ category: "HARM_CATEGORY_DANGEROUS_CONTENT",
8885
+ threshold: "OFF"
8886
+ },
8887
+ {
8888
+ category: "HARM_CATEGORY_CIVIC_INTEGRITY",
8889
+ threshold: "OFF"
8890
+ }
8891
+ ];
8892
+ function antigravityConfig() {
8893
+ return {
8894
+ clientId: ANTIGRAVITY_CLIENT_ID,
8895
+ clientSecret: ANTIGRAVITY_CLIENT_SECRET,
8896
+ redirectUri: ANTIGRAVITY_REDIRECT_URI
8897
+ };
8898
+ }
8899
+ function antigravityAuthorizeUrl(cfg, pkce) {
8900
+ return buildAuthorizeUrl({
8901
+ authUrl: AUTH,
8902
+ clientId: cfg.clientId,
8903
+ redirectUri: cfg.redirectUri,
8904
+ challenge: pkce.challenge,
8905
+ state: pkce.state,
8906
+ scope: SCOPE,
8907
+ extra: {
8908
+ access_type: "offline",
8909
+ prompt: "consent"
8910
+ }
8911
+ });
8912
+ }
8913
+ /**
8914
+ * Headers for project discovery (loadCodeAssist): the reference keeps the
8915
+ * plain SDK identity here, mirroring its project.js loadHeaders.
8916
+ */
8917
+ function loadHeaders(projectId) {
8918
+ const platform = process.platform === "win32" ? "WINDOWS" : process.platform === "darwin" ? "MACOS" : "LINUX";
8919
+ return {
8920
+ "User-Agent": "google-api-nodejs-client/9.15.1",
8921
+ "X-Goog-Api-Client": "google-cloud-sdk vscode_cloudshelleditor/0.1",
8922
+ "Client-Metadata": JSON.stringify({
8923
+ ideType: "ANTIGRAVITY",
8924
+ platform,
8925
+ pluginType: "GEMINI",
8926
+ ...projectId.length > 0 ? { duetProject: projectId } : {}
8927
+ })
8928
+ };
8929
+ }
8930
+ /**
8931
+ * Headers for content requests (image generation): the full Electron
8932
+ * browser User-Agent with the Antigravity client version, matching the
8933
+ * official IDE. This is the identity the license check keys on — the plain
8934
+ * SDK UA here is what produced 403 #3501 on personal accounts.
8935
+ */
8936
+ function contentHeaders(projectId) {
8937
+ const uaPlatform = process.platform === "darwin" ? "Macintosh; Intel Mac OS X 10_15_7" : "Windows NT 10.0; Win64; x64";
8938
+ const platform = process.platform === "win32" ? "WINDOWS" : process.platform === "darwin" ? "MACOS" : "LINUX";
8939
+ return {
8940
+ "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`,
8941
+ "X-Goog-Api-Client": "google-cloud-sdk vscode_cloudshelleditor/0.1",
8942
+ "Client-Metadata": JSON.stringify({
8943
+ ideType: "ANTIGRAVITY",
8944
+ platform,
8945
+ pluginType: "GEMINI",
8946
+ ...projectId.length > 0 ? { duetProject: projectId } : {}
8947
+ })
8948
+ };
8949
+ }
8950
+ function tokenBlobFromOAuth(json) {
8951
+ const access = json.access_token;
8952
+ const refresh = json.refresh_token;
8953
+ const idToken = typeof json.id_token === "string" ? json.id_token : "";
8954
+ return {
8955
+ accessToken: typeof access === "string" ? access : "",
8956
+ refreshToken: typeof refresh === "string" ? refresh : "",
8957
+ expiresAt: Date.now() + (Number(json.expires_in) || 3600) * 1e3,
8958
+ label: "",
8959
+ email: emailFromIdToken(idToken),
8960
+ accountId: ""
8961
+ };
8962
+ }
8963
+ /** Decode the id_token's JWT payload for the account email (identity only). */
8964
+ function emailFromIdToken(idToken) {
8965
+ const parts = idToken.split(".");
8966
+ if (parts.length < 2) return "";
8967
+ try {
8968
+ const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf8"));
8969
+ return typeof payload.email === "string" ? payload.email : "";
8970
+ } catch {
8971
+ return "";
8972
+ }
8973
+ }
8974
+ async function antigravityExchangeCode(cfg, pkce, code) {
8975
+ const blob = tokenBlobFromOAuth(await formTokenRequest(TOKEN, {
8976
+ client_id: cfg.clientId,
8977
+ client_secret: cfg.clientSecret,
8978
+ grant_type: "authorization_code",
8979
+ code,
8980
+ redirect_uri: cfg.redirectUri,
8981
+ code_verifier: pkce.verifier
8982
+ }, fetch));
8983
+ if (blob.accessToken.length === 0) throw new Error("Antigravity token endpoint returned no access token");
8984
+ return blob;
8985
+ }
8986
+ async function antigravityRefresh(blob) {
8987
+ const next = tokenBlobFromOAuth(await formTokenRequest(TOKEN, {
8988
+ client_id: ANTIGRAVITY_CLIENT_ID,
8989
+ client_secret: ANTIGRAVITY_CLIENT_SECRET,
8990
+ grant_type: "refresh_token",
8991
+ refresh_token: blob.refreshToken
8992
+ }, fetch));
8993
+ return {
8994
+ ...next,
8995
+ refreshToken: next.refreshToken.length > 0 ? next.refreshToken : blob.refreshToken,
8996
+ label: blob.label,
8997
+ email: blob.email.length > 0 ? blob.email : next.email,
8998
+ accountId: ""
8999
+ };
9000
+ }
9001
+ /**
9002
+ * Resolve the managed project id for the logged-in account. Most accounts
9003
+ * have one; when loadCodeAssist reports none, the hardcoded fallback keeps
9004
+ * generation working (mirrors the reference's ensureProjectContext).
9005
+ * Returns '' only when every endpoint fails, letting generate() throw then.
9006
+ */
9007
+ async function antigravityResolveProject(blob) {
9008
+ const metadata = {
9009
+ ideType: "ANTIGRAVITY",
9010
+ platform: platformOf(),
9011
+ pluginType: "GEMINI"
9012
+ };
9013
+ for (const base of LOAD_ENDPOINTS) try {
9014
+ const response = await fetch(`${base}/v1internal:loadCodeAssist`, {
9015
+ method: "POST",
9016
+ headers: {
9017
+ "Content-Type": "application/json",
9018
+ Authorization: `Bearer ${blob.accessToken}`,
9019
+ ...loadHeaders("")
9020
+ },
9021
+ body: JSON.stringify({ metadata }),
9022
+ signal: AbortSignal.timeout(25e3)
9023
+ });
9024
+ if (!response.ok) continue;
9025
+ const payload = await response.json().catch(() => null);
9026
+ if (payload === null) continue;
9027
+ const project = payload.cloudaicompanionProject;
9028
+ const id = typeof project === "string" ? project : typeof project === "object" && project !== null && typeof project.id === "string" ? project.id : "";
9029
+ if (id.length > 0) return id;
9030
+ } catch {}
9031
+ return DEFAULT_PROJECT_ID;
9032
+ }
9033
+ function platformOf() {
9034
+ if (process.platform === "win32") return "WINDOWS";
9035
+ if (process.platform === "darwin") return "MACOS";
9036
+ return "LINUX";
9037
+ }
9038
+ /**
9039
+ * Build the streamGenerateContent request body for one image generation.
9040
+ * Ported from the reference buildImageRequest + the wrapping envelope: no
9041
+ * tools, no systemInstruction, no thinkingConfig; responseModalities asks
9042
+ * for TEXT+IMAGE.
9043
+ */
9044
+ function antigravityImageBody(options) {
9045
+ const imageConfig = { aspectRatio: options.aspectRatio !== void 0 && ASPECT_RATIOS.includes(options.aspectRatio) ? options.aspectRatio : "1:1" };
9046
+ if (options.hd === true) imageConfig.imageSize = "4K";
9047
+ return {
9048
+ contents: [{
9049
+ role: "user",
9050
+ parts: [{ text: options.prompt }]
9051
+ }],
9052
+ generationConfig: {
9053
+ candidateCount: 1,
9054
+ imageConfig,
9055
+ responseModalities: ["TEXT", "IMAGE"]
9056
+ },
9057
+ safetySettings: SAFETY_SETTINGS_OFF
9058
+ };
9059
+ }
9060
+ /** Wrap the inner request in the Antigravity agent envelope. */
9061
+ function antigravityEnvelope(projectId, request) {
9062
+ return {
9063
+ project: projectId,
9064
+ model: ANTIGRAVITY_IMAGE_MODEL,
9065
+ userAgent: "antigravity",
9066
+ requestId: `req_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`,
9067
+ requestType: "agent",
9068
+ request: {
9069
+ ...request,
9070
+ session_id: `sess-${randomUUID()}`
9071
+ }
9072
+ };
9073
+ }
9074
+ /**
9075
+ * Generate one image on the logged-in Antigravity account: resolve the
9076
+ * project, POST the wrapped body to each endpoint in fallback order, and
9077
+ * parse the SSE stream for the inlineData image part.
9078
+ */
9079
+ async function antigravityGenerateImage(options) {
9080
+ await initAntigravityVersion();
9081
+ const inner = antigravityImageBody({
9082
+ prompt: options.prompt,
9083
+ ...options.aspectRatio !== void 0 ? { aspectRatio: options.aspectRatio } : {},
9084
+ ...options.hd !== void 0 ? { hd: options.hd } : {}
9085
+ });
9086
+ const body = antigravityEnvelope(options.projectId, inner);
9087
+ let lastError = "no endpoint succeeded";
9088
+ for (const base of ANTIGRAVITY_ENDPOINTS) {
9089
+ const url = `${base}/v1internal:streamGenerateContent?alt=sse`;
9090
+ const timeoutSignal = AbortSignal.timeout(IMAGE_TIMEOUT_MS);
9091
+ const signal = options.signal !== void 0 ? AbortSignal.any([options.signal, timeoutSignal]) : timeoutSignal;
9092
+ try {
9093
+ const response = await fetch(url, {
9094
+ method: "POST",
9095
+ headers: {
9096
+ ...contentHeaders(options.projectId),
9097
+ Authorization: `Bearer ${options.blob.accessToken}`,
9098
+ "Content-Type": "application/json",
9099
+ Accept: "text/event-stream"
9100
+ },
9101
+ body: JSON.stringify(body),
9102
+ signal
9103
+ });
9104
+ if (!response.ok) {
9105
+ const text = await response.text().catch(() => "");
9106
+ lastError = `HTTP ${String(response.status)}: ${text.slice(0, 200)}`;
9107
+ if (response.status === 403 || response.status === 404 || response.status === 429 || response.status >= 500) continue;
9108
+ throw new Error(`antigravity ${lastError}`);
9109
+ }
9110
+ const text = await response.text();
9111
+ const parsed = parseSseImage(text);
9112
+ if (parsed !== void 0) return parsed;
9113
+ const reason = parseSseError(text);
9114
+ lastError = reason !== void 0 ? reason : "response contained no image";
9115
+ continue;
9116
+ } catch (error) {
9117
+ if (options.signal?.aborted === true) throw error;
9118
+ lastError = error instanceof Error ? error.message : String(error);
9119
+ continue;
9120
+ }
9121
+ }
9122
+ throw new Error(`antigravity image generation failed: ${lastError}`);
9123
+ }
9124
+ /** Scan an SSE body for the first image part in any data: line. */
9125
+ function parseSseImage(text) {
9126
+ for (const line of text.split("\n")) {
9127
+ if (!line.startsWith("data: ")) continue;
9128
+ const raw = line.slice(6).trim();
9129
+ if (raw.length === 0 || raw === "[DONE]") continue;
9130
+ try {
9131
+ const data = JSON.parse(raw);
9132
+ for (const candidate of data.response?.candidates ?? []) for (const part of candidate.content?.parts ?? []) {
9133
+ const inline = part.inlineData;
9134
+ if (typeof inline?.data === "string" && inline.data.length > 0 && typeof inline.mimeType === "string" && inline.mimeType.startsWith("image/")) return {
9135
+ b64: inline.data,
9136
+ mimeType: inline.mimeType
9137
+ };
9138
+ }
9139
+ } catch {}
9140
+ }
9141
+ }
9142
+ /** Pull the first error block an SSE body reports, for better messages. */
9143
+ function parseSseError(text) {
9144
+ for (const line of text.split("\n")) {
9145
+ if (!line.startsWith("data: ")) continue;
9146
+ const raw = line.slice(6).trim();
9147
+ if (raw.length === 0 || raw === "[DONE]") continue;
9148
+ try {
9149
+ const data = JSON.parse(raw);
9150
+ if (data.error !== void 0) {
9151
+ const code = typeof data.error.code === "string" || typeof data.error.code === "number" ? String(data.error.code) : "";
9152
+ const message = typeof data.error.message === "string" ? data.error.message : "";
9153
+ const joined = `${code.length > 0 ? `${code}: ` : ""}${message}`.trim();
9154
+ if (joined.length > 0) return joined;
9155
+ }
9156
+ } catch {}
9157
+ }
9158
+ }
9159
+ //#endregion
9160
+ //#region lib/types/subscription/manager.js
9161
+ /**
9162
+ * Subscription account manager: login flows, blob storage through the DSH
9163
+ * Credentials service, token refresh with per-account locking, and image
9164
+ * generation on the logged-in account.
9165
+ *
9166
+ * The account store pattern follows @goodandready/dsh-subscriptions (MIT,
9167
+ * (c) 2026 GooDAnDReaDY) lib/accounts.js, trimmed to one account per vendor
9168
+ * (no rotation, no cooldowns) because this bundle has exactly two vendors
9169
+ * and no chat adapter to protect.
9170
+ *
9171
+ * Isolation invariants (project rules):
9172
+ * - OAuth blobs live under SUBSCRIPTION_OAUTH_* refs, never the API-key refs.
9173
+ * - Login/logout never touches any other setting or credential.
9174
+ * - The browser side never sees a token; it sees the authorize URL and
9175
+ * status words only. Generation happens host-side, mirroring every other
9176
+ * adapter in this bundle.
9177
+ */
9178
+ /** The vendor each dsh-image-gen subscription provider maps onto. */
9179
+ function vendorOf(provider) {
9180
+ if (provider === "chatgpt-sub") return "codex";
9181
+ if (provider === "google-sub") return "antigravity";
9182
+ return "grok";
9183
+ }
9184
+ /** Credential ref one vendor's OAuth blob is stored under (index fixed at 1). */
9185
+ function subscriptionOauthRef(vendor) {
9186
+ return credentialRef(`${vendor.toUpperCase()}_OAUTH_1`);
9187
+ }
9188
+ /**
9189
+ * Per-application manager instance. Constructed once in the plugin entry,
9190
+ * owns the pending-login map (PKCE state to login session) and the refresh
9191
+ * locks; nothing here is persisted except the blobs themselves.
9192
+ */
9193
+ /** Provider names by vendor for login-result pages and errors. */
9194
+ function providerNameOf(vendor) {
9195
+ return SUBSCRIPTION_PROVIDER_DISPLAY_NAMES[vendor === "codex" ? "chatgpt-sub" : vendor === "antigravity" ? "google-sub" : "grok-sub"];
9196
+ }
9197
+ var SubscriptionManager = class {
9198
+ ctx;
9199
+ pending = /* @__PURE__ */ new Map();
9200
+ refreshLocks = /* @__PURE__ */ new Map();
9201
+ /** Cheap in-memory expiry cache so badges do not hit credentials on read. */
9202
+ blobCache = /* @__PURE__ */ new Map();
9203
+ /** Antigravity needs a per-account project id on every generation call. */
9204
+ projectCache = /* @__PURE__ */ new Map();
9205
+ constructor(ctx) {
9206
+ this.ctx = ctx;
9207
+ }
9208
+ /** Read a vendor's stored blob, or undefined when signed out. */
9209
+ async readBlob(vendor) {
9210
+ try {
9211
+ const raw = (await this.ctx.credentials.resolve(subscriptionOauthRef(vendor)))?.value ?? "";
9212
+ if (raw.trim().length === 0) return void 0;
9213
+ return parseBlob(raw);
9214
+ } catch {
9215
+ return;
9216
+ }
9217
+ }
9218
+ /** Login status for the settings card; never throws. */
9219
+ async loginStatus(vendor) {
9220
+ const blob = await this.readBlob(vendor);
9221
+ if (blob === void 0) return { state: "logged-out" };
9222
+ return {
9223
+ state: "logged-in",
9224
+ email: blob.email
9225
+ };
9226
+ }
9227
+ /**
9228
+ * Begin a login: registers PKCE state, starts the loopback catch server on
9229
+ * the vendor-fixed redirect port, and returns the authorize URL for the
9230
+ * browser to open. Completion lands in the loopback callback.
9231
+ */
9232
+ async beginLogin(vendor) {
9233
+ const redirectUri = vendor === "codex" ? CODEX_REDIRECT_URI : vendor === "antigravity" ? ANTIGRAVITY_REDIRECT_URI : GROK_REDIRECT_URI;
9234
+ const pkce = await createPkce();
9235
+ this.pending.set(pkce.state, {
9236
+ vendor,
9237
+ pkce
9238
+ });
9239
+ const url = vendor === "codex" ? codexAuthorizeUrl(codexConfig(), pkce) : vendor === "antigravity" ? antigravityAuthorizeUrl(antigravityConfig(), pkce) : grokAuthorizeUrl(grokConfig(), pkce);
9240
+ startLoopback({
9241
+ redirectUri,
9242
+ onCode: async (params) => await this.completeLoginFromCallback(vendor, params.get("code") ?? "", params.get("state") ?? "")
9243
+ }).catch(() => {});
9244
+ return { url };
9245
+ }
9246
+ /** Loopback callback: validate state, exchange the code, store the blob. */
9247
+ async completeLoginFromCallback(vendor, code, state) {
9248
+ if (code.length === 0) throw new Error("callback carried no authorization code");
9249
+ const row = this.pending.get(state);
9250
+ if (row === void 0 || row.vendor !== vendor) throw new Error("login session expired; start again");
9251
+ this.pending.delete(state);
9252
+ const blob = vendor === "codex" ? await codexExchangeCode(codexConfig(), row.pkce, code) : vendor === "antigravity" ? await antigravityExchangeCode(antigravityConfig(), row.pkce, code) : await grokExchangeCode(grokConfig(), row.pkce, code);
9253
+ await this.saveBlob(vendor, blob);
9254
+ return `<!doctype html><meta charset="utf-8"><title>dsh-image-gen</title><p>${providerNameOf(vendor)} 登录成功(${escapeHtml(blob.email)}),可以关闭此页返回设置。</p>`;
9255
+ }
9256
+ /** Sign out: clear the blob and the caches. No other setting changes. */
9257
+ async logout(vendor) {
9258
+ await this.ctx.credentials.unset(subscriptionOauthRef(vendor));
9259
+ this.blobCache.delete(vendor);
9260
+ this.projectCache.clear();
9261
+ }
9262
+ async saveBlob(vendor, blob) {
9263
+ await this.ctx.credentials.set(subscriptionOauthRef(vendor), serializeBlob(blob));
9264
+ this.blobCache.set(vendor, blob);
9265
+ }
9266
+ /**
9267
+ * Return a blob whose access token is usable; refreshes first when the
9268
+ * stored one is expired (or expiring within the skew window). Single-flight
9269
+ * per vendor so concurrent generate calls share one refresh.
9270
+ */
9271
+ async ensureFresh(vendor) {
9272
+ const blob = await this.readBlob(vendor);
9273
+ if (blob === void 0) throw notLoggedInError(vendor);
9274
+ if (blob.refreshToken.length === 0) return blob;
9275
+ if (blob.expiresAt !== 0 && blob.expiresAt - 6e4 > Date.now()) return blob;
9276
+ const inflight = this.refreshLocks.get(vendor);
9277
+ if (inflight !== void 0) return inflight;
9278
+ const refresh = (async () => {
9279
+ const next = vendor === "codex" ? await codexRefresh(blob) : vendor === "antigravity" ? await antigravityRefresh(blob) : await grokRefresh(blob);
9280
+ const merged = {
9281
+ ...next,
9282
+ refreshToken: next.refreshToken.length > 0 ? next.refreshToken : blob.refreshToken,
9283
+ accountId: next.accountId.length > 0 ? next.accountId : blob.accountId
9284
+ };
9285
+ await this.saveBlob(vendor, merged);
9286
+ return merged;
9287
+ })().finally(() => {
9288
+ this.refreshLocks.delete(vendor);
9289
+ });
9290
+ this.refreshLocks.set(vendor, refresh);
9291
+ return refresh;
9292
+ }
9293
+ /** Generate one image through the logged-in account. b64 reply decoded host-side. */
9294
+ async generate(options) {
9295
+ const { vendor, prompt } = options;
9296
+ const session = await this.ensureFresh(vendor);
9297
+ const text = prompt.trim();
9298
+ if (text.length === 0) throw new Error("prompt must be a non-empty string");
9299
+ if (vendor === "antigravity") {
9300
+ const projectId = await this.ensureAntigravityProject(session);
9301
+ const aspectRatio = antigravityAspectRatioOf(options.size);
9302
+ return [{ b64_json: (await antigravityGenerateImage({
9303
+ blob: session,
9304
+ projectId,
9305
+ prompt: text,
9306
+ ...aspectRatio !== void 0 ? { aspectRatio } : {},
9307
+ hd: options.quality === "hd" || options.quality === "high",
9308
+ ...options.signal !== void 0 ? { signal: options.signal } : {}
9309
+ })).b64 }];
9310
+ }
9311
+ let url;
9312
+ let headers;
9313
+ let body;
9314
+ if (vendor === "codex") {
9315
+ url = CODEX_IMAGE_URL;
9316
+ headers = codexIdentityHeaders(session);
9317
+ body = {
9318
+ prompt: text,
9319
+ model: CODEX_IMAGE_MODEL,
9320
+ ...options.size !== void 0 && options.size.length > 0 ? { size: options.size } : {},
9321
+ ...options.quality !== void 0 && options.quality.length > 0 ? { quality: options.quality } : {}
9322
+ };
9323
+ } else {
9324
+ url = GROK_IMAGE_URL;
9325
+ headers = grokIdentityHeaders(session);
9326
+ const aspect = {
9327
+ "1024x1024": "1:1",
9328
+ "1024x1536": "2:3",
9329
+ "1536x1024": "3:2",
9330
+ auto: "auto"
9331
+ };
9332
+ const level = options.quality === "low" ? "low" : options.quality === "medium" || options.quality === "high" ? "medium" : void 0;
9333
+ body = {
9334
+ prompt: text,
9335
+ model: GROK_IMAGE_MODEL,
9336
+ response_format: "b64_json",
9337
+ ...options.size !== void 0 && aspect[options.size] !== void 0 ? { aspect_ratio: aspect[options.size] } : {},
9338
+ ...level !== void 0 ? { quality: level } : {}
9339
+ };
9340
+ }
9341
+ const response = await fetch(url, {
9342
+ method: "POST",
9343
+ headers,
9344
+ body: JSON.stringify(body),
9345
+ ...options.signal !== void 0 ? { signal: options.signal } : {}
9346
+ });
9347
+ const payload = await response.json().catch(() => ({}));
9348
+ if (!response.ok) {
9349
+ const detail = extractErrorMessage(payload);
9350
+ throw new Error(`${vendor} HTTP ${String(response.status)}${detail !== void 0 ? `: ${detail.slice(0, 200)}` : ""}`);
9351
+ }
9352
+ return parseImages(payload);
9353
+ }
9354
+ /**
9355
+ * Resolve (and cache per refresh token) the managed project id the
9356
+ * Antigravity generation envelope needs. Re-resolved when the account
9357
+ * signs out or a different account logs in.
9358
+ */
9359
+ async ensureAntigravityProject(blob) {
9360
+ const cacheKey = blob.refreshToken.length > 0 ? blob.refreshToken : blob.accessToken.slice(0, 32);
9361
+ const cached = this.projectCache.get(cacheKey);
9362
+ if (cached !== void 0) return cached;
9363
+ const projectId = await antigravityResolveProject(blob);
9364
+ this.projectCache.set(cacheKey, projectId);
9365
+ return projectId;
9366
+ }
9367
+ };
9368
+ /** Map an OpenAI-style size onto the aspect ratio Antigravity accepts. */
9369
+ function antigravityAspectRatioOf(size) {
9370
+ const table = {
9371
+ "1024x1024": "1:1",
9372
+ "1024x1536": "2:3",
9373
+ "1536x1024": "3:2",
9374
+ "768x1398": "9:16",
9375
+ "1398x768": "16:9",
9376
+ auto: "1:1"
9377
+ };
9378
+ if (size === void 0) return void 0;
9379
+ const mapped = table[size];
9380
+ if (mapped !== void 0) return mapped;
9381
+ return /^\d+:\d+$/.test(size) ? size : "1:1";
9382
+ }
9383
+ /** Response parsing: both vendors reply in the same `{data:[{b64_json}]}` shape. */
9384
+ function parseImages(payload) {
9385
+ const body = typeof payload === "object" && payload !== null ? payload : {};
9386
+ const rows = Array.isArray(body.data) ? body.data : [];
9387
+ const images = [];
9388
+ for (const row of rows) {
9389
+ if (typeof row !== "object" || row === null) continue;
9390
+ const b64 = row.b64_json;
9391
+ if (typeof b64 !== "string" || b64.length === 0) continue;
9392
+ const revised = row.revised_prompt;
9393
+ images.push({
9394
+ b64_json: b64,
9395
+ ...typeof revised === "string" && revised.length > 0 ? { revisedPrompt: revised } : {}
9396
+ });
9397
+ }
9398
+ if (images.length === 0) throw new Error("response contains no images");
9399
+ return images;
9400
+ }
9401
+ function extractErrorMessage(payload) {
9402
+ if (typeof payload !== "object" || payload === null) return void 0;
9403
+ const error = payload.error;
9404
+ if (typeof error !== "object" || error === null) return void 0;
9405
+ const row = error;
9406
+ if (typeof row.message === "string") return row.message;
9407
+ if (typeof row.code === "string") return row.code;
9408
+ }
9409
+ function notLoggedInError(vendor) {
9410
+ const display = providerNameOf(vendor);
9411
+ return /* @__PURE__ */ new Error(`${display} 未登录:请到 设置 > 插件 > 图像生成 的订阅区完成登录`);
9412
+ }
9413
+ function escapeHtml(value) {
9414
+ return value.replace(/[&<>"']/g, (ch) => ({
9415
+ "&": "&amp;",
9416
+ "<": "&lt;",
9417
+ ">": "&gt;",
9418
+ "\"": "&quot;",
9419
+ "'": "&#39;"
9420
+ })[ch] ?? ch);
9421
+ }
9422
+ //#endregion
9423
+ //#region lib/types/subscription/subscription-route.js
9424
+ const MAX_BODY_BYTES$1 = 16384;
9425
+ /** The vendor each subscription provider logs in as. */
9426
+ function vendorOfProvider(provider) {
9427
+ if (provider === "chatgpt-sub") return "codex";
9428
+ if (provider === "google-sub") return "antigravity";
9429
+ return "grok";
9430
+ }
9431
+ /** Register the login/status routes on the plugin's web server. */
9432
+ function registerSubscriptionRoutes(ctx, manager) {
9433
+ ctx.effect(() => ctx.webServer.register({
9434
+ kind: "exact",
9435
+ path: SUBSCRIPTION_LOGIN_ROUTE,
9436
+ handler: (req, res) => {
9437
+ serveLogin(req, res, manager);
9438
+ }
9439
+ }), "dsh-image-gen: subscription login route");
9440
+ ctx.effect(() => ctx.webServer.register({
9441
+ kind: "exact",
9442
+ path: SUBSCRIPTION_STATUS_ROUTE,
9443
+ handler: (req, res) => {
9444
+ serveStatus(req, res, manager);
9445
+ }
9446
+ }), "dsh-image-gen: subscription status route");
9447
+ }
9448
+ async function serveLogin(req, res, manager) {
9449
+ if (!sameOrigin$3(req)) return jsonError$3(res, 403, "origin-rejected");
9450
+ if (req.method !== "POST") return jsonError$3(res, 405, "method-not-allowed");
9451
+ if (!(req.headers["content-type"] ?? "").toLowerCase().startsWith("application/json")) return jsonError$3(res, 415, "json-required");
9452
+ let body;
9453
+ try {
9454
+ body = JSON.parse(await readBody$2(req));
9455
+ } catch {
9456
+ return jsonError$3(res, 400, "invalid-request");
9457
+ }
9458
+ const provider = record$3(body)?.provider;
9459
+ if (typeof provider !== "string" || !SUBSCRIPTION_PROVIDERS.includes(provider)) return jsonError$3(res, 400, "invalid-provider");
9460
+ const action = record$3(body)?.action;
9461
+ const vendor = vendorOfProvider(provider);
9462
+ try {
9463
+ if (action === "logout") {
9464
+ await manager.logout(vendor);
9465
+ return json$3(res, 200, { ok: true });
9466
+ }
9467
+ const { url } = await manager.beginLogin(vendor);
9468
+ json$3(res, 200, {
9469
+ ok: true,
9470
+ url
9471
+ });
9472
+ } catch (error) {
9473
+ json$3(res, 502, {
9474
+ ok: false,
9475
+ error: error instanceof Error ? error.message : String(error)
9476
+ });
9477
+ }
9478
+ }
9479
+ async function serveStatus(req, res, manager) {
9480
+ if (!sameOrigin$3(req)) return jsonError$3(res, 403, "origin-rejected");
9481
+ if (req.method !== "POST") return jsonError$3(res, 405, "method-not-allowed");
9482
+ const statuses = {};
9483
+ for (const provider of SUBSCRIPTION_PROVIDERS) {
9484
+ const status = await manager.loginStatus(vendorOfProvider(provider));
9485
+ statuses[provider] = status.state === "logged-in" ? {
9486
+ state: status.state,
9487
+ email: status.email
9488
+ } : { state: status.state };
9489
+ }
9490
+ json$3(res, 200, {
9491
+ ok: true,
9492
+ statuses
9493
+ });
9494
+ }
9495
+ function sameOrigin$3(req) {
9496
+ const origin = req.headers.origin;
9497
+ const host = req.headers.host;
9498
+ return origin === void 0 || host === void 0 || origin === `http://${host}` || origin === `https://${host}`;
9499
+ }
9500
+ function record$3(value) {
9501
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
9502
+ }
9503
+ async function readBody$2(req) {
9504
+ const chunks = [];
9505
+ let bytes = 0;
9506
+ for await (const chunk of req) {
9507
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
9508
+ bytes += buffer.byteLength;
9509
+ if (bytes > MAX_BODY_BYTES$1) throw new Error("request too large");
9510
+ chunks.push(buffer);
9511
+ }
9512
+ return Buffer.concat(chunks).toString("utf8");
9513
+ }
9514
+ function json$3(res, status, value) {
9515
+ if (res.headersSent || res.writableEnded || res.destroyed) return;
9516
+ res.writeHead(status, {
9517
+ "content-type": "application/json; charset=utf-8",
9518
+ "cache-control": "no-store"
9519
+ });
9520
+ res.end(JSON.stringify(value));
9521
+ }
9522
+ function jsonError$3(res, status, code) {
9523
+ json$3(res, status, { error: code });
9524
+ }
9525
+ //#endregion
9526
+ //#region lib/types/subscription.js
9527
+ /** Timeout for one subscription image call, mirroring the other adapters. */
9528
+ const SUBSCRIPTION_TIMEOUT_MS = 3e5;
9529
+ /**
9530
+ * Generate one image through a logged-in subscription account. Returns the
9531
+ * same `{ data, mediaType }` contract as the API-key adapters so the caller
9532
+ * can feed it straight into `saveGenerated`.
9533
+ */
9534
+ async function generateSubscriptionImage(options) {
9535
+ const { manager, provider, prompt, maxBytes, signal } = options;
9536
+ const size = options.size?.trim();
9537
+ const result = await withTimeout(manager.generate({
9538
+ vendor: vendorOf(provider),
9539
+ prompt,
9540
+ ...size !== void 0 && size.length > 0 ? { size } : {},
9541
+ signal
9542
+ }), signal);
9543
+ const first = Array.isArray(result) ? result[0] : void 0;
9544
+ if (first === void 0 || typeof first.b64_json !== "string" || first.b64_json.length === 0) throw new Error("Subscription image reply contained no image payload");
9545
+ const bytes = new Uint8Array(Buffer.from(first.b64_json, "base64"));
9546
+ if (bytes.byteLength > maxBytes) throw new Error(`Subscription image is ${formatBytes(bytes.byteLength)}, above this DSH's ${formatBytes(maxBytes)} limit`);
9547
+ const mediaType = detectImageMediaType(bytes);
9548
+ if (mediaType === void 0) throw new Error("Subscription image payload has an unrecognized format");
9549
+ return {
9550
+ data: bytes,
9551
+ mediaType,
9552
+ ...typeof first.revisedPrompt === "string" && first.revisedPrompt.length > 0 ? { revisedPrompt: first.revisedPrompt } : {}
9553
+ };
9554
+ }
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
+ /** Run one promise with the subscription timeout and the call's abort signal. */
9560
+ async function withTimeout(promise, signal) {
9561
+ let timer;
9562
+ let rejectCall;
9563
+ const onAbort = () => {
9564
+ rejectCall(/* @__PURE__ */ new Error("Subscription image generation aborted"));
9565
+ };
9566
+ const guarded = new Promise((resolve, reject) => {
9567
+ rejectCall = reject;
9568
+ timer = setTimeout(() => {
9569
+ reject(/* @__PURE__ */ new Error(`Subscription image generation timed out after ${String(Math.round(SUBSCRIPTION_TIMEOUT_MS / 1e3))}s`));
9570
+ }, SUBSCRIPTION_TIMEOUT_MS);
9571
+ promise.then(resolve, reject);
9572
+ });
9573
+ signal.addEventListener("abort", onAbort, { once: true });
9574
+ try {
9575
+ return await guarded;
9576
+ } finally {
9577
+ if (timer !== void 0) clearTimeout(timer);
9578
+ signal.removeEventListener("abort", onAbort);
9579
+ }
9580
+ }
9581
+ function formatBytes(bytes) {
9582
+ if (bytes >= 1048576) return `${String(Math.round(bytes / 1048576 * 10) / 10)}MB`;
9583
+ return `${String(Math.round(bytes / 1024))}KB`;
9584
+ }
9585
+ //#endregion
8316
9586
  //#region lib/types/inspiration/data/awesome-gpt-image-2.json
8317
9587
  var awesome_gpt_image_2_default = {
8318
9588
  repository: "https://github.com/freestylefly/awesome-gpt-image-2",
@@ -19214,6 +20484,8 @@ async function generateFromStudio(ctx, config, input, signal, fallbackWorkspaceR
19214
20484
  assertAllowed(studioProfile(config, input.provider, true), input);
19215
20485
  const active = resolveProvider(withProviderOverrides(config, input.provider, input.model));
19216
20486
  if (active.provider === "comfyui") throw new Error("ComfyUI 暂未接入工作台");
20487
+ if (isSubscriptionProvider(active.provider)) throw new Error("订阅生图暂未接入工作台,请通过对话或设置切换默认 Provider 使用");
20488
+ const wired = active;
19217
20489
  const credential = await requireApiKey(ctx, input.provider);
19218
20490
  const rawRefs = input.references ?? (input.reference ? [input.reference] : []);
19219
20491
  if (input.mode === "edit" && rawRefs.length === 0) throw new Error("图生图需要至少一张参考图");
@@ -19223,13 +20495,13 @@ async function generateFromStudio(ctx, config, input, signal, fallbackWorkspaceR
19223
20495
  const generateSingle = async (index) => {
19224
20496
  let generated;
19225
20497
  let output;
19226
- if (active.provider === "google") {
20498
+ if (wired.provider === "google") {
19227
20499
  const aspectRatio = input.ratio;
19228
20500
  const imageSize = input.quality;
19229
20501
  generated = input.mode === "edit" ? await editGoogleImage({
19230
20502
  apiKey: credential,
19231
- endpoint: active.endpoint,
19232
- model: active.model,
20503
+ endpoint: wired.endpoint,
20504
+ model: wired.model,
19233
20505
  prompt: input.prompt,
19234
20506
  sourceImages,
19235
20507
  aspectRatio,
@@ -19238,8 +20510,8 @@ async function generateFromStudio(ctx, config, input, signal, fallbackWorkspaceR
19238
20510
  signal
19239
20511
  }) : await generateGoogleImage({
19240
20512
  apiKey: credential,
19241
- endpoint: active.endpoint,
19242
- model: active.model,
20513
+ endpoint: wired.endpoint,
20514
+ model: wired.model,
19243
20515
  prompt: input.prompt,
19244
20516
  aspectRatio,
19245
20517
  imageSize,
@@ -19247,34 +20519,38 @@ async function generateFromStudio(ctx, config, input, signal, fallbackWorkspaceR
19247
20519
  signal
19248
20520
  });
19249
20521
  output = `${aspectRatio}, ${imageSize}`;
19250
- } else if (active.provider === "openai" || active.provider === "openai-compat" || active.provider === "xai" || active.provider === "zhipu") {
20522
+ } else if (wired.provider === "openai" || wired.provider === "openai-compat" || wired.provider === "xai" || wired.provider === "zhipu") {
19251
20523
  const size = openAISize(input.ratio);
19252
20524
  generated = input.mode === "edit" ? await editOpenAICompatibleImage({
19253
20525
  apiKey: credential,
19254
- baseURL: active.baseURL,
19255
- model: active.model,
20526
+ baseURL: wired.baseURL,
20527
+ model: wired.model,
19256
20528
  prompt: input.prompt,
19257
20529
  sourceImages,
19258
20530
  size,
19259
20531
  maxBytes: ctx.attachments.imageLimits.maxImageBytes,
19260
- signal
20532
+ signal,
20533
+ ...wired.provider === "openai-compat" ? {
20534
+ editFormat: wired.editFormat,
20535
+ editExtra: wired.editExtra
20536
+ } : {}
19261
20537
  }) : await generateOpenAICompatibleImage({
19262
- provider: active.provider,
20538
+ provider: wired.provider,
19263
20539
  apiKey: credential,
19264
- baseURL: active.baseURL,
19265
- model: active.model,
20540
+ baseURL: wired.baseURL,
20541
+ model: wired.model,
19266
20542
  prompt: input.prompt,
19267
20543
  size,
19268
20544
  maxBytes: ctx.attachments.imageLimits.maxImageBytes,
19269
20545
  signal
19270
20546
  });
19271
20547
  output = size;
19272
- } else if (active.provider === "seedream") {
20548
+ } else if (wired.provider === "seedream") {
19273
20549
  const size = input.quality;
19274
20550
  generated = input.mode === "edit" ? await editSeedreamImage({
19275
20551
  apiKey: credential,
19276
- baseURL: active.baseURL,
19277
- model: active.model,
20552
+ baseURL: wired.baseURL,
20553
+ model: wired.model,
19278
20554
  prompt: input.prompt,
19279
20555
  sourceImages,
19280
20556
  size,
@@ -19283,8 +20559,8 @@ async function generateFromStudio(ctx, config, input, signal, fallbackWorkspaceR
19283
20559
  }) : await generateOpenAICompatibleImage({
19284
20560
  provider: "seedream",
19285
20561
  apiKey: credential,
19286
- baseURL: active.baseURL,
19287
- model: active.model,
20562
+ baseURL: wired.baseURL,
20563
+ model: wired.model,
19288
20564
  prompt: input.prompt,
19289
20565
  size,
19290
20566
  maxBytes: ctx.attachments.imageLimits.maxImageBytes,
@@ -19296,8 +20572,8 @@ async function generateFromStudio(ctx, config, input, signal, fallbackWorkspaceR
19296
20572
  const size = dashScopeSize(input.ratio);
19297
20573
  generated = input.mode === "edit" ? await editDashScopeImage({
19298
20574
  apiKey: credential,
19299
- endpoint: active.endpoint,
19300
- model: active.model,
20575
+ endpoint: wired.endpoint,
20576
+ model: wired.model,
19301
20577
  prompt: input.prompt,
19302
20578
  sourceImages,
19303
20579
  size,
@@ -19305,8 +20581,8 @@ async function generateFromStudio(ctx, config, input, signal, fallbackWorkspaceR
19305
20581
  signal
19306
20582
  }) : await generateDashScopeImage({
19307
20583
  apiKey: credential,
19308
- endpoint: active.endpoint,
19309
- model: active.model,
20584
+ endpoint: wired.endpoint,
20585
+ model: wired.model,
19310
20586
  prompt: input.prompt,
19311
20587
  size,
19312
20588
  maxBytes: ctx.attachments.imageLimits.maxImageBytes,
@@ -19412,7 +20688,7 @@ function studioProfile(config, provider, configured) {
19412
20688
  }
19413
20689
  if (active.provider === "comfyui") throw new Error("Invalid cloud provider profile");
19414
20690
  const model = active.model;
19415
- if (provider === "google") return profile(provider, model, configured, ASPECT_RATIOS.map(option), IMAGE_SIZES.map((value) => ({
20691
+ if (provider === "google") return profile(provider, model, configured, ASPECT_RATIOS$1.map(option), IMAGE_SIZES.map((value) => ({
19416
20692
  value,
19417
20693
  label: value
19418
20694
  })), "1:1", "1K");
@@ -19667,6 +20943,7 @@ function probeTarget(provider, config, apiKey) {
19667
20943
  url: joinUrl(config.comfyuiBaseURL ?? "http://127.0.0.1:8188", "system_stats"),
19668
20944
  headers: {}
19669
20945
  };
20946
+ if (isSubscriptionProvider(provider)) throw new Error("Subscription providers are probed through the manager, not probeTarget");
19670
20947
  if (provider === "google") return {
19671
20948
  url: googleModelsUrl(config),
19672
20949
  headers: { "x-goog-api-key": apiKey ?? "" }
@@ -19905,6 +21182,27 @@ async function probeComfyUIConnection(config, signal) {
19905
21182
  message: `HTTP ${String(response.status)}`
19906
21183
  };
19907
21184
  }
21185
+ /**
21186
+ * Probe a subscription provider: the internal manager's login state for the
21187
+ * matching vendor. No network call and no token access; the probe reports
21188
+ * signed-in (with the account email) or signed-out guidance.
21189
+ */
21190
+ async function probeSubscriptionConnection(provider, manager) {
21191
+ const vendor = provider === "chatgpt-sub" ? "codex" : provider === "google-sub" ? "antigravity" : "grok";
21192
+ const display = provider === "chatgpt-sub" ? "ChatGPT" : provider === "google-sub" ? "Google" : "Grok";
21193
+ const status = await manager.loginStatus(vendor);
21194
+ if (status.state === "logged-in") return { ok: true };
21195
+ if (status.state === "logged-out") return {
21196
+ ok: false,
21197
+ reason: "missing-key",
21198
+ message: `${display} 账号未登录:请在设置卡片中点击“登录”完成授权`
21199
+ };
21200
+ return {
21201
+ ok: false,
21202
+ reason: "error",
21203
+ message: "登录状态读取失败"
21204
+ };
21205
+ }
19908
21206
  /** Serve the settings card's per-provider connectivity probe. */
19909
21207
  async function serveTestConnection(req, res, deps) {
19910
21208
  if (!sameOrigin(req)) return jsonError(res, 403, "origin-rejected");
@@ -19918,12 +21216,13 @@ async function serveTestConnection(req, res, deps) {
19918
21216
  }
19919
21217
  const provider = record(body)?.provider;
19920
21218
  if (typeof provider !== "string" || !IMAGE_PROVIDERS.includes(provider)) return jsonError(res, 400, "invalid-provider");
21219
+ const active = provider;
19921
21220
  if (record(body)?.action === "models") {
19922
- if (provider === "comfyui") return jsonError(res, 400, "models-unsupported");
21221
+ if (active === "comfyui" || isSubscriptionProvider(active)) return jsonError(res, 400, "models-unsupported");
19923
21222
  let result;
19924
21223
  try {
19925
- const apiKey = await deps.resolveKey(provider);
19926
- result = provider === "google" ? await fetchGoogleImageModels(deps.config(), apiKey) : provider === "dashscope" ? await fetchDashScopeImageModels(deps.config(), apiKey) : await fetchOpenAIImageModels(provider, deps.config(), apiKey);
21224
+ const apiKey = await deps.resolveKey(active);
21225
+ result = active === "google" ? await fetchGoogleImageModels(deps.config(), apiKey) : active === "dashscope" ? await fetchDashScopeImageModels(deps.config(), apiKey) : await fetchOpenAIImageModels(active, deps.config(), apiKey);
19927
21226
  } catch (error) {
19928
21227
  result = {
19929
21228
  ok: false,
@@ -19935,10 +21234,11 @@ async function serveTestConnection(req, res, deps) {
19935
21234
  }
19936
21235
  let result;
19937
21236
  try {
19938
- if (provider === "comfyui") result = await probeComfyUIConnection(deps.config());
21237
+ if (active === "comfyui") result = await probeComfyUIConnection(deps.config());
21238
+ else if (isSubscriptionProvider(active)) result = await probeSubscriptionConnection(active, deps.subscriptionManager);
19939
21239
  else {
19940
- const apiKey = await deps.resolveKey(provider);
19941
- result = await probeProviderConnection(provider, deps.config(), apiKey);
21240
+ const apiKey = await deps.resolveKey(active);
21241
+ result = await probeProviderConnection(active, deps.config(), apiKey);
19942
21242
  }
19943
21243
  } catch (error) {
19944
21244
  result = {
@@ -19999,6 +21299,8 @@ function apply(ctx, config = {}) {
19999
21299
  let current = () => migrateOpenAICompatConfig(config);
20000
21300
  const knownWorkspaceRoots = /* @__PURE__ */ new Set();
20001
21301
  const canvasMirror = new CanvasMirror();
21302
+ const subscriptionManager = new SubscriptionManager(ctx);
21303
+ registerSubscriptionRoutes(ctx, subscriptionManager);
20002
21304
  installImageSettings(ctx, config, {
20003
21305
  setSource: (source) => {
20004
21306
  current = () => migrateOpenAICompatConfig(source());
@@ -20054,7 +21356,8 @@ function apply(ctx, config = {}) {
20054
21356
  path: TEST_CONNECTION_ROUTE,
20055
21357
  handler: (req, res) => serveTestConnection(req, res, {
20056
21358
  resolveKey: (provider) => resolveApiKey(ctx, provider),
20057
- config: () => current()
21359
+ config: () => current(),
21360
+ subscriptionManager
20058
21361
  })
20059
21362
  }), "dsh-image-gen: test connection route");
20060
21363
  ctx.effect(() => ctx.webServer.register({
@@ -20128,13 +21431,16 @@ function apply(ctx, config = {}) {
20128
21431
  "dashscope",
20129
21432
  "xai",
20130
21433
  "zhipu",
20131
- "comfyui"
21434
+ "comfyui",
21435
+ "chatgpt-sub",
21436
+ "grok-sub",
21437
+ "google-sub"
20132
21438
  ],
20133
- description: "Optional provider for this call only (for example when the user asks to use a specific provider); omit to use the configured default."
21439
+ 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 generate through the logged-in subscription account instead of an API key."
20134
21440
  },
20135
21441
  model: {
20136
21442
  type: "string",
20137
- description: "Optional model name for this call only, overriding the configured model. Not used by ComfyUI (use workflow instead)."
21443
+ description: "Optional model name for this call only, overriding the configured model. Not used by ComfyUI (use workflow instead) nor by the subscription providers (model fixed by the subscription)."
20138
21444
  },
20139
21445
  aspect_ratio: {
20140
21446
  type: "string",
@@ -20181,6 +21487,14 @@ function apply(ctx, config = {}) {
20181
21487
  signal: exec.signal
20182
21488
  }), active.provider, workflow.name, "API workflow", current(), exec, knownWorkspaceRoots);
20183
21489
  }
21490
+ if (active.provider === "chatgpt-sub" || active.provider === "grok-sub" || active.provider === "google-sub") return saveGenerated(ctx, await generateSubscriptionImage({
21491
+ manager: subscriptionManager,
21492
+ provider: active.provider,
21493
+ prompt: args.prompt,
21494
+ ...args.size !== void 0 ? { size: args.size } : {},
21495
+ maxBytes: ctx.attachments.imageLimits.maxImageBytes,
21496
+ signal: exec.signal
21497
+ }), active.provider, active.model, "subscription", current(), exec, knownWorkspaceRoots);
20184
21498
  const credential = await requireApiKey(ctx, active.provider, "generate_image");
20185
21499
  if (active.provider === "google") {
20186
21500
  const aspectRatio = args.aspect_ratio ?? active.aspectRatio;
@@ -20241,13 +21555,16 @@ function apply(ctx, config = {}) {
20241
21555
  "dashscope",
20242
21556
  "xai",
20243
21557
  "zhipu",
20244
- "comfyui"
21558
+ "comfyui",
21559
+ "chatgpt-sub",
21560
+ "grok-sub",
21561
+ "google-sub"
20245
21562
  ],
20246
- description: "Optional provider for this call only (for example when the user asks to use a specific provider); omit to use the configured default."
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."
20247
21564
  },
20248
21565
  model: {
20249
21566
  type: "string",
20250
- description: "Optional model name for this call only, overriding the configured model. Not used by ComfyUI (use workflow instead)."
21567
+ description: "Optional model name for this call only, overriding the configured model. Not used by ComfyUI (use workflow instead) nor by the subscription providers (model fixed by the subscription)."
20251
21568
  },
20252
21569
  source: {
20253
21570
  type: "string",
@@ -20306,6 +21623,7 @@ function apply(ctx, config = {}) {
20306
21623
  output: imageOutput("Edited"),
20307
21624
  async execute(args, exec) {
20308
21625
  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);
20309
21627
  const canvasSelection = args.source === "canvas_selection";
20310
21628
  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");
20311
21629
  const sourceImages = canvasSelection ? await resolveCanvasSelectionReferences({
@@ -20367,7 +21685,11 @@ function apply(ctx, config = {}) {
20367
21685
  sourceImages,
20368
21686
  size,
20369
21687
  maxBytes: ctx.attachments.imageLimits.maxImageBytes,
20370
- signal: exec.signal
21688
+ signal: exec.signal,
21689
+ ...active.provider === "openai-compat" ? {
21690
+ editFormat: active.editFormat,
21691
+ editExtra: active.editExtra
21692
+ } : {}
20371
21693
  }), active.provider, active.model, size, current(), exec, knownWorkspaceRoots);
20372
21694
  if (active.provider === "seedream") return saveGenerated(ctx, await editSeedreamImage({
20373
21695
  apiKey: credential,