fix: recover Robot configuration loading
This commit is contained in:
@@ -172,6 +172,42 @@ async function readBoundedResponse(response: Response): Promise<unknown> {
|
||||
}
|
||||
}
|
||||
|
||||
type TimedResponse = {
|
||||
response: Response;
|
||||
signal: AbortSignal;
|
||||
finish: () => void;
|
||||
};
|
||||
|
||||
async function readTimedResponse(call: TimedResponse): Promise<unknown> {
|
||||
try {
|
||||
return await readBoundedResponse(call.response);
|
||||
} catch (error) {
|
||||
if (call.signal.aborted) {
|
||||
throw new SafeRouteError(504, 'AI_HARDWARE_TIMEOUT', 'AI hardware service timed out', true);
|
||||
}
|
||||
throw error;
|
||||
} finally {
|
||||
call.finish();
|
||||
}
|
||||
}
|
||||
|
||||
async function readSafeErrorPayload(call: TimedResponse): Promise<unknown> {
|
||||
try {
|
||||
return await readTimedResponse(call);
|
||||
} catch (error) {
|
||||
if (error instanceof SafeRouteError && error.code === 'AI_HARDWARE_TIMEOUT') throw error;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function cancelTimedResponse(call: TimedResponse): Promise<void> {
|
||||
try {
|
||||
await call.response.body?.cancel().catch(() => undefined);
|
||||
} finally {
|
||||
call.finish();
|
||||
}
|
||||
}
|
||||
|
||||
function ensureExactKeys(body: Record<string, unknown>, allowed: Set<string>): void {
|
||||
if (Object.keys(body).some((key) => !allowed.has(key))) {
|
||||
throw new SafeRouteError(400, 'AI_HARDWARE_INVALID_REQUEST', 'Invalid AI hardware request');
|
||||
@@ -431,11 +467,17 @@ export function createAiHardwareRouteHandler(dependencies: AiHardwareRouteDepend
|
||||
return true;
|
||||
}
|
||||
const idempotencyKey = operationId ? `makelore-${operationId}` : undefined;
|
||||
const call = async (accessToken: string): Promise<Response> => {
|
||||
const call = async (accessToken: string): Promise<TimedResponse> => {
|
||||
const controller = new AbortController();
|
||||
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
||||
let finished = false;
|
||||
const finish = () => {
|
||||
if (finished) return;
|
||||
finished = true;
|
||||
clearTimeout(timer);
|
||||
};
|
||||
try {
|
||||
return await fetchImpl(`${apiBaseUrl}${upstreamPath}`, {
|
||||
const response = await fetchImpl(`${apiBaseUrl}${upstreamPath}`, {
|
||||
method,
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
@@ -448,24 +490,26 @@ export function createAiHardwareRouteHandler(dependencies: AiHardwareRouteDepend
|
||||
signal: controller.signal,
|
||||
redirect: 'manual',
|
||||
});
|
||||
return { response, signal: controller.signal, finish };
|
||||
} catch (error) {
|
||||
finish();
|
||||
if (controller.signal.aborted) {
|
||||
throw new SafeRouteError(504, 'AI_HARDWARE_TIMEOUT', 'AI hardware service timed out', true);
|
||||
}
|
||||
throw error;
|
||||
} finally {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
};
|
||||
|
||||
let currentToken = token;
|
||||
let response = await call(currentToken);
|
||||
let activeCall = await call(currentToken);
|
||||
let response = activeCall.response;
|
||||
if (response.status === 401) {
|
||||
await response.body?.cancel().catch(() => undefined);
|
||||
await cancelTimedResponse(activeCall);
|
||||
const refreshed = await getAccessToken({ fetchImpl, forceRefresh: true });
|
||||
if (refreshed) {
|
||||
currentToken = refreshed;
|
||||
response = await call(currentToken);
|
||||
activeCall = await call(currentToken);
|
||||
response = activeCall.response;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -473,11 +517,12 @@ export function createAiHardwareRouteHandler(dependencies: AiHardwareRouteDepend
|
||||
if (response.status === 409 && operationId) {
|
||||
const retryDelay = retryAfterSeconds(response, MAX_RETRY_AFTER_SECONDS);
|
||||
if (retryDelay !== undefined) {
|
||||
cachedErrorPayload = isJsonContentType(response) ? await readBoundedResponse(response).catch(() => null) : null;
|
||||
cachedErrorPayload = isJsonContentType(response) ? await readSafeErrorPayload(activeCall) : null;
|
||||
const safe = safeUpstreamError(cachedErrorPayload, response.status);
|
||||
if (safe.code === 'ai_hardware_operation_in_progress') {
|
||||
await new Promise<void>((resolve) => setTimeout(resolve, retryDelay * 1000));
|
||||
response = await call(currentToken);
|
||||
activeCall = await call(currentToken);
|
||||
response = activeCall.response;
|
||||
cachedErrorPayload = undefined;
|
||||
}
|
||||
}
|
||||
@@ -486,14 +531,14 @@ export function createAiHardwareRouteHandler(dependencies: AiHardwareRouteDepend
|
||||
if (!response.ok) {
|
||||
const status = response.status;
|
||||
if (status === 404 && upstreamPath === UPSTREAM_ROOT) {
|
||||
await response.body?.cancel().catch(() => undefined);
|
||||
await cancelTimedResponse(activeCall);
|
||||
sendFailure(res, 200, 404, 'AI_HARDWARE_DISABLED', 'AI hardware module is not enabled', false);
|
||||
return true;
|
||||
}
|
||||
const retryAfter = retryAfterSeconds(response);
|
||||
const payload = cachedErrorPayload ?? (isJsonContentType(response)
|
||||
? await readBoundedResponse(response).catch(() => null)
|
||||
: (await response.body?.cancel().catch(() => undefined), null));
|
||||
? await readSafeErrorPayload(activeCall)
|
||||
: (await cancelTimedResponse(activeCall), null));
|
||||
const safe = safeUpstreamError(payload, status);
|
||||
sendFailure(
|
||||
res,
|
||||
@@ -509,11 +554,17 @@ export function createAiHardwareRouteHandler(dependencies: AiHardwareRouteDepend
|
||||
}
|
||||
|
||||
if (response.status !== expectedStatus || !isJsonContentType(response)) {
|
||||
await response.body?.cancel().catch(() => undefined);
|
||||
await cancelTimedResponse(activeCall);
|
||||
throw new SafeRouteError(502, 'AI_HARDWARE_INVALID_RESPONSE', 'AI hardware service returned an invalid response', true);
|
||||
}
|
||||
const revision = requireEtag ? strongRevisionFrom(response) : undefined;
|
||||
const projected = project(await readBoundedResponse(response));
|
||||
let revision: number | undefined;
|
||||
try {
|
||||
revision = requireEtag ? strongRevisionFrom(response) : undefined;
|
||||
} catch (error) {
|
||||
await cancelTimedResponse(activeCall);
|
||||
throw error;
|
||||
}
|
||||
const projected = project(await readTimedResponse(activeCall));
|
||||
if (!projected) throw new SafeRouteError(502, 'AI_HARDWARE_INVALID_RESPONSE', 'AI hardware service returned an invalid response', true);
|
||||
if (revision !== undefined) {
|
||||
const dtoRevision = 'config_revision' in projected
|
||||
|
||||
Reference in New Issue
Block a user