dsh-image-gen 0.9.0 → 0.9.1
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/README.en.md +1 -1
- package/README.md +1 -1
- package/lib/client.js +1 -1
- package/lib/index.js +167 -75
- package/lib/types/fetch-diagnostics.d.ts +10 -0
- package/lib/types/subscription/vendors/antigravity.d.ts +1 -1
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -333,7 +333,7 @@ Bring private image generation on your local GPU directly into Agent conversatio
|
|
|
333
333
|
| Google subscription | auto · 1:1 · 3:2 · 2:3 · 4:3 · 3:4 · 4:5 · 5:4 · 16:9 · 9:16 · 21:9 | standard / HD (4K) |
|
|
334
334
|
|
|
335
335
|
> An `auto` ratio lets the model pick the framing from the prompt; tiers the upstream dropped (e.g. Seedream 5.0's 1K) are never exposed.
|
|
336
|
-
> Google subscription discovers account projects with valid platform enums and includes the ordinary daily endpoint as a compatibility fallback. If discovery returns no project, the legacy compatibility project is still tried but is not cached as an actual account project. Generation failures retain redacted discovery, eligibility, and per-endpoint diagnostics instead of only the last 429. The subscription “Test connection” checks stored sign-in status; successful generation verifies image access for that account. A Code Assist `UNSUPPORTED_CLIENT` response alone does not establish that image generation is unavailable.
|
|
336
|
+
> Google subscription discovers account projects with valid platform enums and includes the ordinary daily endpoint as a compatibility fallback. When discovery returns no project with `UNSUPPORTED_CLIENT`, it retries using the Antigravity client identity without automatically onboarding the account. If discovery still returns no project, the legacy compatibility project is still tried but is not cached as an actual account project. Generation failures retain redacted discovery, eligibility, and per-endpoint diagnostics instead of only the last 429. The subscription “Test connection” checks stored sign-in status; successful generation verifies image access for that account. A Code Assist `UNSUPPORTED_CLIENT` response alone does not establish that image generation is unavailable.
|
|
337
337
|
> Studio exposes separate aspect ratio, resolution, and generation quality controls for OpenAI and compatible providers. Resolution labels show pixel dimensions. Comparison and regeneration preserve these separate parameters.
|
|
338
338
|
> Official OpenAI size presets can be customized with `openaiSizes`, shaped as ratio → tier → pixels, e.g. `{ "16:9": { "2K": "2048x1152", "4K": "3840x2160" } }`. For the gpt-image-2 family, dimensions must be multiples of 16, the longest edge at most 3840, aspect ratio within 1:3–3:1, and total pixels within 655360–8294400. Outputs above 3686400 pixels have experimental upstream support. See the [official API reference](https://developers.openai.com/api/reference/resources/images/methods/generate). Older GPT Image models retain their three standard sizes.
|
|
339
339
|
> Compatible providers use `openaiCompatSizes` in the same format; an empty table retains the legacy sizes. `openaiCompatQuality` sets the default generation quality (empty means omitted), and `openaiCompatQualityOptions` sets Studio choices. Choosing “Provider default (omit)” omits quality for that request. Conversation tools also accept independent `size` and `quality` parameters.
|
package/README.md
CHANGED
|
@@ -333,7 +333,7 @@ pnpm dsh plugin --profile web add ./dsh-image-gen
|
|
|
333
333
|
| Google 订阅 | 自动 · 1:1 · 3:2 · 2:3 · 4:3 · 3:4 · 4:5 · 5:4 · 16:9 · 9:16 · 21:9 | 标准 / 高清(4K) |
|
|
334
334
|
|
|
335
335
|
> 「自动」比例表示由模型按 Prompt 自行决定构图;上游已移除的档位(如 Seedream 5.0 的 1K)插件不会暴露。
|
|
336
|
-
> Google 订阅会使用有效的平台枚举发现账号项目,并提供普通 daily
|
|
336
|
+
> Google 订阅会使用有效的平台枚举发现账号项目,并提供普通 daily 入口作为兼容回退。项目发现未返回项目且提示 `UNSUPPORTED_CLIENT` 时,会再用 Antigravity 客户端身份重试;该过程不自动开户。仍未返回项目时保留旧兼容项目尝试,但不会把它缓存成账号真实项目;若生图仍失败,错误会保留项目发现、账号资格及各生图入口的脱敏响应,避免只显示最后一个 429。设置中的订阅「测试连接」只检查登录记录,实际生图成功才验证该账号的生图链路;`UNSUPPORTED_CLIENT` 的 Code Assist 提示也不单独判定生图不可用。
|
|
337
337
|
> OpenAI 与兼容渠道的 Studio 控件分别显示「比例 / 分辨率 / 生成质量」,分辨率选项会显示实际像素。多模型对比及再次生成也保留这两类参数。
|
|
338
338
|
> OpenAI 官方可通过设置中的 `openaiSizes` 自定义尺寸表,格式为「比例 → 档位 → 像素」,例如 `{ "16:9": { "2K": "2048x1152", "4K": "3840x2160" } }`。gpt-image-2 系列按官方限制校验:边长为 16 的倍数、最大 3840、比例在 1:3 到 3:1、总像素 655360–8294400;超过 3686400 像素的输出属于上游实验支持。[官方说明](https://developers.openai.com/api/reference/resources/images/methods/generate)。旧版 GPT Image 模型仍只使用三种标准尺寸。
|
|
339
339
|
> OpenAI Compatible 的尺寸表为 `openaiCompatSizes`,格式相同;留空时保持原来的标准尺寸。`openaiCompatQuality` 设置默认生成质量(留空不发送),`openaiCompatQualityOptions` 设置 Studio 可选值。选择「渠道默认(不发送)」可在单次请求中省略 quality;对话工具也支持独立的 `size` 和 `quality` 参数。
|
package/lib/client.js
CHANGED
|
@@ -125221,7 +125221,7 @@ body[data-ds-dark-theme] .dsh-ig-workbench{--ig-danger:#f08c8c;--ig-warn:#e5b76a
|
|
|
125221
125221
|
`;
|
|
125222
125222
|
//#endregion
|
|
125223
125223
|
//#region lib/types/client/index.js
|
|
125224
|
-
console.info(`[dsh-image-gen] canvas bundle 2026-10-
|
|
125224
|
+
console.info(`[dsh-image-gen] canvas bundle 2026-10-15T09:51:10.196Z`);
|
|
125225
125225
|
/** Right-sidebar tab-type registration id (DSH 0.1.5 `sidebarRightTabs`). */
|
|
125226
125226
|
const SIDEBAR_STUDIO_TAB_ID = "dsh-image-gen/studio";
|
|
125227
125227
|
/** The tab kind: a page type opened by kind, recognizing no resource address. */
|
package/lib/index.js
CHANGED
|
@@ -7382,6 +7382,81 @@ async function readBoundedBytes$2(response, maxBytes) {
|
|
|
7382
7382
|
return joined;
|
|
7383
7383
|
}
|
|
7384
7384
|
//#endregion
|
|
7385
|
+
//#region lib/types/fetch-diagnostics.js
|
|
7386
|
+
/**
|
|
7387
|
+
* Transport-failure diagnostics for provider fetches (#79).
|
|
7388
|
+
*
|
|
7389
|
+
* Node's undici reports the real reason behind a failed fetch (socket reset,
|
|
7390
|
+
* DNS, TLS, ...) only on `error.cause`, and the host's tool-error rendering
|
|
7391
|
+
* keeps just `.message` — so the conversation sees nothing but undici's bare
|
|
7392
|
+
* "fetch failed". These helpers rethrow transport failures with the failing
|
|
7393
|
+
* phase, elapsed seconds, and a bounded cause chain.
|
|
7394
|
+
*/
|
|
7395
|
+
/** Cap a single transport-failure description so diagnostics cannot flood the conversation. */
|
|
7396
|
+
const TRANSPORT_DETAIL_LIMIT = 600;
|
|
7397
|
+
/** How deep to walk `error.cause` chains; undici nests at most twice in practice. */
|
|
7398
|
+
const CAUSE_CHAIN_DEPTH = 3;
|
|
7399
|
+
/** Keep body reads tied to the same request timing, cancellation, and secrets. */
|
|
7400
|
+
const responseContexts = /* @__PURE__ */ new WeakMap();
|
|
7401
|
+
/**
|
|
7402
|
+
* Render one error plus its `cause` chain as a single line, e.g.
|
|
7403
|
+
* `TypeError: fetch failed <- Error [UND_ERR_SOCKET]: other side closed`.
|
|
7404
|
+
*/
|
|
7405
|
+
function describeErrorChain(error, depth = 0) {
|
|
7406
|
+
if (depth >= CAUSE_CHAIN_DEPTH) return "...";
|
|
7407
|
+
if (error === void 0 || error === null) return "unknown transport error";
|
|
7408
|
+
if (typeof error === "object") {
|
|
7409
|
+
const details = error;
|
|
7410
|
+
if (error instanceof Error || typeof details.message === "string") {
|
|
7411
|
+
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(" ");
|
|
7412
|
+
const message = typeof details.message === "string" ? details.message : "";
|
|
7413
|
+
const text = label.length > 0 && message.length > 0 ? `${label}: ${message}` : label || message;
|
|
7414
|
+
return details.cause !== void 0 && details.cause !== null && depth + 1 < CAUSE_CHAIN_DEPTH ? `${text} <- ${describeErrorChain(details.cause, depth + 1)}` : text || "unknown transport error";
|
|
7415
|
+
}
|
|
7416
|
+
if (typeof details.kind === "string") return details.reason === void 0 || details.reason === null ? details.kind : `${details.kind}: ${describeErrorChain(details.reason, depth + 1)}`;
|
|
7417
|
+
}
|
|
7418
|
+
return String(error).trim() || "unknown transport error";
|
|
7419
|
+
}
|
|
7420
|
+
/** One line describing why a provider fetch failed: the abort reason when the caller cancelled, the cause chain otherwise. */
|
|
7421
|
+
function describeTransportFailure(error, signal) {
|
|
7422
|
+
try {
|
|
7423
|
+
if (signal?.aborted === true) return `aborted (${describeErrorChain(signal.reason ?? error)})`;
|
|
7424
|
+
return describeErrorChain(error);
|
|
7425
|
+
} catch {
|
|
7426
|
+
return "unknown transport error";
|
|
7427
|
+
}
|
|
7428
|
+
}
|
|
7429
|
+
async function withTransportDiagnostics(context, run) {
|
|
7430
|
+
try {
|
|
7431
|
+
return await run();
|
|
7432
|
+
} catch (error) {
|
|
7433
|
+
const elapsed = ((performance.now() - context.startedAt) / 1e3).toFixed(1);
|
|
7434
|
+
const detail = redactSecrets(describeTransportFailure(error, context.signal), ...context.secrets).replace(/\s+/g, " ").slice(0, TRANSPORT_DETAIL_LIMIT);
|
|
7435
|
+
throw new Error(`${context.label} failed after ${elapsed}s: ${detail}`, { cause: error });
|
|
7436
|
+
}
|
|
7437
|
+
}
|
|
7438
|
+
/**
|
|
7439
|
+
* Await one provider fetch and rethrow transport failures as
|
|
7440
|
+
* `${label} failed after 12.3s: ${cause chain}`. The original error stays
|
|
7441
|
+
* reachable on `cause` for host-side error rendering.
|
|
7442
|
+
*/
|
|
7443
|
+
async function guardedFetch(label, signal, run, ...secrets) {
|
|
7444
|
+
const context = {
|
|
7445
|
+
label,
|
|
7446
|
+
signal,
|
|
7447
|
+
secrets,
|
|
7448
|
+
startedAt: performance.now()
|
|
7449
|
+
};
|
|
7450
|
+
const response = await withTransportDiagnostics(context, run);
|
|
7451
|
+
responseContexts.set(response, context);
|
|
7452
|
+
return response;
|
|
7453
|
+
}
|
|
7454
|
+
/** Guard only body I/O; parsing, HTTP checks and size enforcement stay with the adapter. */
|
|
7455
|
+
function readResponseBody(response, read) {
|
|
7456
|
+
const context = responseContexts.get(response);
|
|
7457
|
+
return context === void 0 ? read() : withTransportDiagnostics(context, read);
|
|
7458
|
+
}
|
|
7459
|
+
//#endregion
|
|
7385
7460
|
//#region lib/types/dashscope.js
|
|
7386
7461
|
async function generateDashScopeImage(options) {
|
|
7387
7462
|
assertQwenImageModel(options.model);
|
|
@@ -7435,7 +7510,7 @@ function toDataUrl$3(image) {
|
|
|
7435
7510
|
}
|
|
7436
7511
|
async function requestQwenImage(options) {
|
|
7437
7512
|
const base = options.endpoint.replace(/\/+$/, "");
|
|
7438
|
-
const response = await fetch(`${base}/services/aigc/multimodal-generation/generation`, {
|
|
7513
|
+
const response = await guardedFetch(`DashScope image ${options.operation}`, options.signal, () => fetch(`${base}/services/aigc/multimodal-generation/generation`, {
|
|
7439
7514
|
method: "POST",
|
|
7440
7515
|
...options.signal ? { signal: options.signal } : {},
|
|
7441
7516
|
headers: {
|
|
@@ -7443,12 +7518,12 @@ async function requestQwenImage(options) {
|
|
|
7443
7518
|
authorization: `Bearer ${options.apiKey}`
|
|
7444
7519
|
},
|
|
7445
7520
|
body: JSON.stringify(options.requestBody)
|
|
7446
|
-
});
|
|
7521
|
+
}), options.apiKey);
|
|
7447
7522
|
if (!response.ok) {
|
|
7448
|
-
const errorText = redactSecrets(await response.text(), options.apiKey);
|
|
7523
|
+
const errorText = redactSecrets(await readResponseBody(response, () => response.text()), options.apiKey);
|
|
7449
7524
|
throw new Error(`DashScope image ${options.operation} failed (${String(response.status)}): ${errorText}`);
|
|
7450
7525
|
}
|
|
7451
|
-
const payload = await response.
|
|
7526
|
+
const payload = JSON.parse(await readResponseBody(response, () => response.text()));
|
|
7452
7527
|
const imageUrl = extractImageUrl(payload);
|
|
7453
7528
|
if (imageUrl === void 0) throw new Error(`DashScope image ${options.operation} returned no image URL: ${redactSecrets(payload.message ?? JSON.stringify(payload), options.apiKey)}`);
|
|
7454
7529
|
return downloadImageBlob(imageUrl, options);
|
|
@@ -7463,9 +7538,9 @@ function extractImageUrl(response) {
|
|
|
7463
7538
|
}
|
|
7464
7539
|
}
|
|
7465
7540
|
async function downloadImageBlob(imageUrl, options) {
|
|
7466
|
-
const imageResponse = await fetch(imageUrl, { ...options.signal ? { signal: options.signal } : {} });
|
|
7541
|
+
const imageResponse = await guardedFetch("DashScope image download", options.signal, () => fetch(imageUrl, { ...options.signal ? { signal: options.signal } : {} }), options.apiKey);
|
|
7467
7542
|
if (!imageResponse.ok) throw new Error(`Failed to fetch DashScope image from URL (${String(imageResponse.status)})`);
|
|
7468
|
-
const buffer = await imageResponse.arrayBuffer();
|
|
7543
|
+
const buffer = await readResponseBody(imageResponse, () => imageResponse.arrayBuffer());
|
|
7469
7544
|
if (buffer.byteLength > options.maxBytes) throw new Error(`DashScope generated image (${String(buffer.byteLength)} bytes) exceeds the ${String(options.maxBytes)} byte limit`);
|
|
7470
7545
|
const data = new Uint8Array(buffer);
|
|
7471
7546
|
const mediaType = detectImageMediaType(data) ?? imageMediaType$3(imageResponse.headers.get("content-type"));
|
|
@@ -7520,7 +7595,7 @@ function editGoogleImage(input) {
|
|
|
7520
7595
|
/** Shared Google request, response parsing, decoding, and size enforcement. */
|
|
7521
7596
|
async function requestGoogleImage(input) {
|
|
7522
7597
|
const label = `Google image ${input.operation}`;
|
|
7523
|
-
const response = await fetch(input.endpoint, {
|
|
7598
|
+
const response = await guardedFetch(`${label} request`, input.signal, () => fetch(input.endpoint, {
|
|
7524
7599
|
method: "POST",
|
|
7525
7600
|
redirect: "error",
|
|
7526
7601
|
signal: input.signal,
|
|
@@ -7538,7 +7613,7 @@ async function requestGoogleImage(input) {
|
|
|
7538
7613
|
image_size: input.imageSize
|
|
7539
7614
|
}
|
|
7540
7615
|
})
|
|
7541
|
-
});
|
|
7616
|
+
}), input.apiKey);
|
|
7542
7617
|
const text = await readBoundedText$2(response, Math.ceil(input.maxBytes * 1.4) + ERROR_LIMIT$2, label);
|
|
7543
7618
|
if (!response.ok) throw new Error(`${label} failed (${response.status}): ${redactSecrets(text, input.apiKey).slice(0, ERROR_LIMIT$2)}`);
|
|
7544
7619
|
let payload;
|
|
@@ -7601,7 +7676,7 @@ async function readBoundedText$2(response, maxBytes, label) {
|
|
|
7601
7676
|
let bytes = 0;
|
|
7602
7677
|
try {
|
|
7603
7678
|
for (;;) {
|
|
7604
|
-
const next = await reader.read();
|
|
7679
|
+
const next = await readResponseBody(response, () => reader.read());
|
|
7605
7680
|
if (next.done) break;
|
|
7606
7681
|
bytes += next.value.byteLength;
|
|
7607
7682
|
if (bytes > maxBytes) throw new Error(`${label} response exceeded the ${String(maxBytes)} byte limit`);
|
|
@@ -8101,7 +8176,7 @@ function jsonError$4(res, status, code) {
|
|
|
8101
8176
|
//#region lib/types/openai-compatible.js
|
|
8102
8177
|
const ERROR_LIMIT$1 = 4096;
|
|
8103
8178
|
async function generateOpenAICompatibleImage(input) {
|
|
8104
|
-
return parseImageResponse(await fetch(imageEndpoint$1(input.baseURL, "generations"), {
|
|
8179
|
+
return parseImageResponse(await guardedFetch(`${input.provider} image request`, input.signal, () => fetch(imageEndpoint$1(input.baseURL, "generations"), {
|
|
8105
8180
|
method: "POST",
|
|
8106
8181
|
redirect: "error",
|
|
8107
8182
|
signal: input.signal,
|
|
@@ -8120,10 +8195,10 @@ async function generateOpenAICompatibleImage(input) {
|
|
|
8120
8195
|
} : {},
|
|
8121
8196
|
...input.extraBody ?? {}
|
|
8122
8197
|
})
|
|
8123
|
-
}), input.provider, input);
|
|
8198
|
+
}), input.apiKey), input.provider, input);
|
|
8124
8199
|
}
|
|
8125
8200
|
async function editOpenAICompatibleImage(input) {
|
|
8126
|
-
if (input.editFormat === "qwenJson") return parseImageResponse(await fetch(imageEndpoint$1(input.baseURL, "generations"), {
|
|
8201
|
+
if (input.editFormat === "qwenJson") return parseImageResponse(await guardedFetch("openai-compat image edit request", input.signal, () => fetch(imageEndpoint$1(input.baseURL, "generations"), {
|
|
8127
8202
|
method: "POST",
|
|
8128
8203
|
redirect: "error",
|
|
8129
8204
|
signal: input.signal,
|
|
@@ -8140,13 +8215,13 @@ async function editOpenAICompatibleImage(input) {
|
|
|
8140
8215
|
...input.editExtra ?? {},
|
|
8141
8216
|
...input.extraBody ?? {}
|
|
8142
8217
|
})
|
|
8143
|
-
}), "openai-compat", input);
|
|
8218
|
+
}), input.apiKey), "openai-compat", input);
|
|
8144
8219
|
if (input.editFormat === "xaiJson") {
|
|
8145
8220
|
const references = input.sourceImages.map((image) => ({
|
|
8146
8221
|
type: "image_url",
|
|
8147
8222
|
url: toDataUrl$2(image)
|
|
8148
8223
|
}));
|
|
8149
|
-
return parseImageResponse(await fetch(imageEndpoint$1(input.baseURL, "edits"), {
|
|
8224
|
+
return parseImageResponse(await guardedFetch("xai image edit request", input.signal, () => fetch(imageEndpoint$1(input.baseURL, "edits"), {
|
|
8150
8225
|
method: "POST",
|
|
8151
8226
|
redirect: "error",
|
|
8152
8227
|
signal: input.signal,
|
|
@@ -8161,7 +8236,7 @@ async function editOpenAICompatibleImage(input) {
|
|
|
8161
8236
|
...input.quality === void 0 ? {} : { quality: input.quality },
|
|
8162
8237
|
...input.extraBody ?? {}
|
|
8163
8238
|
})
|
|
8164
|
-
}), "xai", input);
|
|
8239
|
+
}), input.apiKey), "xai", input);
|
|
8165
8240
|
}
|
|
8166
8241
|
if (input.editFormat === "jsonImageUrlArray") {
|
|
8167
8242
|
const body = {
|
|
@@ -8175,7 +8250,7 @@ async function editOpenAICompatibleImage(input) {
|
|
|
8175
8250
|
...input.editExtra ?? {},
|
|
8176
8251
|
...input.extraBody ?? {}
|
|
8177
8252
|
};
|
|
8178
|
-
return parseImageResponse(await fetch(imageEndpoint$1(input.baseURL, "edits"), {
|
|
8253
|
+
return parseImageResponse(await guardedFetch("openai image edit request", input.signal, () => fetch(imageEndpoint$1(input.baseURL, "edits"), {
|
|
8179
8254
|
method: "POST",
|
|
8180
8255
|
redirect: "error",
|
|
8181
8256
|
signal: input.signal,
|
|
@@ -8184,7 +8259,7 @@ async function editOpenAICompatibleImage(input) {
|
|
|
8184
8259
|
"content-type": "application/json"
|
|
8185
8260
|
},
|
|
8186
8261
|
body: JSON.stringify(body)
|
|
8187
|
-
}), "openai", input);
|
|
8262
|
+
}), input.apiKey), "openai", input);
|
|
8188
8263
|
}
|
|
8189
8264
|
if (input.editFormat === "formReferenceImages") {
|
|
8190
8265
|
const form = new FormData();
|
|
@@ -8195,13 +8270,13 @@ async function editOpenAICompatibleImage(input) {
|
|
|
8195
8270
|
for (const [key, value] of Object.entries(input.extraBody ?? {})) form.append(key, String(value));
|
|
8196
8271
|
form.append("response_format", "b64_json");
|
|
8197
8272
|
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"), {
|
|
8273
|
+
return parseImageResponse(await guardedFetch("openai image edit request", input.signal, () => fetch(imageEndpoint$1(input.baseURL, "edits"), {
|
|
8199
8274
|
method: "POST",
|
|
8200
8275
|
redirect: "error",
|
|
8201
8276
|
signal: input.signal,
|
|
8202
8277
|
headers: { authorization: `Bearer ${input.apiKey}` },
|
|
8203
8278
|
body: form
|
|
8204
|
-
}), "openai", input);
|
|
8279
|
+
}), input.apiKey), "openai", input);
|
|
8205
8280
|
}
|
|
8206
8281
|
const form = new FormData();
|
|
8207
8282
|
const imageField = input.sourceImages.length > 1 ? "image[]" : "image";
|
|
@@ -8216,13 +8291,13 @@ async function editOpenAICompatibleImage(input) {
|
|
|
8216
8291
|
if (input.size !== void 0 && input.size.length > 0) form.append("size", input.size);
|
|
8217
8292
|
if (input.quality !== void 0) form.append("quality", input.quality);
|
|
8218
8293
|
for (const [key, value] of Object.entries(input.extraBody ?? {})) form.append(key, String(value));
|
|
8219
|
-
return parseImageResponse(await fetch(imageEndpoint$1(input.baseURL, "edits"), {
|
|
8294
|
+
return parseImageResponse(await guardedFetch("openai image edit request", input.signal, () => fetch(imageEndpoint$1(input.baseURL, "edits"), {
|
|
8220
8295
|
method: "POST",
|
|
8221
8296
|
redirect: "error",
|
|
8222
8297
|
signal: input.signal,
|
|
8223
8298
|
headers: { authorization: `Bearer ${input.apiKey}` },
|
|
8224
8299
|
body: form
|
|
8225
|
-
}), "openai", input);
|
|
8300
|
+
}), input.apiKey), "openai", input);
|
|
8226
8301
|
}
|
|
8227
8302
|
async function parseImageResponse(response, provider, input) {
|
|
8228
8303
|
const text = await readBoundedText$1(response, Math.ceil(input.maxBytes * 1.4) + ERROR_LIMIT$1);
|
|
@@ -8282,15 +8357,15 @@ async function downloadImage$1(url, provider, input) {
|
|
|
8282
8357
|
mediaType: detectImageMediaType(data) ?? imageMediaType$2(parsed.mediaType) ?? "image/png"
|
|
8283
8358
|
};
|
|
8284
8359
|
}
|
|
8285
|
-
let response = await fetch(url, {
|
|
8360
|
+
let response = await guardedFetch(`${provider} image download`, input.signal, () => fetch(url, {
|
|
8286
8361
|
redirect: "follow",
|
|
8287
8362
|
signal: input.signal,
|
|
8288
8363
|
...input.apiKey === void 0 ? {} : { headers: { authorization: `Bearer ${input.apiKey}` } }
|
|
8289
|
-
});
|
|
8290
|
-
if (!response.ok && input.apiKey !== void 0) response = await fetch(url, {
|
|
8364
|
+
}), input.apiKey);
|
|
8365
|
+
if (!response.ok && input.apiKey !== void 0) response = await guardedFetch(`${provider} image download`, input.signal, () => fetch(url, {
|
|
8291
8366
|
redirect: "follow",
|
|
8292
8367
|
signal: input.signal
|
|
8293
|
-
});
|
|
8368
|
+
}), input.apiKey);
|
|
8294
8369
|
if (!response.ok) throw new Error(`${provider} image download failed (${response.status})`);
|
|
8295
8370
|
const data = await readBoundedBytes$1(response, input.maxBytes);
|
|
8296
8371
|
const mediaType = detectImageMediaType(data) ?? imageMediaType$2(response.headers.get("content-type"));
|
|
@@ -8336,7 +8411,7 @@ async function readBoundedBytes$1(response, maxBytes) {
|
|
|
8336
8411
|
let bytes = 0;
|
|
8337
8412
|
try {
|
|
8338
8413
|
for (;;) {
|
|
8339
|
-
const next = await reader.read();
|
|
8414
|
+
const next = await readResponseBody(response, () => reader.read());
|
|
8340
8415
|
if (next.done) break;
|
|
8341
8416
|
bytes += next.value.byteLength;
|
|
8342
8417
|
if (bytes > maxBytes) throw new Error(`Image response exceeded the ${String(maxBytes)} byte limit`);
|
|
@@ -8358,7 +8433,7 @@ async function readBoundedBytes$1(response, maxBytes) {
|
|
|
8358
8433
|
const ERROR_LIMIT = 4096;
|
|
8359
8434
|
/** Edit one image through Ark ImageGenerations using a data-URL reference. */
|
|
8360
8435
|
async function editSeedreamImage(input) {
|
|
8361
|
-
const response = await fetch(imageEndpoint(input.baseURL), {
|
|
8436
|
+
const response = await guardedFetch("seedream image editing", input.signal, () => fetch(imageEndpoint(input.baseURL), {
|
|
8362
8437
|
method: "POST",
|
|
8363
8438
|
redirect: "error",
|
|
8364
8439
|
signal: input.signal,
|
|
@@ -8374,7 +8449,7 @@ async function editSeedreamImage(input) {
|
|
|
8374
8449
|
...arkOutputBody(input.arkOptions),
|
|
8375
8450
|
response_format: "b64_json"
|
|
8376
8451
|
})
|
|
8377
|
-
});
|
|
8452
|
+
}), input.apiKey);
|
|
8378
8453
|
const text = await readBoundedText(response, Math.ceil(input.maxBytes * 1.4) + ERROR_LIMIT);
|
|
8379
8454
|
if (!response.ok) throw new Error(`seedream image editing failed (${response.status}): ${redactSecrets(text, input.apiKey).slice(0, ERROR_LIMIT)}`);
|
|
8380
8455
|
let payload;
|
|
@@ -8423,10 +8498,10 @@ function firstImage(value) {
|
|
|
8423
8498
|
}
|
|
8424
8499
|
async function downloadImage(url, input) {
|
|
8425
8500
|
if (url === void 0) throw new Error("seedream image editing returned no image data");
|
|
8426
|
-
const response = await fetch(url, {
|
|
8501
|
+
const response = await guardedFetch("seedream image download", input.signal, () => fetch(url, {
|
|
8427
8502
|
redirect: "follow",
|
|
8428
8503
|
signal: input.signal
|
|
8429
|
-
});
|
|
8504
|
+
}), input.apiKey);
|
|
8430
8505
|
if (!response.ok) throw new Error(`seedream image download failed (${response.status})`);
|
|
8431
8506
|
const mediaType = imageMediaType$1(response.headers.get("content-type"));
|
|
8432
8507
|
if (mediaType === void 0) throw new Error("seedream image download returned unsupported content type");
|
|
@@ -8456,7 +8531,7 @@ async function readBoundedBytes(response, maxBytes) {
|
|
|
8456
8531
|
let bytes = 0;
|
|
8457
8532
|
try {
|
|
8458
8533
|
for (;;) {
|
|
8459
|
-
const next = await reader.read();
|
|
8534
|
+
const next = await readResponseBody(response, () => reader.read());
|
|
8460
8535
|
if (next.done) break;
|
|
8461
8536
|
bytes += next.value.byteLength;
|
|
8462
8537
|
if (bytes > maxBytes) throw new Error(`Image response exceeded the ${String(maxBytes)} byte limit`);
|
|
@@ -8937,20 +9012,24 @@ function getAntigravityVersion() {
|
|
|
8937
9012
|
return antigravityVersion ?? ANTIGRAVITY_VERSION_FALLBACK;
|
|
8938
9013
|
}
|
|
8939
9014
|
/** Fetch and cache the current version once; failures keep the fallback. */
|
|
8940
|
-
async function initAntigravityVersion() {
|
|
9015
|
+
async function initAntigravityVersion(signal) {
|
|
9016
|
+
signal?.throwIfAborted();
|
|
8941
9017
|
if (antigravityVersion !== void 0) return;
|
|
8942
|
-
const parsed = await fetchVersionText(VERSION_URL) ?? await fetchVersionText(CHANGELOG_URL, CHANGELOG_SCAN_CHARS);
|
|
9018
|
+
const parsed = await fetchVersionText(VERSION_URL, void 0, signal) ?? await fetchVersionText(CHANGELOG_URL, CHANGELOG_SCAN_CHARS, signal);
|
|
9019
|
+
signal?.throwIfAborted();
|
|
8943
9020
|
if (parsed !== void 0) antigravityVersion = parsed;
|
|
8944
9021
|
}
|
|
8945
|
-
async function fetchVersionText(url, maxChars) {
|
|
9022
|
+
async function fetchVersionText(url, maxChars, signal) {
|
|
8946
9023
|
try {
|
|
8947
|
-
const
|
|
9024
|
+
const timeout = AbortSignal.timeout(VERSION_FETCH_TIMEOUT_MS);
|
|
9025
|
+
const response = await fetch(url, { signal: signal === void 0 ? timeout : AbortSignal.any([signal, timeout]) });
|
|
8948
9026
|
if (!response.ok) return void 0;
|
|
8949
9027
|
let text = await response.text();
|
|
8950
9028
|
if (maxChars !== void 0) text = text.slice(0, maxChars);
|
|
8951
9029
|
const match = text.match(VERSION_REGEX);
|
|
8952
9030
|
return match === null ? void 0 : match[0];
|
|
8953
|
-
} catch {
|
|
9031
|
+
} catch (error) {
|
|
9032
|
+
if (signal?.aborted === true) throw error;
|
|
8954
9033
|
return;
|
|
8955
9034
|
}
|
|
8956
9035
|
}
|
|
@@ -9016,16 +9095,19 @@ function antigravityAuthorizeUrl(cfg, pkce) {
|
|
|
9016
9095
|
});
|
|
9017
9096
|
}
|
|
9018
9097
|
/**
|
|
9019
|
-
*
|
|
9020
|
-
*
|
|
9098
|
+
* Preserve the SDK discovery identity first. A no-project UNSUPPORTED_CLIENT
|
|
9099
|
+
* response can be retried with the same Antigravity identity as generation.
|
|
9021
9100
|
*/
|
|
9022
|
-
function loadHeaders(projectId) {
|
|
9101
|
+
function loadHeaders(projectId, identity) {
|
|
9023
9102
|
return {
|
|
9024
|
-
"User-Agent": "google-api-nodejs-client/9.15.1",
|
|
9103
|
+
"User-Agent": identity === "antigravity" ? antigravityUserAgent() : "google-api-nodejs-client/9.15.1",
|
|
9025
9104
|
"X-Goog-Api-Client": "google-cloud-sdk vscode_cloudshelleditor/0.1",
|
|
9026
9105
|
"Client-Metadata": JSON.stringify(clientMetadata(projectId))
|
|
9027
9106
|
};
|
|
9028
9107
|
}
|
|
9108
|
+
function antigravityUserAgent() {
|
|
9109
|
+
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`;
|
|
9110
|
+
}
|
|
9029
9111
|
/**
|
|
9030
9112
|
* Headers for content requests (image generation): the full Electron
|
|
9031
9113
|
* browser User-Agent with the Antigravity client version, matching the
|
|
@@ -9034,7 +9116,7 @@ function loadHeaders(projectId) {
|
|
|
9034
9116
|
*/
|
|
9035
9117
|
function contentHeaders(projectId) {
|
|
9036
9118
|
return {
|
|
9037
|
-
"User-Agent":
|
|
9119
|
+
"User-Agent": antigravityUserAgent(),
|
|
9038
9120
|
"X-Goog-Api-Client": "google-cloud-sdk vscode_cloudshelleditor/0.1",
|
|
9039
9121
|
"Client-Metadata": JSON.stringify(clientMetadata(projectId))
|
|
9040
9122
|
};
|
|
@@ -9100,40 +9182,50 @@ async function antigravityResolveProjectContext(blob, signal) {
|
|
|
9100
9182
|
signal?.throwIfAborted();
|
|
9101
9183
|
const metadata = clientMetadata("");
|
|
9102
9184
|
const diagnostics = [];
|
|
9103
|
-
|
|
9104
|
-
|
|
9105
|
-
|
|
9106
|
-
|
|
9107
|
-
|
|
9108
|
-
|
|
9109
|
-
|
|
9110
|
-
|
|
9111
|
-
|
|
9112
|
-
|
|
9113
|
-
|
|
9114
|
-
|
|
9115
|
-
|
|
9116
|
-
|
|
9117
|
-
|
|
9118
|
-
|
|
9119
|
-
|
|
9120
|
-
|
|
9121
|
-
|
|
9122
|
-
|
|
9123
|
-
|
|
9185
|
+
const identityRetryEndpoints = [];
|
|
9186
|
+
for (const identity of ["sdk", "antigravity"]) {
|
|
9187
|
+
const endpoints = identity === "sdk" ? LOAD_ENDPOINTS : identityRetryEndpoints;
|
|
9188
|
+
if (identity === "antigravity") {
|
|
9189
|
+
if (endpoints.length === 0) break;
|
|
9190
|
+
await initAntigravityVersion(signal);
|
|
9191
|
+
}
|
|
9192
|
+
for (const base of endpoints) {
|
|
9193
|
+
signal?.throwIfAborted();
|
|
9194
|
+
const label = `loadCodeAssist ${new URL(base).hostname}${identity === "antigravity" ? ` [Antigravity/${getAntigravityVersion()}]` : ""}`;
|
|
9195
|
+
try {
|
|
9196
|
+
const response = await fetch(`${base}/v1internal:loadCodeAssist`, {
|
|
9197
|
+
method: "POST",
|
|
9198
|
+
headers: {
|
|
9199
|
+
"Content-Type": "application/json",
|
|
9200
|
+
Authorization: `Bearer ${blob.accessToken}`,
|
|
9201
|
+
...loadHeaders("", identity)
|
|
9202
|
+
},
|
|
9203
|
+
body: JSON.stringify({ metadata }),
|
|
9204
|
+
signal: signal === void 0 ? AbortSignal.timeout(25e3) : AbortSignal.any([signal, AbortSignal.timeout(25e3)])
|
|
9205
|
+
});
|
|
9206
|
+
if (!response.ok) {
|
|
9207
|
+
diagnostics.push(`${label}: ${httpDiagnostic(response.status, await response.text().catch(() => ""), blob)}`);
|
|
9208
|
+
continue;
|
|
9209
|
+
}
|
|
9210
|
+
const payload = recordOf(await response.json().catch(() => null));
|
|
9211
|
+
if (payload === void 0) {
|
|
9212
|
+
diagnostics.push(`${label}: HTTP ${String(response.status)}, invalid response`);
|
|
9213
|
+
continue;
|
|
9214
|
+
}
|
|
9215
|
+
const project = payload.cloudaicompanionProject;
|
|
9216
|
+
const projectRecord = recordOf(project);
|
|
9217
|
+
const id = typeof project === "string" ? project : typeof projectRecord?.id === "string" ? projectRecord.id : "";
|
|
9218
|
+
if (id.trim().length > 0) return {
|
|
9219
|
+
projectId: id.trim(),
|
|
9220
|
+
source: "account",
|
|
9221
|
+
diagnostics
|
|
9222
|
+
};
|
|
9223
|
+
diagnostics.push(`${label}: HTTP ${String(response.status)}, ${missingProjectDiagnostic(payload, blob)}`);
|
|
9224
|
+
if (identity === "sdk" && Array.isArray(payload.ineligibleTiers) && payload.ineligibleTiers.some((tier) => recordOf(tier)?.reasonCode === "UNSUPPORTED_CLIENT")) identityRetryEndpoints.push(base);
|
|
9225
|
+
} catch (error) {
|
|
9226
|
+
if (signal?.aborted === true) throw error;
|
|
9227
|
+
diagnostics.push(`${label}: ${diagnosticText(error instanceof Error ? error.message : "request failed", blob)}`);
|
|
9124
9228
|
}
|
|
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
9229
|
}
|
|
9138
9230
|
}
|
|
9139
9231
|
return {
|
|
@@ -9250,7 +9342,7 @@ function antigravityEnvelope(projectId, request) {
|
|
|
9250
9342
|
*/
|
|
9251
9343
|
async function antigravityGenerateImage(options) {
|
|
9252
9344
|
options.signal?.throwIfAborted();
|
|
9253
|
-
await initAntigravityVersion();
|
|
9345
|
+
await initAntigravityVersion(options.signal);
|
|
9254
9346
|
const inner = antigravityImageBody({
|
|
9255
9347
|
prompt: options.prompt,
|
|
9256
9348
|
...options.aspectRatio !== void 0 ? { aspectRatio: options.aspectRatio } : {},
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** One line describing why a provider fetch failed: the abort reason when the caller cancelled, the cause chain otherwise. */
|
|
2
|
+
export declare function describeTransportFailure(error: unknown, signal?: AbortSignal): string;
|
|
3
|
+
/**
|
|
4
|
+
* Await one provider fetch and rethrow transport failures as
|
|
5
|
+
* `${label} failed after 12.3s: ${cause chain}`. The original error stays
|
|
6
|
+
* reachable on `cause` for host-side error rendering.
|
|
7
|
+
*/
|
|
8
|
+
export declare function guardedFetch(label: string, signal: AbortSignal | undefined, run: () => Promise<Response>, ...secrets: Array<string | undefined>): Promise<Response>;
|
|
9
|
+
/** Guard only body I/O; parsing, HTTP checks and size enforcement stay with the adapter. */
|
|
10
|
+
export declare function readResponseBody<T>(response: Response, read: () => Promise<T>): Promise<T>;
|
|
@@ -17,7 +17,7 @@ export declare function getAntigravityVersion(): string;
|
|
|
17
17
|
/** Test seam: pin the version so unit tests never depend on the network. */
|
|
18
18
|
export declare function setAntigravityVersionForTest(version: string | undefined): void;
|
|
19
19
|
/** Fetch and cache the current version once; failures keep the fallback. */
|
|
20
|
-
export declare function initAntigravityVersion(): Promise<void>;
|
|
20
|
+
export declare function initAntigravityVersion(signal?: AbortSignal): Promise<void>;
|
|
21
21
|
export interface AntigravityConfig {
|
|
22
22
|
clientId: string;
|
|
23
23
|
clientSecret: string;
|
package/package.json
CHANGED