fix(provider): cap operation timeouts

This commit is contained in:
Peter Steinberger 2026-05-29 16:47:26 -04:00
parent 150673a734
commit a7820b2f54
2 changed files with 26 additions and 3 deletions

View file

@ -1,6 +1,8 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { VERSION } from "../version.js";
const MAX_TIMER_TIMEOUT_MS = 2_147_000_000;
const { fetchWithSsrFGuardMock, shouldUseEnvHttpProxyForUrlMock } = vi.hoisted(() => ({
fetchWithSsrFGuardMock: vi.fn(),
shouldUseEnvHttpProxyForUrlMock: vi.fn(() => false),
@ -70,6 +72,25 @@ describe("provider operation deadlines", () => {
expect(resolveProviderOperationTimeoutMs({ deadline, defaultTimeoutMs: 60_000 })).toBe(60_000);
});
it("caps oversized operation and per-call timeouts to timer-safe values", () => {
vi.useFakeTimers();
vi.setSystemTime(1_000);
const deadline = createProviderOperationDeadline({
label: "video generation",
timeoutMs: MAX_TIMER_TIMEOUT_MS + 1_000_000,
});
expect(deadline.timeoutMs).toBe(MAX_TIMER_TIMEOUT_MS);
expect(deadline.deadlineAtMs).toBe(1_000 + MAX_TIMER_TIMEOUT_MS);
expect(
resolveProviderOperationTimeoutMs({
deadline: createProviderOperationDeadline({ label: "no deadline" }),
defaultTimeoutMs: MAX_TIMER_TIMEOUT_MS + 1_000_000,
}),
).toBe(MAX_TIMER_TIMEOUT_MS);
});
it("clamps per-call timeouts to the remaining operation deadline", () => {
vi.useFakeTimers();
vi.setSystemTime(1_000);

View file

@ -28,6 +28,7 @@ import {
type ProviderOperationRetryStage,
type TransientProviderRetryConfig,
} from "../provider-runtime/operation-retry.js";
import { resolveTimerTimeoutMs } from "../shared/number-coercion.js";
import { fetchWithTimeout } from "../utils/fetch-timeout.js";
export { fetchWithTimeout };
export { normalizeBaseUrl } from "../agents/provider-request-config.js";
@ -101,7 +102,7 @@ export function createProviderOperationDeadline(params: {
) {
return { label: params.label };
}
const timeoutMs = Math.floor(params.timeoutMs);
const timeoutMs = resolveTimerTimeoutMs(params.timeoutMs, 1);
return {
deadlineAtMs: Date.now() + timeoutMs,
label: params.label,
@ -113,15 +114,16 @@ export function resolveProviderOperationTimeoutMs(params: {
deadline: ProviderOperationDeadline;
defaultTimeoutMs: number;
}): number {
const defaultTimeoutMs = resolveTimerTimeoutMs(params.defaultTimeoutMs, 1);
const deadlineAtMs = params.deadline.deadlineAtMs;
if (typeof deadlineAtMs !== "number") {
return params.defaultTimeoutMs;
return defaultTimeoutMs;
}
const remainingMs = deadlineAtMs - Date.now();
if (remainingMs <= 0) {
throw new Error(`${params.deadline.label} timed out after ${params.deadline.timeoutMs}ms`);
}
return Math.max(1, Math.min(params.defaultTimeoutMs, remainingMs));
return Math.max(1, Math.min(defaultTimeoutMs, remainingMs));
}
export function createProviderOperationTimeoutResolver(params: {