dsh-image-gen 0.9.0 → 0.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js CHANGED
@@ -5955,6 +5955,7 @@ const Config = z.object({
5955
5955
  provider: volatile(z.union(IMAGE_PROVIDERS).default("google")),
5956
5956
  googleModel: volatile(z.string().default(DEFAULT_GOOGLE_MODEL)),
5957
5957
  googleEndpoint: volatile(z.string().default(DEFAULT_GOOGLE_ENDPOINT)),
5958
+ googleNative: volatile(z.boolean().default(false)),
5958
5959
  openaiBaseURL: volatile(z.string().default(DEFAULT_OPENAI_BASE_URL)),
5959
5960
  openaiModel: volatile(z.string().default(DEFAULT_OPENAI_MODEL)),
5960
5961
  openaiSizes: volatile(z.dict(z.dict(z.string())).default({})),
@@ -6090,6 +6091,7 @@ function resolveProvider(config) {
6090
6091
  apiKeyEnv: GOOGLE_API_KEY_ENV,
6091
6092
  model: config.googleModel ?? "gemini-3.1-flash-image",
6092
6093
  endpoint: config.googleEndpoint ?? "https://generativelanguage.googleapis.com/v1beta/interactions",
6094
+ native: config.googleNative === true,
6093
6095
  aspectRatio: "1:1",
6094
6096
  imageSize: "1K"
6095
6097
  };
@@ -7382,6 +7384,81 @@ async function readBoundedBytes$2(response, maxBytes) {
7382
7384
  return joined;
7383
7385
  }
7384
7386
  //#endregion
7387
+ //#region lib/types/fetch-diagnostics.js
7388
+ /**
7389
+ * Transport-failure diagnostics for provider fetches (#79).
7390
+ *
7391
+ * Node's undici reports the real reason behind a failed fetch (socket reset,
7392
+ * DNS, TLS, ...) only on `error.cause`, and the host's tool-error rendering
7393
+ * keeps just `.message` — so the conversation sees nothing but undici's bare
7394
+ * "fetch failed". These helpers rethrow transport failures with the failing
7395
+ * phase, elapsed seconds, and a bounded cause chain.
7396
+ */
7397
+ /** Cap a single transport-failure description so diagnostics cannot flood the conversation. */
7398
+ const TRANSPORT_DETAIL_LIMIT = 600;
7399
+ /** How deep to walk `error.cause` chains; undici nests at most twice in practice. */
7400
+ const CAUSE_CHAIN_DEPTH = 3;
7401
+ /** Keep body reads tied to the same request timing, cancellation, and secrets. */
7402
+ const responseContexts = /* @__PURE__ */ new WeakMap();
7403
+ /**
7404
+ * Render one error plus its `cause` chain as a single line, e.g.
7405
+ * `TypeError: fetch failed <- Error [UND_ERR_SOCKET]: other side closed`.
7406
+ */
7407
+ function describeErrorChain(error, depth = 0) {
7408
+ if (depth >= CAUSE_CHAIN_DEPTH) return "...";
7409
+ if (error === void 0 || error === null) return "unknown transport error";
7410
+ if (typeof error === "object") {
7411
+ const details = error;
7412
+ if (error instanceof Error || typeof details.message === "string") {
7413
+ const label = [typeof details.name === "string" ? details.name : "Error", typeof details.code === "string" && details.code.length > 0 ? `[${details.code}]` : ""].filter((part) => part.length > 0).join(" ");
7414
+ const message = typeof details.message === "string" ? details.message : "";
7415
+ const text = label.length > 0 && message.length > 0 ? `${label}: ${message}` : label || message;
7416
+ return details.cause !== void 0 && details.cause !== null && depth + 1 < CAUSE_CHAIN_DEPTH ? `${text} <- ${describeErrorChain(details.cause, depth + 1)}` : text || "unknown transport error";
7417
+ }
7418
+ if (typeof details.kind === "string") return details.reason === void 0 || details.reason === null ? details.kind : `${details.kind}: ${describeErrorChain(details.reason, depth + 1)}`;
7419
+ }
7420
+ return String(error).trim() || "unknown transport error";
7421
+ }
7422
+ /** One line describing why a provider fetch failed: the abort reason when the caller cancelled, the cause chain otherwise. */
7423
+ function describeTransportFailure(error, signal) {
7424
+ try {
7425
+ if (signal?.aborted === true) return `aborted (${describeErrorChain(signal.reason ?? error)})`;
7426
+ return describeErrorChain(error);
7427
+ } catch {
7428
+ return "unknown transport error";
7429
+ }
7430
+ }
7431
+ async function withTransportDiagnostics(context, run) {
7432
+ try {
7433
+ return await run();
7434
+ } catch (error) {
7435
+ const elapsed = ((performance.now() - context.startedAt) / 1e3).toFixed(1);
7436
+ const detail = redactSecrets(describeTransportFailure(error, context.signal), ...context.secrets).replace(/\s+/g, " ").slice(0, TRANSPORT_DETAIL_LIMIT);
7437
+ throw new Error(`${context.label} failed after ${elapsed}s: ${detail}`, { cause: error });
7438
+ }
7439
+ }
7440
+ /**
7441
+ * Await one provider fetch and rethrow transport failures as
7442
+ * `${label} failed after 12.3s: ${cause chain}`. The original error stays
7443
+ * reachable on `cause` for host-side error rendering.
7444
+ */
7445
+ async function guardedFetch(label, signal, run, ...secrets) {
7446
+ const context = {
7447
+ label,
7448
+ signal,
7449
+ secrets,
7450
+ startedAt: performance.now()
7451
+ };
7452
+ const response = await withTransportDiagnostics(context, run);
7453
+ responseContexts.set(response, context);
7454
+ return response;
7455
+ }
7456
+ /** Guard only body I/O; parsing, HTTP checks and size enforcement stay with the adapter. */
7457
+ function readResponseBody(response, read) {
7458
+ const context = responseContexts.get(response);
7459
+ return context === void 0 ? read() : withTransportDiagnostics(context, read);
7460
+ }
7461
+ //#endregion
7385
7462
  //#region lib/types/dashscope.js
7386
7463
  async function generateDashScopeImage(options) {
7387
7464
  assertQwenImageModel(options.model);
@@ -7435,7 +7512,7 @@ function toDataUrl$3(image) {
7435
7512
  }
7436
7513
  async function requestQwenImage(options) {
7437
7514
  const base = options.endpoint.replace(/\/+$/, "");
7438
- const response = await fetch(`${base}/services/aigc/multimodal-generation/generation`, {
7515
+ const response = await guardedFetch(`DashScope image ${options.operation}`, options.signal, () => fetch(`${base}/services/aigc/multimodal-generation/generation`, {
7439
7516
  method: "POST",
7440
7517
  ...options.signal ? { signal: options.signal } : {},
7441
7518
  headers: {
@@ -7443,12 +7520,12 @@ async function requestQwenImage(options) {
7443
7520
  authorization: `Bearer ${options.apiKey}`
7444
7521
  },
7445
7522
  body: JSON.stringify(options.requestBody)
7446
- });
7523
+ }), options.apiKey);
7447
7524
  if (!response.ok) {
7448
- const errorText = redactSecrets(await response.text(), options.apiKey);
7525
+ const errorText = redactSecrets(await readResponseBody(response, () => response.text()), options.apiKey);
7449
7526
  throw new Error(`DashScope image ${options.operation} failed (${String(response.status)}): ${errorText}`);
7450
7527
  }
7451
- const payload = await response.json();
7528
+ const payload = JSON.parse(await readResponseBody(response, () => response.text()));
7452
7529
  const imageUrl = extractImageUrl(payload);
7453
7530
  if (imageUrl === void 0) throw new Error(`DashScope image ${options.operation} returned no image URL: ${redactSecrets(payload.message ?? JSON.stringify(payload), options.apiKey)}`);
7454
7531
  return downloadImageBlob(imageUrl, options);
@@ -7463,9 +7540,9 @@ function extractImageUrl(response) {
7463
7540
  }
7464
7541
  }
7465
7542
  async function downloadImageBlob(imageUrl, options) {
7466
- const imageResponse = await fetch(imageUrl, { ...options.signal ? { signal: options.signal } : {} });
7543
+ const imageResponse = await guardedFetch("DashScope image download", options.signal, () => fetch(imageUrl, { ...options.signal ? { signal: options.signal } : {} }), options.apiKey);
7467
7544
  if (!imageResponse.ok) throw new Error(`Failed to fetch DashScope image from URL (${String(imageResponse.status)})`);
7468
- const buffer = await imageResponse.arrayBuffer();
7545
+ const buffer = await readResponseBody(imageResponse, () => imageResponse.arrayBuffer());
7469
7546
  if (buffer.byteLength > options.maxBytes) throw new Error(`DashScope generated image (${String(buffer.byteLength)} bytes) exceeds the ${String(options.maxBytes)} byte limit`);
7470
7547
  const data = new Uint8Array(buffer);
7471
7548
  const mediaType = detectImageMediaType(data) ?? imageMediaType$3(imageResponse.headers.get("content-type"));
@@ -7488,12 +7565,13 @@ function generateGoogleImage(input) {
7488
7565
  return requestGoogleImage({
7489
7566
  ...input,
7490
7567
  operation: "generation",
7491
- interactionInput: input.prompt
7568
+ interactionInput: input.prompt,
7569
+ nativeParts: [{ text: input.prompt }]
7492
7570
  });
7493
7571
  }
7494
7572
  /** Send one native Google image-editing request using already-resolved bytes. */
7495
7573
  function editGoogleImage(input) {
7496
- const interactionInput = input.sourceImages.length === 1 ? [{
7574
+ const interactionInput = input.native ? [] : input.sourceImages.length === 1 ? [{
7497
7575
  type: "image",
7498
7576
  mime_type: input.sourceImages[0].mediaType,
7499
7577
  data: Buffer.from(input.sourceImages[0].data).toString("base64")
@@ -7514,13 +7592,41 @@ function editGoogleImage(input) {
7514
7592
  return requestGoogleImage({
7515
7593
  ...input,
7516
7594
  operation: "editing",
7517
- interactionInput
7595
+ interactionInput,
7596
+ nativeParts: input.native ? [...input.sourceImages.map((image) => ({ inlineData: {
7597
+ mimeType: image.mediaType,
7598
+ data: Buffer.from(image.data).toString("base64")
7599
+ } })), { text: input.prompt }] : []
7518
7600
  });
7519
7601
  }
7520
7602
  /** Shared Google request, response parsing, decoding, and size enforcement. */
7521
7603
  async function requestGoogleImage(input) {
7522
7604
  const label = `Google image ${input.operation}`;
7523
- const response = await fetch(input.endpoint, {
7605
+ const endpoint = input.native ? nativeGoogleUrl(input.endpoint, input.model).toString() : input.endpoint;
7606
+ const body = input.native ? {
7607
+ contents: [{
7608
+ role: "user",
7609
+ parts: input.nativeParts
7610
+ }],
7611
+ generationConfig: {
7612
+ candidateCount: 1,
7613
+ responseModalities: ["TEXT", "IMAGE"],
7614
+ imageConfig: {
7615
+ aspectRatio: input.aspectRatio,
7616
+ imageSize: input.imageSize
7617
+ }
7618
+ }
7619
+ } : {
7620
+ model: input.model,
7621
+ input: input.interactionInput,
7622
+ response_format: {
7623
+ type: "image",
7624
+ mime_type: REQUESTED_MEDIA_TYPE,
7625
+ aspect_ratio: input.aspectRatio,
7626
+ image_size: input.imageSize
7627
+ }
7628
+ };
7629
+ const response = await guardedFetch(`${label} request`, input.signal, () => fetch(endpoint, {
7524
7630
  method: "POST",
7525
7631
  redirect: "error",
7526
7632
  signal: input.signal,
@@ -7528,17 +7634,8 @@ async function requestGoogleImage(input) {
7528
7634
  "content-type": "application/json",
7529
7635
  "x-goog-api-key": input.apiKey
7530
7636
  },
7531
- body: JSON.stringify({
7532
- model: input.model,
7533
- input: input.interactionInput,
7534
- response_format: {
7535
- type: "image",
7536
- mime_type: REQUESTED_MEDIA_TYPE,
7537
- aspect_ratio: input.aspectRatio,
7538
- image_size: input.imageSize
7539
- }
7540
- })
7541
- });
7637
+ body: JSON.stringify(body)
7638
+ }), input.apiKey);
7542
7639
  const text = await readBoundedText$2(response, Math.ceil(input.maxBytes * 1.4) + ERROR_LIMIT$2, label);
7543
7640
  if (!response.ok) throw new Error(`${label} failed (${response.status}): ${redactSecrets(text, input.apiKey).slice(0, ERROR_LIMIT$2)}`);
7544
7641
  let payload;
@@ -7547,7 +7644,7 @@ async function requestGoogleImage(input) {
7547
7644
  } catch {
7548
7645
  throw new Error(`${label} returned invalid JSON`);
7549
7646
  }
7550
- const image = outputImage(payload);
7647
+ const image = input.native ? nativeOutputImage(payload) : outputImage(payload);
7551
7648
  if (image === void 0) throw new Error(`${label} returned no image: ${redactSecrets(text, input.apiKey).slice(0, ERROR_LIMIT$2)}`);
7552
7649
  const data = decodeBase64$2(image.data, label);
7553
7650
  if (data.byteLength > input.maxBytes) throw new Error(`${label} exceeded the ${String(input.maxBytes)} byte image limit`);
@@ -7558,6 +7655,49 @@ async function requestGoogleImage(input) {
7558
7655
  mediaType
7559
7656
  };
7560
7657
  }
7658
+ /** Accept a versioned base, the existing interactions URL, or a full native URL. */
7659
+ function nativeGoogleUrl(endpoint, model) {
7660
+ let url;
7661
+ try {
7662
+ url = new URL(endpoint);
7663
+ } catch {
7664
+ throw new Error("Google endpoint must be an absolute URL");
7665
+ }
7666
+ const base = url.pathname.replace(/\/+$/, "").replace(/\/(?:interactions|models(?:\/[^/]+:generateContent)?)$/, "");
7667
+ url.pathname = `${base}/models${model === void 0 ? "" : `/${encodeURIComponent(model.replace(/^models\//, ""))}:generateContent`}`;
7668
+ return url;
7669
+ }
7670
+ /** The settings probe and model picker use the same base as image requests. */
7671
+ function googleModelsEndpoint(endpoint, native) {
7672
+ if (native) return nativeGoogleUrl(endpoint).toString();
7673
+ let url;
7674
+ try {
7675
+ url = new URL(endpoint);
7676
+ } catch {
7677
+ throw new Error("Google endpoint must be an absolute URL");
7678
+ }
7679
+ const segments = url.pathname.split("/").filter(Boolean);
7680
+ segments[segments.length - 1] = "models";
7681
+ url.pathname = `/${segments.join("/")}`;
7682
+ return url.toString();
7683
+ }
7684
+ function nativeOutputImage(value) {
7685
+ const candidates = record$5(value)?.candidates;
7686
+ if (!Array.isArray(candidates)) return void 0;
7687
+ for (const candidate of candidates) {
7688
+ const parts = record$5(record$5(candidate)?.content)?.parts;
7689
+ if (!Array.isArray(parts)) continue;
7690
+ for (const value of parts) {
7691
+ const part = record$5(value);
7692
+ if (part?.thought === true) continue;
7693
+ const image = record$5(part?.inlineData ?? part?.inline_data);
7694
+ if (typeof image?.data === "string") return {
7695
+ data: image.data,
7696
+ mime_type: image.mimeType ?? image.mime_type
7697
+ };
7698
+ }
7699
+ }
7700
+ }
7561
7701
  function outputImage(value) {
7562
7702
  const interaction = record$5(value);
7563
7703
  if (interaction === void 0) return void 0;
@@ -7601,7 +7741,7 @@ async function readBoundedText$2(response, maxBytes, label) {
7601
7741
  let bytes = 0;
7602
7742
  try {
7603
7743
  for (;;) {
7604
- const next = await reader.read();
7744
+ const next = await readResponseBody(response, () => reader.read());
7605
7745
  if (next.done) break;
7606
7746
  bytes += next.value.byteLength;
7607
7747
  if (bytes > maxBytes) throw new Error(`${label} response exceeded the ${String(maxBytes)} byte limit`);
@@ -8101,7 +8241,7 @@ function jsonError$4(res, status, code) {
8101
8241
  //#region lib/types/openai-compatible.js
8102
8242
  const ERROR_LIMIT$1 = 4096;
8103
8243
  async function generateOpenAICompatibleImage(input) {
8104
- return parseImageResponse(await fetch(imageEndpoint$1(input.baseURL, "generations"), {
8244
+ return parseImageResponse(await guardedFetch(`${input.provider} image request`, input.signal, () => fetch(imageEndpoint$1(input.baseURL, "generations"), {
8105
8245
  method: "POST",
8106
8246
  redirect: "error",
8107
8247
  signal: input.signal,
@@ -8120,10 +8260,10 @@ async function generateOpenAICompatibleImage(input) {
8120
8260
  } : {},
8121
8261
  ...input.extraBody ?? {}
8122
8262
  })
8123
- }), input.provider, input);
8263
+ }), input.apiKey), input.provider, input);
8124
8264
  }
8125
8265
  async function editOpenAICompatibleImage(input) {
8126
- if (input.editFormat === "qwenJson") return parseImageResponse(await fetch(imageEndpoint$1(input.baseURL, "generations"), {
8266
+ if (input.editFormat === "qwenJson") return parseImageResponse(await guardedFetch("openai-compat image edit request", input.signal, () => fetch(imageEndpoint$1(input.baseURL, "generations"), {
8127
8267
  method: "POST",
8128
8268
  redirect: "error",
8129
8269
  signal: input.signal,
@@ -8140,13 +8280,13 @@ async function editOpenAICompatibleImage(input) {
8140
8280
  ...input.editExtra ?? {},
8141
8281
  ...input.extraBody ?? {}
8142
8282
  })
8143
- }), "openai-compat", input);
8283
+ }), input.apiKey), "openai-compat", input);
8144
8284
  if (input.editFormat === "xaiJson") {
8145
8285
  const references = input.sourceImages.map((image) => ({
8146
8286
  type: "image_url",
8147
8287
  url: toDataUrl$2(image)
8148
8288
  }));
8149
- return parseImageResponse(await fetch(imageEndpoint$1(input.baseURL, "edits"), {
8289
+ return parseImageResponse(await guardedFetch("xai image edit request", input.signal, () => fetch(imageEndpoint$1(input.baseURL, "edits"), {
8150
8290
  method: "POST",
8151
8291
  redirect: "error",
8152
8292
  signal: input.signal,
@@ -8161,7 +8301,7 @@ async function editOpenAICompatibleImage(input) {
8161
8301
  ...input.quality === void 0 ? {} : { quality: input.quality },
8162
8302
  ...input.extraBody ?? {}
8163
8303
  })
8164
- }), "xai", input);
8304
+ }), input.apiKey), "xai", input);
8165
8305
  }
8166
8306
  if (input.editFormat === "jsonImageUrlArray") {
8167
8307
  const body = {
@@ -8175,7 +8315,7 @@ async function editOpenAICompatibleImage(input) {
8175
8315
  ...input.editExtra ?? {},
8176
8316
  ...input.extraBody ?? {}
8177
8317
  };
8178
- return parseImageResponse(await fetch(imageEndpoint$1(input.baseURL, "edits"), {
8318
+ return parseImageResponse(await guardedFetch("openai image edit request", input.signal, () => fetch(imageEndpoint$1(input.baseURL, "edits"), {
8179
8319
  method: "POST",
8180
8320
  redirect: "error",
8181
8321
  signal: input.signal,
@@ -8184,7 +8324,7 @@ async function editOpenAICompatibleImage(input) {
8184
8324
  "content-type": "application/json"
8185
8325
  },
8186
8326
  body: JSON.stringify(body)
8187
- }), "openai", input);
8327
+ }), input.apiKey), "openai", input);
8188
8328
  }
8189
8329
  if (input.editFormat === "formReferenceImages") {
8190
8330
  const form = new FormData();
@@ -8195,13 +8335,13 @@ async function editOpenAICompatibleImage(input) {
8195
8335
  for (const [key, value] of Object.entries(input.extraBody ?? {})) form.append(key, String(value));
8196
8336
  form.append("response_format", "b64_json");
8197
8337
  form.append("reference_images", JSON.stringify(input.sourceImages.map((sourceImage) => Buffer.from(sourceImage.data).toString("base64"))));
8198
- return parseImageResponse(await fetch(imageEndpoint$1(input.baseURL, "edits"), {
8338
+ return parseImageResponse(await guardedFetch("openai image edit request", input.signal, () => fetch(imageEndpoint$1(input.baseURL, "edits"), {
8199
8339
  method: "POST",
8200
8340
  redirect: "error",
8201
8341
  signal: input.signal,
8202
8342
  headers: { authorization: `Bearer ${input.apiKey}` },
8203
8343
  body: form
8204
- }), "openai", input);
8344
+ }), input.apiKey), "openai", input);
8205
8345
  }
8206
8346
  const form = new FormData();
8207
8347
  const imageField = input.sourceImages.length > 1 ? "image[]" : "image";
@@ -8216,13 +8356,13 @@ async function editOpenAICompatibleImage(input) {
8216
8356
  if (input.size !== void 0 && input.size.length > 0) form.append("size", input.size);
8217
8357
  if (input.quality !== void 0) form.append("quality", input.quality);
8218
8358
  for (const [key, value] of Object.entries(input.extraBody ?? {})) form.append(key, String(value));
8219
- return parseImageResponse(await fetch(imageEndpoint$1(input.baseURL, "edits"), {
8359
+ return parseImageResponse(await guardedFetch("openai image edit request", input.signal, () => fetch(imageEndpoint$1(input.baseURL, "edits"), {
8220
8360
  method: "POST",
8221
8361
  redirect: "error",
8222
8362
  signal: input.signal,
8223
8363
  headers: { authorization: `Bearer ${input.apiKey}` },
8224
8364
  body: form
8225
- }), "openai", input);
8365
+ }), input.apiKey), "openai", input);
8226
8366
  }
8227
8367
  async function parseImageResponse(response, provider, input) {
8228
8368
  const text = await readBoundedText$1(response, Math.ceil(input.maxBytes * 1.4) + ERROR_LIMIT$1);
@@ -8282,15 +8422,15 @@ async function downloadImage$1(url, provider, input) {
8282
8422
  mediaType: detectImageMediaType(data) ?? imageMediaType$2(parsed.mediaType) ?? "image/png"
8283
8423
  };
8284
8424
  }
8285
- let response = await fetch(url, {
8425
+ let response = await guardedFetch(`${provider} image download`, input.signal, () => fetch(url, {
8286
8426
  redirect: "follow",
8287
8427
  signal: input.signal,
8288
8428
  ...input.apiKey === void 0 ? {} : { headers: { authorization: `Bearer ${input.apiKey}` } }
8289
- });
8290
- if (!response.ok && input.apiKey !== void 0) response = await fetch(url, {
8429
+ }), input.apiKey);
8430
+ if (!response.ok && input.apiKey !== void 0) response = await guardedFetch(`${provider} image download`, input.signal, () => fetch(url, {
8291
8431
  redirect: "follow",
8292
8432
  signal: input.signal
8293
- });
8433
+ }), input.apiKey);
8294
8434
  if (!response.ok) throw new Error(`${provider} image download failed (${response.status})`);
8295
8435
  const data = await readBoundedBytes$1(response, input.maxBytes);
8296
8436
  const mediaType = detectImageMediaType(data) ?? imageMediaType$2(response.headers.get("content-type"));
@@ -8336,7 +8476,7 @@ async function readBoundedBytes$1(response, maxBytes) {
8336
8476
  let bytes = 0;
8337
8477
  try {
8338
8478
  for (;;) {
8339
- const next = await reader.read();
8479
+ const next = await readResponseBody(response, () => reader.read());
8340
8480
  if (next.done) break;
8341
8481
  bytes += next.value.byteLength;
8342
8482
  if (bytes > maxBytes) throw new Error(`Image response exceeded the ${String(maxBytes)} byte limit`);
@@ -8358,7 +8498,7 @@ async function readBoundedBytes$1(response, maxBytes) {
8358
8498
  const ERROR_LIMIT = 4096;
8359
8499
  /** Edit one image through Ark ImageGenerations using a data-URL reference. */
8360
8500
  async function editSeedreamImage(input) {
8361
- const response = await fetch(imageEndpoint(input.baseURL), {
8501
+ const response = await guardedFetch("seedream image editing", input.signal, () => fetch(imageEndpoint(input.baseURL), {
8362
8502
  method: "POST",
8363
8503
  redirect: "error",
8364
8504
  signal: input.signal,
@@ -8374,7 +8514,7 @@ async function editSeedreamImage(input) {
8374
8514
  ...arkOutputBody(input.arkOptions),
8375
8515
  response_format: "b64_json"
8376
8516
  })
8377
- });
8517
+ }), input.apiKey);
8378
8518
  const text = await readBoundedText(response, Math.ceil(input.maxBytes * 1.4) + ERROR_LIMIT);
8379
8519
  if (!response.ok) throw new Error(`seedream image editing failed (${response.status}): ${redactSecrets(text, input.apiKey).slice(0, ERROR_LIMIT)}`);
8380
8520
  let payload;
@@ -8423,10 +8563,10 @@ function firstImage(value) {
8423
8563
  }
8424
8564
  async function downloadImage(url, input) {
8425
8565
  if (url === void 0) throw new Error("seedream image editing returned no image data");
8426
- const response = await fetch(url, {
8566
+ const response = await guardedFetch("seedream image download", input.signal, () => fetch(url, {
8427
8567
  redirect: "follow",
8428
8568
  signal: input.signal
8429
- });
8569
+ }), input.apiKey);
8430
8570
  if (!response.ok) throw new Error(`seedream image download failed (${response.status})`);
8431
8571
  const mediaType = imageMediaType$1(response.headers.get("content-type"));
8432
8572
  if (mediaType === void 0) throw new Error("seedream image download returned unsupported content type");
@@ -8456,7 +8596,7 @@ async function readBoundedBytes(response, maxBytes) {
8456
8596
  let bytes = 0;
8457
8597
  try {
8458
8598
  for (;;) {
8459
- const next = await reader.read();
8599
+ const next = await readResponseBody(response, () => reader.read());
8460
8600
  if (next.done) break;
8461
8601
  bytes += next.value.byteLength;
8462
8602
  if (bytes > maxBytes) throw new Error(`Image response exceeded the ${String(maxBytes)} byte limit`);
@@ -8937,20 +9077,24 @@ function getAntigravityVersion() {
8937
9077
  return antigravityVersion ?? ANTIGRAVITY_VERSION_FALLBACK;
8938
9078
  }
8939
9079
  /** Fetch and cache the current version once; failures keep the fallback. */
8940
- async function initAntigravityVersion() {
9080
+ async function initAntigravityVersion(signal) {
9081
+ signal?.throwIfAborted();
8941
9082
  if (antigravityVersion !== void 0) return;
8942
- const parsed = await fetchVersionText(VERSION_URL) ?? await fetchVersionText(CHANGELOG_URL, CHANGELOG_SCAN_CHARS);
9083
+ const parsed = await fetchVersionText(VERSION_URL, void 0, signal) ?? await fetchVersionText(CHANGELOG_URL, CHANGELOG_SCAN_CHARS, signal);
9084
+ signal?.throwIfAborted();
8943
9085
  if (parsed !== void 0) antigravityVersion = parsed;
8944
9086
  }
8945
- async function fetchVersionText(url, maxChars) {
9087
+ async function fetchVersionText(url, maxChars, signal) {
8946
9088
  try {
8947
- const response = await fetch(url, { signal: AbortSignal.timeout(VERSION_FETCH_TIMEOUT_MS) });
9089
+ const timeout = AbortSignal.timeout(VERSION_FETCH_TIMEOUT_MS);
9090
+ const response = await fetch(url, { signal: signal === void 0 ? timeout : AbortSignal.any([signal, timeout]) });
8948
9091
  if (!response.ok) return void 0;
8949
9092
  let text = await response.text();
8950
9093
  if (maxChars !== void 0) text = text.slice(0, maxChars);
8951
9094
  const match = text.match(VERSION_REGEX);
8952
9095
  return match === null ? void 0 : match[0];
8953
- } catch {
9096
+ } catch (error) {
9097
+ if (signal?.aborted === true) throw error;
8954
9098
  return;
8955
9099
  }
8956
9100
  }
@@ -9016,16 +9160,19 @@ function antigravityAuthorizeUrl(cfg, pkce) {
9016
9160
  });
9017
9161
  }
9018
9162
  /**
9019
- * Headers for project discovery (loadCodeAssist): the reference keeps the
9020
- * plain SDK identity here, mirroring its project.js loadHeaders.
9163
+ * Preserve the SDK discovery identity first. A no-project UNSUPPORTED_CLIENT
9164
+ * response can be retried with the same Antigravity identity as generation.
9021
9165
  */
9022
- function loadHeaders(projectId) {
9166
+ function loadHeaders(projectId, identity) {
9023
9167
  return {
9024
- "User-Agent": "google-api-nodejs-client/9.15.1",
9168
+ "User-Agent": identity === "antigravity" ? antigravityUserAgent() : "google-api-nodejs-client/9.15.1",
9025
9169
  "X-Goog-Api-Client": "google-cloud-sdk vscode_cloudshelleditor/0.1",
9026
9170
  "Client-Metadata": JSON.stringify(clientMetadata(projectId))
9027
9171
  };
9028
9172
  }
9173
+ function antigravityUserAgent() {
9174
+ return `Mozilla/5.0 (${process.platform === "darwin" ? "Macintosh; Intel Mac OS X 10_15_7" : "Windows NT 10.0; Win64; x64"}) AppleWebKit/537.36 (KHTML, like Gecko) Antigravity/${getAntigravityVersion()} Chrome/138.0.7204.235 Electron/37.3.1 Safari/537.36`;
9175
+ }
9029
9176
  /**
9030
9177
  * Headers for content requests (image generation): the full Electron
9031
9178
  * browser User-Agent with the Antigravity client version, matching the
@@ -9034,7 +9181,7 @@ function loadHeaders(projectId) {
9034
9181
  */
9035
9182
  function contentHeaders(projectId) {
9036
9183
  return {
9037
- "User-Agent": `Mozilla/5.0 (${process.platform === "darwin" ? "Macintosh; Intel Mac OS X 10_15_7" : "Windows NT 10.0; Win64; x64"}) AppleWebKit/537.36 (KHTML, like Gecko) Antigravity/${getAntigravityVersion()} Chrome/138.0.7204.235 Electron/37.3.1 Safari/537.36`,
9184
+ "User-Agent": antigravityUserAgent(),
9038
9185
  "X-Goog-Api-Client": "google-cloud-sdk vscode_cloudshelleditor/0.1",
9039
9186
  "Client-Metadata": JSON.stringify(clientMetadata(projectId))
9040
9187
  };
@@ -9100,40 +9247,50 @@ async function antigravityResolveProjectContext(blob, signal) {
9100
9247
  signal?.throwIfAborted();
9101
9248
  const metadata = clientMetadata("");
9102
9249
  const diagnostics = [];
9103
- for (const base of LOAD_ENDPOINTS) {
9104
- const label = `loadCodeAssist ${new URL(base).hostname}`;
9105
- try {
9106
- const response = await fetch(`${base}/v1internal:loadCodeAssist`, {
9107
- method: "POST",
9108
- headers: {
9109
- "Content-Type": "application/json",
9110
- Authorization: `Bearer ${blob.accessToken}`,
9111
- ...loadHeaders("")
9112
- },
9113
- body: JSON.stringify({ metadata }),
9114
- signal: signal === void 0 ? AbortSignal.timeout(25e3) : AbortSignal.any([signal, AbortSignal.timeout(25e3)])
9115
- });
9116
- if (!response.ok) {
9117
- diagnostics.push(`${label}: ${httpDiagnostic(response.status, await response.text().catch(() => ""), blob)}`);
9118
- continue;
9119
- }
9120
- const payload = recordOf(await response.json().catch(() => null));
9121
- if (payload === void 0) {
9122
- diagnostics.push(`${label}: HTTP ${String(response.status)}, invalid response`);
9123
- continue;
9250
+ const identityRetryEndpoints = [];
9251
+ for (const identity of ["sdk", "antigravity"]) {
9252
+ const endpoints = identity === "sdk" ? LOAD_ENDPOINTS : identityRetryEndpoints;
9253
+ if (identity === "antigravity") {
9254
+ if (endpoints.length === 0) break;
9255
+ await initAntigravityVersion(signal);
9256
+ }
9257
+ for (const base of endpoints) {
9258
+ signal?.throwIfAborted();
9259
+ const label = `loadCodeAssist ${new URL(base).hostname}${identity === "antigravity" ? ` [Antigravity/${getAntigravityVersion()}]` : ""}`;
9260
+ try {
9261
+ const response = await fetch(`${base}/v1internal:loadCodeAssist`, {
9262
+ method: "POST",
9263
+ headers: {
9264
+ "Content-Type": "application/json",
9265
+ Authorization: `Bearer ${blob.accessToken}`,
9266
+ ...loadHeaders("", identity)
9267
+ },
9268
+ body: JSON.stringify({ metadata }),
9269
+ signal: signal === void 0 ? AbortSignal.timeout(25e3) : AbortSignal.any([signal, AbortSignal.timeout(25e3)])
9270
+ });
9271
+ if (!response.ok) {
9272
+ diagnostics.push(`${label}: ${httpDiagnostic(response.status, await response.text().catch(() => ""), blob)}`);
9273
+ continue;
9274
+ }
9275
+ const payload = recordOf(await response.json().catch(() => null));
9276
+ if (payload === void 0) {
9277
+ diagnostics.push(`${label}: HTTP ${String(response.status)}, invalid response`);
9278
+ continue;
9279
+ }
9280
+ const project = payload.cloudaicompanionProject;
9281
+ const projectRecord = recordOf(project);
9282
+ const id = typeof project === "string" ? project : typeof projectRecord?.id === "string" ? projectRecord.id : "";
9283
+ if (id.trim().length > 0) return {
9284
+ projectId: id.trim(),
9285
+ source: "account",
9286
+ diagnostics
9287
+ };
9288
+ diagnostics.push(`${label}: HTTP ${String(response.status)}, ${missingProjectDiagnostic(payload, blob)}`);
9289
+ if (identity === "sdk" && Array.isArray(payload.ineligibleTiers) && payload.ineligibleTiers.some((tier) => recordOf(tier)?.reasonCode === "UNSUPPORTED_CLIENT")) identityRetryEndpoints.push(base);
9290
+ } catch (error) {
9291
+ if (signal?.aborted === true) throw error;
9292
+ diagnostics.push(`${label}: ${diagnosticText(error instanceof Error ? error.message : "request failed", blob)}`);
9124
9293
  }
9125
- const project = payload.cloudaicompanionProject;
9126
- const projectRecord = recordOf(project);
9127
- const id = typeof project === "string" ? project : typeof projectRecord?.id === "string" ? projectRecord.id : "";
9128
- if (id.trim().length > 0) return {
9129
- projectId: id.trim(),
9130
- source: "account",
9131
- diagnostics
9132
- };
9133
- diagnostics.push(`${label}: HTTP ${String(response.status)}, ${missingProjectDiagnostic(payload, blob)}`);
9134
- } catch (error) {
9135
- if (signal?.aborted === true) throw error;
9136
- diagnostics.push(`${label}: ${diagnosticText(error instanceof Error ? error.message : "request failed", blob)}`);
9137
9294
  }
9138
9295
  }
9139
9296
  return {
@@ -9250,7 +9407,7 @@ function antigravityEnvelope(projectId, request) {
9250
9407
  */
9251
9408
  async function antigravityGenerateImage(options) {
9252
9409
  options.signal?.throwIfAborted();
9253
- await initAntigravityVersion();
9410
+ await initAntigravityVersion(options.signal);
9254
9411
  const inner = antigravityImageBody({
9255
9412
  prompt: options.prompt,
9256
9413
  ...options.aspectRatio !== void 0 ? { aspectRatio: options.aspectRatio } : {},
@@ -27679,6 +27836,7 @@ async function generateFromStudio(ctx, config, input, signal, fallbackWorkspaceR
27679
27836
  generated = input.mode === "edit" ? await editGoogleImage({
27680
27837
  apiKey: credential,
27681
27838
  endpoint: wired.endpoint,
27839
+ native: wired.native,
27682
27840
  model: wired.model,
27683
27841
  prompt: input.prompt,
27684
27842
  sourceImages,
@@ -27689,6 +27847,7 @@ async function generateFromStudio(ctx, config, input, signal, fallbackWorkspaceR
27689
27847
  }) : await generateGoogleImage({
27690
27848
  apiKey: credential,
27691
27849
  endpoint: wired.endpoint,
27850
+ native: wired.native,
27692
27851
  model: wired.model,
27693
27852
  prompt: input.prompt,
27694
27853
  aspectRatio,
@@ -28321,15 +28480,7 @@ function googleModelsUrl(config) {
28321
28480
  const endpoint = config.googleEndpoint;
28322
28481
  const fallback = "https://generativelanguage.googleapis.com/v1beta/models";
28323
28482
  if (typeof endpoint !== "string" || endpoint.trim().length === 0) return fallback;
28324
- try {
28325
- const url = new URL(endpoint);
28326
- const segments = url.pathname.split("/").filter((segment) => segment.length > 0);
28327
- segments[segments.length - 1] = "models";
28328
- url.pathname = `/${segments.join("/")}`;
28329
- return url.toString();
28330
- } catch {
28331
- throw new Error("Google endpoint must be an absolute URL");
28332
- }
28483
+ return googleModelsEndpoint(endpoint, config.googleNative === true);
28333
28484
  }
28334
28485
  function joinUrl(base, path) {
28335
28486
  return new URL(path, base.endsWith("/") ? base : `${base}/`).toString();
@@ -28834,6 +28985,7 @@ function apply(ctx, config = {}) {
28834
28985
  return saveGenerated(ctx, await generateGoogleImage({
28835
28986
  apiKey: credential,
28836
28987
  endpoint: active.endpoint,
28988
+ native: active.native,
28837
28989
  model: active.model,
28838
28990
  prompt: args.prompt,
28839
28991
  aspectRatio,
@@ -29341,6 +29493,7 @@ function apply(ctx, config = {}) {
29341
29493
  return saveGenerated(ctx, await editGoogleImage({
29342
29494
  apiKey: credential,
29343
29495
  endpoint: active.endpoint,
29496
+ native: active.native,
29344
29497
  model: active.model,
29345
29498
  prompt: args.prompt,
29346
29499
  sourceImages,
@@ -17,6 +17,7 @@ interface ImageSettings {
17
17
  provider?: Provider;
18
18
  googleModel?: string;
19
19
  googleEndpoint?: string;
20
+ googleNative?: boolean;
20
21
  openaiBaseURL?: string;
21
22
  openaiModel?: string;
22
23
  openaiSizes?: Record<string, Record<string, string>>;