fix(ai-contracts): customNodeFetch för att kringgå IPv6-häng i WSL
Node.js fetch() hänger på POST till Gemini via IPv6 i WSL. Ersätter default fetch med customNodeFetch som använder node:https och därmed kör över IPv4. Signifikant hastighetsförbättring (~1 sek vs timeout). Relaterat till scale-batch.ts i Fas A Steg 2.
This commit is contained in:
@@ -23,6 +23,61 @@ import type { LocaleContext } from "@app/shared-types";
|
|||||||
|
|
||||||
const GEMINI_API_BASE = "https://generativelanguage.googleapis.com/v1beta";
|
const GEMINI_API_BASE = "https://generativelanguage.googleapis.com/v1beta";
|
||||||
|
|
||||||
|
/** Custom fetch using Node https module — works around IPv6 hangs on WSL. */
|
||||||
|
function customNodeFetch(input: string | URL | Request, init?: RequestInit): Promise<Response> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
|
||||||
|
const u = new URL(url);
|
||||||
|
const isHttps = u.protocol === "https:";
|
||||||
|
const mod = isHttps ? import("node:https") : import("node:http");
|
||||||
|
mod.then((httpMod) => {
|
||||||
|
const postData = init?.body ? String(init.body) : undefined;
|
||||||
|
const headers: Record<string, string> = {};
|
||||||
|
if (init?.headers) {
|
||||||
|
if (init.headers instanceof Headers) {
|
||||||
|
init.headers.forEach((v, k) => { headers[k] = v; });
|
||||||
|
} else if (Array.isArray(init.headers)) {
|
||||||
|
(init.headers as [string, string][]).forEach(([k, v]) => { if (k != null) headers[k] = v; });
|
||||||
|
} else {
|
||||||
|
Object.assign(headers, init.headers as Record<string, string>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (postData && !headers["content-length"]) {
|
||||||
|
headers["content-length"] = String(Buffer.byteLength(postData));
|
||||||
|
}
|
||||||
|
|
||||||
|
const req = httpMod.request(
|
||||||
|
{
|
||||||
|
hostname: u.hostname,
|
||||||
|
port: u.port || (isHttps ? 443 : 80),
|
||||||
|
path: u.pathname + u.search,
|
||||||
|
method: init?.method || "GET",
|
||||||
|
headers,
|
||||||
|
timeout: 120_000,
|
||||||
|
},
|
||||||
|
(res) => {
|
||||||
|
let body = "";
|
||||||
|
res.setEncoding("utf8");
|
||||||
|
res.on("data", (chunk) => { body += chunk; });
|
||||||
|
res.on("end", () => {
|
||||||
|
resolve(
|
||||||
|
new Response(body, {
|
||||||
|
status: res.statusCode ?? 200,
|
||||||
|
statusText: res.statusMessage ?? "OK",
|
||||||
|
headers: new Headers(Object.entries(res.headers).map(([k, v]) => [k, String(v)])),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
req.on("error", (err) => reject(err));
|
||||||
|
req.on("timeout", () => { req.destroy(); reject(new Error("Request timeout")); });
|
||||||
|
if (postData) req.write(postData);
|
||||||
|
req.end();
|
||||||
|
}).catch(reject);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/** Estimated max cost per image call in USD (pessimistic). */
|
/** Estimated max cost per image call in USD (pessimistic). */
|
||||||
const COST_ESTIMATE_PER_IMAGE_USD = 0.0015;
|
const COST_ESTIMATE_PER_IMAGE_USD = 0.0015;
|
||||||
|
|
||||||
@@ -160,7 +215,7 @@ export class GeminiAamosClient implements AamosClient {
|
|||||||
timeoutMs: config.timeoutMs ?? 60_000,
|
timeoutMs: config.timeoutMs ?? 60_000,
|
||||||
dailyBudgetUsd: config.dailyBudgetUsd ?? 0,
|
dailyBudgetUsd: config.dailyBudgetUsd ?? 0,
|
||||||
promptVersion: config.promptVersion ?? "gemini-fridge-v1",
|
promptVersion: config.promptVersion ?? "gemini-fridge-v1",
|
||||||
fetchImpl: config.fetchImpl ?? fetch,
|
fetchImpl: config.fetchImpl ?? customNodeFetch,
|
||||||
budgetStore: config.budgetStore,
|
budgetStore: config.budgetStore,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user