mirror of
https://github.com/moltbot/moltbot.git
synced 2026-05-13 23:56:07 +00:00
fix(provider): preserve retry deadlines
This commit is contained in:
@@ -90,10 +90,11 @@ async function pollBytePlusTask(params: {
|
||||
method: "GET",
|
||||
headers: params.headers,
|
||||
},
|
||||
timeoutMs: resolveProviderOperationTimeoutMs({
|
||||
deadline,
|
||||
defaultTimeoutMs: DEFAULT_TIMEOUT_MS,
|
||||
}),
|
||||
timeoutMs: () =>
|
||||
resolveProviderOperationTimeoutMs({
|
||||
deadline,
|
||||
defaultTimeoutMs: DEFAULT_TIMEOUT_MS,
|
||||
}),
|
||||
fetchFn: params.fetchFn,
|
||||
provider: "byteplus",
|
||||
requestFailedMessage: "BytePlus video status request failed",
|
||||
|
||||
@@ -47,12 +47,18 @@ const minimaxProviderHttpMocks = vi.hoisted(() => ({
|
||||
})),
|
||||
}));
|
||||
|
||||
function resolveMockProviderTimeoutMs(
|
||||
timeoutMs: FetchProviderOperationResponseParams["timeoutMs"],
|
||||
) {
|
||||
return typeof timeoutMs === "function" ? timeoutMs() : (timeoutMs ?? 60_000);
|
||||
}
|
||||
|
||||
minimaxProviderHttpMocks.fetchProviderOperationResponseMock.mockImplementation(
|
||||
async (params: FetchProviderOperationResponseParams) => {
|
||||
const response = await minimaxProviderHttpMocks.fetchWithTimeoutMock(
|
||||
params.url,
|
||||
params.init ?? {},
|
||||
params.timeoutMs ?? 60_000,
|
||||
resolveMockProviderTimeoutMs(params.timeoutMs),
|
||||
params.fetchFn,
|
||||
);
|
||||
if (params.requestFailedMessage) {
|
||||
@@ -70,7 +76,7 @@ minimaxProviderHttpMocks.fetchProviderDownloadResponseMock.mockImplementation(
|
||||
const response = await minimaxProviderHttpMocks.fetchWithTimeoutMock(
|
||||
params.url,
|
||||
params.init ?? {},
|
||||
params.timeoutMs ?? 60_000,
|
||||
resolveMockProviderTimeoutMs(params.timeoutMs),
|
||||
params.fetchFn,
|
||||
);
|
||||
await minimaxProviderHttpMocks.assertOkOrThrowHttpErrorMock(
|
||||
|
||||
@@ -179,10 +179,11 @@ async function pollMinimaxVideo(params: {
|
||||
method: "GET",
|
||||
headers: params.headers,
|
||||
},
|
||||
timeoutMs: resolveProviderOperationTimeoutMs({
|
||||
deadline,
|
||||
defaultTimeoutMs: DEFAULT_TIMEOUT_MS,
|
||||
}),
|
||||
timeoutMs: () =>
|
||||
resolveProviderOperationTimeoutMs({
|
||||
deadline,
|
||||
defaultTimeoutMs: DEFAULT_TIMEOUT_MS,
|
||||
}),
|
||||
fetchFn: params.fetchFn,
|
||||
provider: "minimax",
|
||||
requestFailedMessage: "MiniMax video status request failed",
|
||||
|
||||
@@ -216,10 +216,11 @@ async function pollRunwayTask(params: {
|
||||
method: "GET",
|
||||
headers: params.headers,
|
||||
},
|
||||
timeoutMs: resolveProviderOperationTimeoutMs({
|
||||
deadline,
|
||||
defaultTimeoutMs: DEFAULT_TIMEOUT_MS,
|
||||
}),
|
||||
timeoutMs: () =>
|
||||
resolveProviderOperationTimeoutMs({
|
||||
deadline,
|
||||
defaultTimeoutMs: DEFAULT_TIMEOUT_MS,
|
||||
}),
|
||||
fetchFn: params.fetchFn,
|
||||
provider: "runway",
|
||||
requestFailedMessage: "Runway video status request failed",
|
||||
|
||||
@@ -269,10 +269,11 @@ async function pollXaiVideo(params: {
|
||||
method: "GET",
|
||||
headers: params.headers,
|
||||
},
|
||||
timeoutMs: resolveProviderOperationTimeoutMs({
|
||||
deadline,
|
||||
defaultTimeoutMs: DEFAULT_TIMEOUT_MS,
|
||||
}),
|
||||
timeoutMs: () =>
|
||||
resolveProviderOperationTimeoutMs({
|
||||
deadline,
|
||||
defaultTimeoutMs: DEFAULT_TIMEOUT_MS,
|
||||
}),
|
||||
fetchFn: params.fetchFn,
|
||||
provider: "xai",
|
||||
requestFailedMessage: "xAI video status request failed",
|
||||
|
||||
@@ -213,6 +213,39 @@ describe("provider operation deadlines", () => {
|
||||
expect(fetchFn).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("recomputes remaining poll timeout before retry attempts", async () => {
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(1_000);
|
||||
const fetchFn = vi.fn<typeof fetch>(async () => {
|
||||
vi.setSystemTime(2_001);
|
||||
return new Response("busy", { status: 503, statusText: "Service Unavailable" });
|
||||
});
|
||||
|
||||
const result = pollProviderOperationJson<{ status?: string }>({
|
||||
url: "https://api.example.com/v1/videos/task-1",
|
||||
headers: new Headers({ authorization: "Bearer test" }),
|
||||
deadline: createProviderOperationDeadline({
|
||||
label: "video generation task task-1",
|
||||
timeoutMs: 1_000,
|
||||
}),
|
||||
defaultTimeoutMs: 5_000,
|
||||
fetchFn,
|
||||
maxAttempts: 3,
|
||||
pollIntervalMs: 1_000,
|
||||
requestFailedMessage: "status failed",
|
||||
timeoutMessage: "task timed out",
|
||||
isComplete: (payload) => payload.status === "completed",
|
||||
});
|
||||
const assertion = expect(result).rejects.toThrow(
|
||||
"video generation task task-1 timed out after 1000ms",
|
||||
);
|
||||
|
||||
await vi.advanceTimersByTimeAsync(250);
|
||||
|
||||
await assertion;
|
||||
expect(fetchFn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("retries transient generated asset downloads", async () => {
|
||||
const sleep = vi.fn(async () => undefined);
|
||||
const fetchFn = vi
|
||||
|
||||
@@ -71,6 +71,8 @@ export type ProviderOperationDeadline = {
|
||||
timeoutMs?: number;
|
||||
};
|
||||
|
||||
export type ProviderOperationTimeoutMs = number | (() => number);
|
||||
|
||||
export function createProviderOperationDeadline(params: {
|
||||
timeoutMs?: number;
|
||||
label: string;
|
||||
@@ -142,10 +144,11 @@ export async function pollProviderOperationJson<TPayload>(params: {
|
||||
method: "GET",
|
||||
headers: params.headers,
|
||||
},
|
||||
timeoutMs: resolveProviderOperationTimeoutMs({
|
||||
deadline: params.deadline,
|
||||
defaultTimeoutMs: params.defaultTimeoutMs,
|
||||
}),
|
||||
timeoutMs: () =>
|
||||
resolveProviderOperationTimeoutMs({
|
||||
deadline: params.deadline,
|
||||
defaultTimeoutMs: params.defaultTimeoutMs,
|
||||
}),
|
||||
fetchFn: params.fetchFn,
|
||||
requestFailedMessage: params.requestFailedMessage,
|
||||
});
|
||||
@@ -169,7 +172,7 @@ export async function fetchProviderOperationResponse(params: {
|
||||
stage: ProviderOperationRetryStage;
|
||||
url: string;
|
||||
init?: RequestInit;
|
||||
timeoutMs?: number;
|
||||
timeoutMs?: ProviderOperationTimeoutMs;
|
||||
fetchFn: typeof fetch;
|
||||
provider?: string;
|
||||
requestFailedMessage?: string;
|
||||
@@ -183,7 +186,7 @@ export async function fetchProviderOperationResponse(params: {
|
||||
const response = await fetchWithTimeout(
|
||||
params.url,
|
||||
params.init ?? {},
|
||||
params.timeoutMs ?? DEFAULT_GUARDED_HTTP_TIMEOUT_MS,
|
||||
resolveProviderOperationRequestTimeoutMs(params.timeoutMs),
|
||||
params.fetchFn,
|
||||
);
|
||||
if (params.requestFailedMessage) {
|
||||
@@ -197,7 +200,7 @@ export async function fetchProviderOperationResponse(params: {
|
||||
export async function fetchProviderDownloadResponse(params: {
|
||||
url: string;
|
||||
init?: RequestInit;
|
||||
timeoutMs?: number;
|
||||
timeoutMs?: ProviderOperationTimeoutMs;
|
||||
fetchFn: typeof fetch;
|
||||
provider?: string;
|
||||
requestFailedMessage: string;
|
||||
@@ -215,6 +218,16 @@ export async function fetchProviderDownloadResponse(params: {
|
||||
});
|
||||
}
|
||||
|
||||
function resolveProviderOperationRequestTimeoutMs(
|
||||
timeoutMs: ProviderOperationTimeoutMs | undefined,
|
||||
): number {
|
||||
const resolved = typeof timeoutMs === "function" ? timeoutMs() : timeoutMs;
|
||||
if (typeof resolved !== "number" || !Number.isFinite(resolved) || resolved <= 0) {
|
||||
return DEFAULT_GUARDED_HTTP_TIMEOUT_MS;
|
||||
}
|
||||
return resolved;
|
||||
}
|
||||
|
||||
function resolveGuardedHttpTimeoutMs(timeoutMs: number | undefined): number {
|
||||
if (typeof timeoutMs !== "number" || !Number.isFinite(timeoutMs) || timeoutMs <= 0) {
|
||||
return DEFAULT_GUARDED_HTTP_TIMEOUT_MS;
|
||||
|
||||
@@ -31,7 +31,10 @@ export {
|
||||
sanitizeConfiguredModelProviderRequest,
|
||||
waitProviderOperationPollInterval,
|
||||
} from "../media-understanding/shared.js";
|
||||
export type { ProviderOperationDeadline } from "../media-understanding/shared.js";
|
||||
export type {
|
||||
ProviderOperationDeadline,
|
||||
ProviderOperationTimeoutMs,
|
||||
} from "../media-understanding/shared.js";
|
||||
export {
|
||||
executeProviderOperationWithRetry,
|
||||
providerOperationRetryConfig,
|
||||
|
||||
@@ -63,12 +63,18 @@ const providerHttpMocks = vi.hoisted(() => ({
|
||||
})),
|
||||
}));
|
||||
|
||||
function resolveMockProviderTimeoutMs(
|
||||
timeoutMs: FetchProviderOperationResponseParams["timeoutMs"],
|
||||
) {
|
||||
return typeof timeoutMs === "function" ? timeoutMs() : (timeoutMs ?? 60_000);
|
||||
}
|
||||
|
||||
providerHttpMocks.fetchProviderOperationResponseMock.mockImplementation(
|
||||
async (params: FetchProviderOperationResponseParams) => {
|
||||
const response = await providerHttpMocks.fetchWithTimeoutMock(
|
||||
params.url,
|
||||
params.init ?? {},
|
||||
params.timeoutMs ?? 60_000,
|
||||
resolveMockProviderTimeoutMs(params.timeoutMs),
|
||||
params.fetchFn,
|
||||
);
|
||||
if (params.requestFailedMessage) {
|
||||
@@ -83,7 +89,7 @@ providerHttpMocks.fetchProviderDownloadResponseMock.mockImplementation(
|
||||
const response = await providerHttpMocks.fetchWithTimeoutMock(
|
||||
params.url,
|
||||
params.init ?? {},
|
||||
params.timeoutMs ?? 60_000,
|
||||
resolveMockProviderTimeoutMs(params.timeoutMs),
|
||||
params.fetchFn,
|
||||
);
|
||||
await providerHttpMocks.assertOkOrThrowHttpErrorMock(response, params.requestFailedMessage);
|
||||
|
||||
@@ -182,7 +182,7 @@ export async function pollDashscopeVideoTaskUntilComplete(params: {
|
||||
method: "GET",
|
||||
headers: params.headers,
|
||||
},
|
||||
timeoutMs: resolveProviderOperationTimeoutMs({ deadline, defaultTimeoutMs }),
|
||||
timeoutMs: () => resolveProviderOperationTimeoutMs({ deadline, defaultTimeoutMs }),
|
||||
fetchFn: params.fetchFn,
|
||||
provider: params.providerLabel,
|
||||
requestFailedMessage: `${params.providerLabel} video-generation task poll failed`,
|
||||
|
||||
Reference in New Issue
Block a user