- Restored per-provider stream watchdog with idle and first-event timeout support, progress-event filtering, and per-provider timeout overrides via `getStreamIdleTimeoutMs()` and `getStreamFirstEventTimeoutMs()`. - Fixed silent multi-hour hangs on Codex WebSocket and z.ai/GLM-via-OpenRouter subagent runs by filtering keepalive frames from idle watchdog resets. - Added `isOpenAICompletionsProgressChunk` export and progress-event filtering to multiple providers (Anthropic, OpenAI Responses/Completions, Azure OpenAI, Codex) to prevent keepalive frames from resetting idle timeouts. - Un-deprecated `StreamOptions.streamIdleTimeoutMs` and wired it into all built-in providers and lazy stream forwarder with environment variable fallback support.
82 lines
2.7 KiB
TypeScript
82 lines
2.7 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it } from "bun:test";
|
|
import { getStreamFirstEventTimeoutMs, getStreamIdleTimeoutMs } from "../src/utils/idle-iterator";
|
|
|
|
/**
|
|
* Per-provider fallback overrides on the stream-watchdog helpers.
|
|
*
|
|
* These are the gear that lets `google-gemini-cli` widen its first-event floor
|
|
* beyond the 100s global default without forcing every other provider to wait
|
|
* just as long. Tests pin the precedence contract callers depend on:
|
|
* caller option > env var > per-provider fallback > base default.
|
|
*/
|
|
|
|
const ENV_KEYS = [
|
|
"PI_STREAM_IDLE_TIMEOUT_MS",
|
|
"PI_OPENAI_STREAM_IDLE_TIMEOUT_MS",
|
|
"PI_STREAM_FIRST_EVENT_TIMEOUT_MS",
|
|
] as const;
|
|
|
|
const originalEnv: Partial<Record<(typeof ENV_KEYS)[number], string | undefined>> = {};
|
|
|
|
beforeEach(() => {
|
|
for (const key of ENV_KEYS) {
|
|
originalEnv[key] = Bun.env[key];
|
|
delete Bun.env[key];
|
|
}
|
|
});
|
|
|
|
afterEach(() => {
|
|
for (const key of ENV_KEYS) {
|
|
const prior = originalEnv[key];
|
|
if (prior === undefined) {
|
|
delete Bun.env[key];
|
|
} else {
|
|
Bun.env[key] = prior;
|
|
}
|
|
}
|
|
});
|
|
|
|
describe("getStreamIdleTimeoutMs(fallbackMs)", () => {
|
|
it("returns the per-provider fallback when env vars are unset", () => {
|
|
expect(getStreamIdleTimeoutMs(300_000)).toBe(300_000);
|
|
});
|
|
|
|
it("lets PI_STREAM_IDLE_TIMEOUT_MS override the per-provider fallback", () => {
|
|
Bun.env.PI_STREAM_IDLE_TIMEOUT_MS = "42";
|
|
expect(getStreamIdleTimeoutMs(300_000)).toBe(42);
|
|
});
|
|
|
|
it("treats PI_STREAM_IDLE_TIMEOUT_MS=0 as a watchdog disable", () => {
|
|
Bun.env.PI_STREAM_IDLE_TIMEOUT_MS = "0";
|
|
expect(getStreamIdleTimeoutMs(300_000)).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe("getStreamFirstEventTimeoutMs(idleTimeoutMs, fallbackMs)", () => {
|
|
it("returns the per-provider fallback when env unset and idle timeout is undefined", () => {
|
|
expect(getStreamFirstEventTimeoutMs(undefined, 300_000)).toBe(300_000);
|
|
});
|
|
|
|
it("floors the first-event timeout at the per-provider fallback even when idle is shorter", () => {
|
|
expect(getStreamFirstEventTimeoutMs(50_000, 300_000)).toBe(300_000);
|
|
});
|
|
|
|
it("never undershoots the steady-state idle timeout", () => {
|
|
expect(getStreamFirstEventTimeoutMs(500_000, 300_000)).toBe(500_000);
|
|
});
|
|
|
|
it("lets PI_STREAM_FIRST_EVENT_TIMEOUT_MS override the per-provider fallback", () => {
|
|
Bun.env.PI_STREAM_FIRST_EVENT_TIMEOUT_MS = "42";
|
|
expect(getStreamFirstEventTimeoutMs(undefined, 300_000)).toBe(42);
|
|
});
|
|
|
|
it("treats PI_STREAM_FIRST_EVENT_TIMEOUT_MS=0 as a watchdog disable", () => {
|
|
Bun.env.PI_STREAM_FIRST_EVENT_TIMEOUT_MS = "0";
|
|
expect(getStreamFirstEventTimeoutMs(undefined, 300_000)).toBeUndefined();
|
|
});
|
|
|
|
it("falls back to the 100s global default when no fallback or env is provided", () => {
|
|
expect(getStreamFirstEventTimeoutMs()).toBe(100_000);
|
|
});
|
|
});
|