Skip to content

Commit d2a3cc9

Browse files
committed
fix(agents): apply provider-class idle ceilings to explicit run budgets
1 parent 02eb663 commit d2a3cc9

2 files changed

Lines changed: 40 additions & 16 deletions

File tree

src/agents/embedded-agent-runner/run/llm-idle-timeout.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,27 @@ describe("resolveLlmIdleTimeoutMs", () => {
520520
expect(resolveLlmIdleTimeoutMs({ cfg, model })).toBe(expected);
521521
});
522522

523+
it.each([
524+
["local keeps no class ceiling", { baseUrl: "http://127.0.0.1:11434" }, 900_000],
525+
[
526+
"self-hosted keeps the 300s tier",
527+
{ provider: "vllm", baseUrl: "https://gpu.example.com/v1" },
528+
300_000,
529+
],
530+
["cloud keeps the 120s default", { provider: "openai" }, 120_000],
531+
])("explicit run timeout above the tiers: %s", (_label, model, expected) => {
532+
expect(resolveLlmIdleTimeoutMs({ runTimeoutMs: 900_000, model })).toBe(expected);
533+
});
534+
535+
it("explicit run timeouts below the class tier still bound self-hosted idle", () => {
536+
expect(
537+
resolveLlmIdleTimeoutMs({
538+
runTimeoutMs: 90_000,
539+
model: { provider: "vllm", baseUrl: "https://gpu.example.com/v1" },
540+
}),
541+
).toBe(90_000);
542+
});
543+
523544
it("cron exempts provider-id self-hosted models from the 60s clamp", () => {
524545
expect(
525546
resolveLlmIdleTimeoutMs({

src/agents/embedded-agent-runner/run/llm-idle-timeout.ts

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -249,8 +249,6 @@ export function resolveLlmIdleTimeoutMs(params?: {
249249
model?: { baseUrl?: string; id?: string; provider?: string };
250250
}): number {
251251
const clampTimeoutMs = (valueMs: number) => clampTimerTimeoutMs(valueMs) ?? 1;
252-
const clampImplicitTimeoutMs = (valueMs: number) =>
253-
clampTimeoutMs(Math.min(valueMs, DEFAULT_LLM_IDLE_TIMEOUT_MS));
254252

255253
const runTimeoutMs = params?.runTimeoutMs;
256254
const agentTimeoutSeconds = params?.cfg?.agents?.defaults?.timeoutSeconds;
@@ -276,6 +274,23 @@ export function resolveLlmIdleTimeoutMs(params?: {
276274
value < MAX_TIMER_TIMEOUT_MS,
277275
);
278276

277+
// Run/agent budgets bound idle from below the provider-class ceiling; they
278+
// must not shrink class tolerance (local has no ceiling, self-hosted 300s).
279+
// Clamping every class to the cloud default reopened #85826-style kills for
280+
// self-hosted users with explicit budgets above 120s.
281+
const clampToClassIdleCeiling = (budgetMs: number): number => {
282+
if (isLocalRuntimeModel) {
283+
return clampTimeoutMs(budgetMs);
284+
}
285+
const classIdleTimeoutMs =
286+
isSelfHostedRuntimeModel ||
287+
isExplicitLocalHostnameRuntimeModel ||
288+
isSelfHostedHostnameRuntimeModel
289+
? SELF_HOSTED_LLM_IDLE_TIMEOUT_MS
290+
: DEFAULT_LLM_IDLE_TIMEOUT_MS;
291+
return clampTimeoutMs(Math.min(budgetMs, classIdleTimeoutMs));
292+
};
293+
279294
// Explicit per-model idle timeout (`models.providers.<id>.timeoutSeconds`) wins
280295
// over the NO_TIMEOUT_MS sentinel that runTimeoutMs may carry when the caller
281296
// declared "run is unlimited". The two are independent: an unlimited run does
@@ -314,23 +329,11 @@ export function resolveLlmIdleTimeoutMs(params?: {
314329
}
315330
return clampTimeoutMs(Math.min(runTimeoutMs, CRON_LLM_IDLE_TIMEOUT_MS));
316331
}
317-
return clampImplicitTimeoutMs(runTimeoutMs);
332+
return clampToClassIdleCeiling(runTimeoutMs);
318333
}
319334

320335
if (agentTimeoutMs !== undefined) {
321-
// The agent budget bounds idle from below the provider-class ceiling; it
322-
// must not shrink class tolerance (local has no ceiling, self-hosted 300s).
323-
// Clamping every class to the cloud default here reopened #85826-style kills.
324-
if (isLocalRuntimeModel) {
325-
return clampTimeoutMs(agentTimeoutMs);
326-
}
327-
const classIdleTimeoutMs =
328-
isSelfHostedRuntimeModel ||
329-
isExplicitLocalHostnameRuntimeModel ||
330-
isSelfHostedHostnameRuntimeModel
331-
? SELF_HOSTED_LLM_IDLE_TIMEOUT_MS
332-
: DEFAULT_LLM_IDLE_TIMEOUT_MS;
333-
return clampTimeoutMs(Math.min(agentTimeoutMs, classIdleTimeoutMs));
336+
return clampToClassIdleCeiling(agentTimeoutMs);
334337
}
335338

336339
// The default watchdog is a network-silence-as-hang guard for cloud providers.

0 commit comments

Comments
 (0)