fix(agents): cap provider setup timeout

(cherry picked from commit 575ee22448)
This commit is contained in:
Peter Steinberger
2026-05-10 10:03:06 +01:00
parent 19a3962f9a
commit 40db71b320
2 changed files with 35 additions and 4 deletions

View File

@@ -277,19 +277,37 @@ describe("streamWithIdleTimeout", () => {
const baseFn = vi.fn().mockReturnValue(mockStream);
const wrapped = streamWithIdleTimeout(baseFn, 1000);
const model = { api: "openai" } as Parameters<typeof baseFn>[0];
const model = { api: "openai", requestTimeoutMs: 5000 } as Parameters<typeof baseFn>[0];
const context = {} as Parameters<typeof baseFn>[1];
const options = {} as Parameters<typeof baseFn>[2];
void wrapped(model, context, options);
expect(baseFn).toHaveBeenCalledWith(
model,
expect.objectContaining({ api: "openai", requestTimeoutMs: 1000 }),
context,
expect.objectContaining({ signal: expect.any(AbortSignal) }),
);
});
it("keeps model request timeouts that are shorter than the idle watchdog", () => {
const mockStream = createMockAsyncIterable([]);
const baseFn = vi.fn().mockReturnValue(mockStream);
const wrapped = streamWithIdleTimeout(baseFn, 1000);
const model = { requestTimeoutMs: 250 } as Parameters<typeof baseFn>[0];
const context = {} as Parameters<typeof baseFn>[1];
const options = {} as Parameters<typeof baseFn>[2];
void wrapped(model, context, options);
expect(baseFn).toHaveBeenCalledWith(
expect.objectContaining({ requestTimeoutMs: 250 }),
context,
expect.any(Object),
);
});
it("throws on idle timeout", async () => {
vi.useFakeTimers();
const slowStream = createNeverYieldingStream();

View File

@@ -225,9 +225,22 @@ export function streamWithIdleTimeout(
sourceSignal?.removeEventListener("abort", abortFromSourceSignal);
};
const wrappedOptions = {
...(options ?? {}),
...options,
signal: streamAbortController.signal,
} as typeof options;
const existingRequestTimeoutMs =
typeof (model as { requestTimeoutMs?: unknown })?.requestTimeoutMs === "number" &&
Number.isFinite((model as { requestTimeoutMs?: number }).requestTimeoutMs) &&
(model as { requestTimeoutMs?: number }).requestTimeoutMs! > 0
? Math.floor((model as { requestTimeoutMs?: number }).requestTimeoutMs!)
: timeoutMs;
const wrappedModel =
typeof model === "object" && model !== null
? ({
...model,
requestTimeoutMs: Math.min(existingRequestTimeoutMs, timeoutMs),
} as typeof model)
: model;
const createTimeoutPromise = (setTimer: (timer: NodeJS.Timeout) => void): Promise<never> => {
return new Promise((_, reject) => {
@@ -244,7 +257,7 @@ export function streamWithIdleTimeout(
let maybeStream: ReturnType<StreamFn>;
try {
maybeStream = baseFn(model, context, wrappedOptions);
maybeStream = baseFn(wrappedModel, context, wrappedOptions);
} catch (error) {
cleanupSourceSignal();
throw error;