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/README.en.md +452 -442
- package/README.md +452 -442
- package/lib/client.js +286 -30
- package/lib/index.js +251 -98
- package/lib/types/client/index.d.ts +1 -0
- package/lib/types/client/studio-style.d.ts +1 -1
- package/lib/types/client/tl/conversation-landings.d.ts +16 -1
- package/lib/types/client/tl/gallery-canvas-button.d.ts +10 -0
- package/lib/types/client/tl/gallery-image-transfer.d.ts +12 -0
- package/lib/types/client/tl/studio-tl-canvas.d.ts +3 -0
- package/lib/types/client/tl/tl-canvas-bridge.d.ts +6 -0
- package/lib/types/config.d.ts +3 -0
- package/lib/types/fetch-diagnostics.d.ts +10 -0
- package/lib/types/google.d.ts +4 -1
- package/lib/types/subscription/vendors/antigravity.d.ts +1 -1
- package/package.json +139 -139
- package/skills/install-dsh-image-gen/SKILL.md +22 -22
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.
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
*
|
|
9020
|
-
*
|
|
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":
|
|
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
|
-
|
|
9104
|
-
|
|
9105
|
-
|
|
9106
|
-
|
|
9107
|
-
|
|
9108
|
-
|
|
9109
|
-
|
|
9110
|
-
|
|
9111
|
-
|
|
9112
|
-
|
|
9113
|
-
|
|
9114
|
-
|
|
9115
|
-
|
|
9116
|
-
|
|
9117
|
-
|
|
9118
|
-
|
|
9119
|
-
|
|
9120
|
-
|
|
9121
|
-
|
|
9122
|
-
|
|
9123
|
-
|
|
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
|
-
|
|
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,
|