mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-17 20:32:25 +03:00
* fix(sse): bound active streams without terminal events * fix(sse): derive the active-stream ceiling from the largest registered model budget The watchdog is a hard lifetime cap that never resets on bytes, so a flat 15-minute default killed models the registry already allows to run for 20 minutes (the Codex entries declare timeoutMs: 1_200_000). The default is now that maximum plus a one-minute margin, and a new test re-derives the maximum from the registry so a future larger budget fails the gate instead of silently re-opening the bug. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
188 lines
6.6 KiB
TypeScript
188 lines
6.6 KiB
TypeScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
|
|
const runtimeTimeouts = await import("../../src/shared/utils/runtimeTimeouts.ts");
|
|
|
|
test("upstream timeout config derives hidden fetch timeouts from FETCH_TIMEOUT_MS", () => {
|
|
const config = runtimeTimeouts.getUpstreamTimeoutConfig({
|
|
FETCH_TIMEOUT_MS: "600000",
|
|
STREAM_IDLE_TIMEOUT_MS: "600000",
|
|
});
|
|
|
|
assert.deepEqual(config, {
|
|
fetchTimeoutMs: 600000,
|
|
streamIdleTimeoutMs: 600000,
|
|
streamActiveTimeoutMs: 1260000,
|
|
sseHeartbeatIntervalMs: 15000,
|
|
streamReadinessTimeoutMs: 80000,
|
|
streamReadinessMaxTimeoutMs: 180000,
|
|
streamDisconnectGracePeriodMs: 10000,
|
|
fetchHeadersTimeoutMs: 600000,
|
|
fetchBodyTimeoutMs: 600000,
|
|
fetchConnectTimeoutMs: 30000,
|
|
fetchKeepAliveTimeoutMs: 4000,
|
|
});
|
|
});
|
|
|
|
test("REQUEST_TIMEOUT_MS becomes the common timeout baseline when specific overrides are unset", () => {
|
|
const upstreamConfig = runtimeTimeouts.getUpstreamTimeoutConfig({
|
|
REQUEST_TIMEOUT_MS: "600000",
|
|
});
|
|
const apiBridgeConfig = runtimeTimeouts.getApiBridgeTimeoutConfig({
|
|
REQUEST_TIMEOUT_MS: "600000",
|
|
});
|
|
|
|
assert.equal(upstreamConfig.fetchTimeoutMs, 600000);
|
|
assert.equal(upstreamConfig.streamIdleTimeoutMs, 600000);
|
|
assert.equal(upstreamConfig.streamReadinessTimeoutMs, 600000);
|
|
assert.equal(upstreamConfig.streamReadinessMaxTimeoutMs, 180000);
|
|
assert.equal(upstreamConfig.fetchHeadersTimeoutMs, 600000);
|
|
assert.equal(upstreamConfig.fetchBodyTimeoutMs, 600000);
|
|
assert.equal(apiBridgeConfig.proxyTimeoutMs, 600000);
|
|
assert.equal(apiBridgeConfig.serverRequestTimeoutMs, 600000);
|
|
});
|
|
|
|
test("upstream timeout config honors explicit overrides and falls back on invalid values", () => {
|
|
const config = runtimeTimeouts.getUpstreamTimeoutConfig({
|
|
REQUEST_TIMEOUT_MS: "550000",
|
|
FETCH_TIMEOUT_MS: "600000",
|
|
STREAM_IDLE_TIMEOUT_MS: "600000",
|
|
STREAM_READINESS_TIMEOUT_MS: "90000",
|
|
STREAM_READINESS_MAX_TIMEOUT_MS: "240000",
|
|
FETCH_HEADERS_TIMEOUT_MS: "610000",
|
|
FETCH_BODY_TIMEOUT_MS: "0",
|
|
FETCH_CONNECT_TIMEOUT_MS: "45000",
|
|
FETCH_KEEPALIVE_TIMEOUT_MS: "-1",
|
|
});
|
|
|
|
assert.equal(config.streamReadinessTimeoutMs, 90000);
|
|
assert.equal(config.streamReadinessMaxTimeoutMs, 240000);
|
|
assert.equal(config.fetchHeadersTimeoutMs, 610000);
|
|
assert.equal(config.fetchBodyTimeoutMs, 0);
|
|
assert.equal(config.fetchConnectTimeoutMs, 45000);
|
|
assert.equal(config.fetchKeepAliveTimeoutMs, 4000);
|
|
});
|
|
|
|
test("TLS client timeout defaults to FETCH_TIMEOUT_MS and can be overridden", () => {
|
|
const defaultConfig = runtimeTimeouts.getTlsClientTimeoutConfig({
|
|
FETCH_TIMEOUT_MS: "600000",
|
|
});
|
|
const overriddenConfig = runtimeTimeouts.getTlsClientTimeoutConfig({
|
|
FETCH_TIMEOUT_MS: "600000",
|
|
TLS_CLIENT_TIMEOUT_MS: "720000",
|
|
});
|
|
|
|
assert.equal(defaultConfig.timeoutMs, 600000);
|
|
assert.equal(overriddenConfig.timeoutMs, 720000);
|
|
});
|
|
|
|
test("stainless timeout derives from fetch timeout and rounds up to whole seconds", () => {
|
|
assert.equal(
|
|
runtimeTimeouts.getStainlessTimeoutSeconds({
|
|
REQUEST_TIMEOUT_MS: "1200000",
|
|
}),
|
|
1200
|
|
);
|
|
assert.equal(
|
|
runtimeTimeouts.getStainlessTimeoutSeconds({
|
|
FETCH_TIMEOUT_MS: "600001",
|
|
}),
|
|
601
|
|
);
|
|
});
|
|
|
|
test("API bridge timeouts align request timeout with long proxy timeout by default", () => {
|
|
const config = runtimeTimeouts.getApiBridgeTimeoutConfig({
|
|
API_BRIDGE_PROXY_TIMEOUT_MS: "600000",
|
|
});
|
|
|
|
assert.deepEqual(config, {
|
|
proxyTimeoutMs: 600000,
|
|
serverRequestTimeoutMs: 600000,
|
|
serverHeadersTimeoutMs: 60000,
|
|
serverKeepAliveTimeoutMs: 5000,
|
|
serverSocketTimeoutMs: 0,
|
|
});
|
|
});
|
|
|
|
test("active stream timeout is independent from REQUEST_TIMEOUT_MS and validates its own setting", () => {
|
|
assert.equal(runtimeTimeouts.DEFAULT_STREAM_ACTIVE_TIMEOUT_MS, 1_260_000);
|
|
assert.equal(runtimeTimeouts.getUpstreamTimeoutConfig({}).streamActiveTimeoutMs, 1_260_000);
|
|
assert.equal(
|
|
runtimeTimeouts.getUpstreamTimeoutConfig({ REQUEST_TIMEOUT_MS: "25000" }).streamActiveTimeoutMs,
|
|
1_260_000
|
|
);
|
|
assert.equal(
|
|
runtimeTimeouts.getUpstreamTimeoutConfig({ STREAM_ACTIVE_TIMEOUT_MS: "120000" })
|
|
.streamActiveTimeoutMs,
|
|
120_000
|
|
);
|
|
assert.equal(
|
|
runtimeTimeouts.getUpstreamTimeoutConfig({ STREAM_ACTIVE_TIMEOUT_MS: "0" })
|
|
.streamActiveTimeoutMs,
|
|
0
|
|
);
|
|
|
|
for (const value of ["-1", "NaN", "Infinity"]) {
|
|
assert.equal(
|
|
runtimeTimeouts.getUpstreamTimeoutConfig({ STREAM_ACTIVE_TIMEOUT_MS: value })
|
|
.streamActiveTimeoutMs,
|
|
1_260_000
|
|
);
|
|
}
|
|
});
|
|
|
|
test("idle timeout default stays at 10min (600_000) for slow-thinking model safety", () => {
|
|
// NOTE: PR #2233 originally lowered this to 300_000, but the reviewer asked to keep
|
|
// the legacy default (slow thinking models, long Anthropic extended-thinking runs).
|
|
// The heartbeat-shape change is preserved; only the idle-timeout default revert remains.
|
|
assert.equal(runtimeTimeouts.DEFAULT_STREAM_IDLE_TIMEOUT_MS, 600_000);
|
|
assert.equal(runtimeTimeouts.getUpstreamTimeoutConfig({}).streamIdleTimeoutMs, 600_000);
|
|
});
|
|
|
|
test("readiness adaptive cap defaults to 180s and is env-overridable", () => {
|
|
assert.equal(runtimeTimeouts.DEFAULT_STREAM_READINESS_MAX_TIMEOUT_MS, 180_000);
|
|
assert.equal(runtimeTimeouts.getUpstreamTimeoutConfig({}).streamReadinessMaxTimeoutMs, 180_000);
|
|
assert.equal(
|
|
runtimeTimeouts.getUpstreamTimeoutConfig({ STREAM_READINESS_MAX_TIMEOUT_MS: "300000" })
|
|
.streamReadinessMaxTimeoutMs,
|
|
300_000
|
|
);
|
|
assert.equal(
|
|
runtimeTimeouts.getUpstreamTimeoutConfig({ STREAM_READINESS_MAX_TIMEOUT_MS: "bad" })
|
|
.streamReadinessMaxTimeoutMs,
|
|
180_000
|
|
);
|
|
});
|
|
|
|
test("heartbeat interval default = 15s, env-overridable", () => {
|
|
assert.equal(runtimeTimeouts.DEFAULT_SSE_HEARTBEAT_INTERVAL_MS, 15_000);
|
|
assert.equal(runtimeTimeouts.getUpstreamTimeoutConfig({}).sseHeartbeatIntervalMs, 15_000);
|
|
assert.equal(
|
|
runtimeTimeouts.getUpstreamTimeoutConfig({ SSE_HEARTBEAT_INTERVAL_MS: "8000" })
|
|
.sseHeartbeatIntervalMs,
|
|
8_000
|
|
);
|
|
assert.equal(
|
|
runtimeTimeouts.getUpstreamTimeoutConfig({ SSE_HEARTBEAT_INTERVAL_MS: "0" })
|
|
.sseHeartbeatIntervalMs,
|
|
0
|
|
);
|
|
});
|
|
|
|
test("API bridge proxy timeout defaults to the long upstream request window", () => {
|
|
const config = runtimeTimeouts.getApiBridgeTimeoutConfig({});
|
|
|
|
assert.equal(config.proxyTimeoutMs, 600000);
|
|
assert.equal(config.serverRequestTimeoutMs, 600000);
|
|
});
|
|
|
|
test("REQUEST_TIMEOUT_MS=0 disables API bridge proxy and request timeouts consistently", () => {
|
|
const config = runtimeTimeouts.getApiBridgeTimeoutConfig({
|
|
REQUEST_TIMEOUT_MS: "0",
|
|
});
|
|
|
|
assert.equal(config.proxyTimeoutMs, 0);
|
|
assert.equal(config.serverRequestTimeoutMs, 0);
|
|
});
|