fix(agents): apply provider-class idle ceilings to explicit run budgets

This commit is contained in:
Ayaan Zaidi 2026-07-08 17:34:05 +05:30
parent 048f2096d2
commit e4949996d1
2 changed files with 40 additions and 16 deletions

View file

@ -520,6 +520,27 @@ describe("resolveLlmIdleTimeoutMs", () => {
expect(resolveLlmIdleTimeoutMs({ cfg, model })).toBe(expected);
});
it.each([
["local keeps no class ceiling", { baseUrl: "http://127.0.0.1:11434" }, 900_000],
[
"self-hosted keeps the 300s tier",
{ provider: "vllm", baseUrl: "https://gpu.example.com/v1" },
300_000,
],
["cloud keeps the 120s default", { provider: "openai" }, 120_000],
])("explicit run timeout above the tiers: %s", (_label, model, expected) => {
expect(resolveLlmIdleTimeoutMs({ runTimeoutMs: 900_000, model })).toBe(expected);
});
it("explicit run timeouts below the class tier still bound self-hosted idle", () => {
expect(
resolveLlmIdleTimeoutMs({
runTimeoutMs: 90_000,
model: { provider: "vllm", baseUrl: "https://gpu.example.com/v1" },
}),
).toBe(90_000);
});
it("cron exempts provider-id self-hosted models from the 60s clamp", () => {
expect(
resolveLlmIdleTimeoutMs({

View file

@ -249,8 +249,6 @@ export function resolveLlmIdleTimeoutMs(params?: {
model?: { baseUrl?: string; id?: string; provider?: string };
}): number {
const clampTimeoutMs = (valueMs: number) => clampTimerTimeoutMs(valueMs) ?? 1;
const clampImplicitTimeoutMs = (valueMs: number) =>
clampTimeoutMs(Math.min(valueMs, DEFAULT_LLM_IDLE_TIMEOUT_MS));
const runTimeoutMs = params?.runTimeoutMs;
const agentTimeoutSeconds = params?.cfg?.agents?.defaults?.timeoutSeconds;
@ -276,6 +274,23 @@ export function resolveLlmIdleTimeoutMs(params?: {
value < MAX_TIMER_TIMEOUT_MS,
);
// Run/agent budgets bound idle from below the provider-class ceiling; they
// must not shrink class tolerance (local has no ceiling, self-hosted 300s).
// Clamping every class to the cloud default reopened #85826-style kills for
// self-hosted users with explicit budgets above 120s.
const clampToClassIdleCeiling = (budgetMs: number): number => {
if (isLocalRuntimeModel) {
return clampTimeoutMs(budgetMs);
}
const classIdleTimeoutMs =
isSelfHostedRuntimeModel ||
isExplicitLocalHostnameRuntimeModel ||
isSelfHostedHostnameRuntimeModel
? SELF_HOSTED_LLM_IDLE_TIMEOUT_MS
: DEFAULT_LLM_IDLE_TIMEOUT_MS;
return clampTimeoutMs(Math.min(budgetMs, classIdleTimeoutMs));
};
// Explicit per-model idle timeout (`models.providers.<id>.timeoutSeconds`) wins
// over the NO_TIMEOUT_MS sentinel that runTimeoutMs may carry when the caller
// declared "run is unlimited". The two are independent: an unlimited run does
@ -314,23 +329,11 @@ export function resolveLlmIdleTimeoutMs(params?: {
}
return clampTimeoutMs(Math.min(runTimeoutMs, CRON_LLM_IDLE_TIMEOUT_MS));
}
return clampImplicitTimeoutMs(runTimeoutMs);
return clampToClassIdleCeiling(runTimeoutMs);
}
if (agentTimeoutMs !== undefined) {
// The agent budget bounds idle from below the provider-class ceiling; it
// must not shrink class tolerance (local has no ceiling, self-hosted 300s).
// Clamping every class to the cloud default here reopened #85826-style kills.
if (isLocalRuntimeModel) {
return clampTimeoutMs(agentTimeoutMs);
}
const classIdleTimeoutMs =
isSelfHostedRuntimeModel ||
isExplicitLocalHostnameRuntimeModel ||
isSelfHostedHostnameRuntimeModel
? SELF_HOSTED_LLM_IDLE_TIMEOUT_MS
: DEFAULT_LLM_IDLE_TIMEOUT_MS;
return clampTimeoutMs(Math.min(agentTimeoutMs, classIdleTimeoutMs));
return clampToClassIdleCeiling(agentTimeoutMs);
}
// The default watchdog is a network-silence-as-hang guard for cloud providers.