Add localhost->127.0.0.1 fallback for transcription fetch

This commit is contained in:
William Valentin
2026-02-22 20:26:38 -08:00
parent 58eee60023
commit 3d59e5ea9d
6 changed files with 111 additions and 8 deletions
+25 -3
View File
@@ -45,22 +45,42 @@ function isTransientNetworkError(error: unknown): boolean {
|| message.includes('ehostunreach');
}
function buildEndpointCandidates(endpoint: string): string[] {
try {
const parsed = new URL(endpoint);
if (parsed.hostname !== 'localhost') {
return [endpoint];
}
const ipv4Endpoint = new URL(endpoint);
ipv4Endpoint.hostname = '127.0.0.1';
return [endpoint, ipv4Endpoint.toString()];
} catch {
return [endpoint];
}
}
async function fetchWithRetry(endpoint: string, init: RequestInit): Promise<Response> {
const endpointCandidates = buildEndpointCandidates(endpoint);
let lastErrorMessage = 'Unknown network error';
let lastEndpoint = endpoint;
for (let attempt = 1; attempt <= TRANSCRIPTION_FETCH_MAX_ATTEMPTS; attempt += 1) {
const endpointForAttempt = endpointCandidates[(attempt - 1) % endpointCandidates.length];
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), TRANSCRIPTION_FETCH_TIMEOUT_MS);
try {
return await fetch(endpoint, { ...init, signal: controller.signal });
return await fetch(endpointForAttempt, { ...init, signal: controller.signal });
} catch (error) {
const timedOut = error instanceof Error && error.name === 'AbortError';
const normalizedMessage = timedOut
? `request timed out after ${TRANSCRIPTION_FETCH_TIMEOUT_MS}ms`
: (error instanceof Error ? error.message : String(error));
lastErrorMessage = normalizedMessage;
lastEndpoint = endpointForAttempt;
const retriable = timedOut || isTransientNetworkError(error);
const exhausted = attempt >= TRANSCRIPTION_FETCH_MAX_ATTEMPTS;
if (!retriable || exhausted) {
throw new Error(
`Transcription request to ${endpoint} failed after ${attempt} attempt(s): ${normalizedMessage}`,
`Transcription service connectivity failure at ${lastEndpoint} after ${attempt} attempt(s): ${normalizedMessage}. This indicates endpoint/network availability, not missing audio bytes.`,
);
}
await sleep(TRANSCRIPTION_FETCH_BASE_DELAY_MS * (2 ** (attempt - 1)));
@@ -69,7 +89,9 @@ async function fetchWithRetry(endpoint: string, init: RequestInit): Promise<Resp
}
}
throw new Error(`Transcription request to ${endpoint} failed after retries`);
throw new Error(
`Transcription service connectivity failure at ${lastEndpoint} after retries: ${lastErrorMessage}. This indicates endpoint/network availability, not missing audio bytes.`,
);
}
function validateUrl(url: string): { valid: boolean; error?: string } {