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";
|
||||
|
||||
/** 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). */
|
||||
const COST_ESTIMATE_PER_IMAGE_USD = 0.0015;
|
||||
|
||||
@@ -160,7 +215,7 @@ export class GeminiAamosClient implements AamosClient {
|
||||
timeoutMs: config.timeoutMs ?? 60_000,
|
||||
dailyBudgetUsd: config.dailyBudgetUsd ?? 0,
|
||||
promptVersion: config.promptVersion ?? "gemini-fridge-v1",
|
||||
fetchImpl: config.fetchImpl ?? fetch,
|
||||
fetchImpl: config.fetchImpl ?? customNodeFetch,
|
||||
budgetStore: config.budgetStore,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user