























@@ -12,6 +12,7 @@ import type { StreamFn } from "../../runtime/index.js";
1212import { resolveLlmIdleTimeoutMs, streamWithIdleTimeout } from "./llm-idle-timeout.js";
13131414const DEFAULT_LLM_IDLE_TIMEOUT_MS = 120_000;
15+const CRON_LLM_IDLE_TIMEOUT_MS = 60_000;
15161617describe("resolveLlmIdleTimeoutMs", () => {
1718it("returns default when config is undefined", () => {
@@ -41,8 +42,153 @@ describe("resolveLlmIdleTimeoutMs", () => {
4142expect(resolveLlmIdleTimeoutMs({ runTimeoutMs: 30_000 })).toBe(30_000);
4243});
434444-it("honors explicit cron run timeouts as the idle watchdog ceiling", () => {
45-expect(resolveLlmIdleTimeoutMs({ trigger: "cron", runTimeoutMs: 600_000 })).toBe(600_000);
45+it("caps explicit cron run timeouts so stream stalls can reach model fallbacks", () => {
46+expect(resolveLlmIdleTimeoutMs({ trigger: "cron", runTimeoutMs: 600_000 })).toBe(
47+CRON_LLM_IDLE_TIMEOUT_MS,
48+);
49+});
50+51+it("uses shorter explicit cron run timeouts as the idle watchdog ceiling", () => {
52+expect(resolveLlmIdleTimeoutMs({ trigger: "cron", runTimeoutMs: 30_000 })).toBe(30_000);
53+});
54+55+it("honors explicit cron run timeouts for local provider model calls", () => {
56+expect(
57+resolveLlmIdleTimeoutMs({
58+trigger: "cron",
59+runTimeoutMs: 600_000,
60+model: { baseUrl: "http://127.0.0.1:11434" },
61+}),
62+).toBe(600_000);
63+});
64+65+it.each([
66+["ollama", "http://ollama-host:11434"],
67+["ollama-beelink", "http://ollama-host:11434"],
68+["lmstudio", "http://lmstudio-box:1234/v1"],
69+["lmstudio-mac", "http://lmstudio-box:1234/v1"],
70+["vllm", "http://vllm-rig:8000/v1"],
71+["sglang", "http://sglang-rig:30000/v1"],
72+])(
73+"honors explicit cron run timeouts for self-hosted provider %s hostname %s",
74+(provider, baseUrl) => {
75+expect(
76+resolveLlmIdleTimeoutMs({
77+trigger: "cron",
78+runTimeoutMs: 600_000,
79+model: { provider, baseUrl },
80+}),
81+).toBe(600_000);
82+},
83+);
84+85+it("honors explicit cron run timeouts for explicit local host aliases", () => {
86+expect(
87+resolveLlmIdleTimeoutMs({
88+trigger: "cron",
89+runTimeoutMs: 600_000,
90+model: { baseUrl: "http://host.docker.internal:11434" },
91+}),
92+).toBe(600_000);
93+});
94+95+it("honors explicit cron run timeouts for custom local provider markers on bare hostnames", () => {
96+const cfg = {
97+models: {
98+providers: {
99+gpu: {
100+baseUrl: "http://gpu-box:8000/v1",
101+api: "openai-completions",
102+apiKey: "custom-local",
103+models: [],
104+},
105+"local-ollama": {
106+baseUrl: "http://ollama-box:11434",
107+api: "ollama",
108+apiKey: "ollama-local",
109+models: [],
110+},
111+},
112+},
113+} as unknown as OpenClawConfig;
114+115+expect(
116+resolveLlmIdleTimeoutMs({
117+ cfg,
118+trigger: "cron",
119+runTimeoutMs: 600_000,
120+model: { provider: "gpu", baseUrl: "http://gpu-box:8000/v1" },
121+}),
122+).toBe(600_000);
123+expect(
124+resolveLlmIdleTimeoutMs({
125+ cfg,
126+trigger: "cron",
127+runTimeoutMs: 600_000,
128+model: { provider: "local-ollama", baseUrl: "http://ollama-box:11434" },
129+}),
130+).toBe(600_000);
131+});
132+133+it("honors explicit cron run timeouts for provider-owned local services on bare hostnames", () => {
134+const cfg = {
135+models: {
136+providers: {
137+ds4: {
138+baseUrl: "http://ds4-box:8000/v1",
139+api: "openai-completions",
140+localService: {
141+command: "/opt/ds4/ds4-server",
142+healthUrl: "http://ds4-box:8000/v1/models",
143+},
144+models: [],
145+},
146+},
147+},
148+} as unknown as OpenClawConfig;
149+150+expect(
151+resolveLlmIdleTimeoutMs({
152+ cfg,
153+trigger: "cron",
154+runTimeoutMs: 600_000,
155+model: { provider: "ds4", baseUrl: "http://ds4-box:8000/v1" },
156+}),
157+).toBe(600_000);
158+});
159+160+it.each([
161+["openai", "openai/gpt-5.5", "http://api:8080/v1"],
162+["custom-proxy", "custom-proxy/gpt-5.5", "http://gateway:4000/v1"],
163+["ollama-cloud", "ollama-cloud/kimi-k2.6", "http://ollama-host:11434"],
164+])(
165+"keeps the cron stall cap for cloud provider %s routed through single-label host %s",
166+(provider, id, baseUrl) => {
167+expect(
168+resolveLlmIdleTimeoutMs({
169+trigger: "cron",
170+runTimeoutMs: 600_000,
171+model: { provider, id, baseUrl },
172+}),
173+).toBe(CRON_LLM_IDLE_TIMEOUT_MS);
174+},
175+);
176+177+it("keeps the cron stall cap for remote or cloud hostnames", () => {
178+expect(
179+resolveLlmIdleTimeoutMs({
180+trigger: "cron",
181+runTimeoutMs: 600_000,
182+model: { provider: "openai", id: "openai/gpt-5.5", baseUrl: "https://api.openai.com/v1" },
183+}),
184+).toBe(CRON_LLM_IDLE_TIMEOUT_MS);
185+expect(
186+resolveLlmIdleTimeoutMs({
187+trigger: "cron",
188+runTimeoutMs: 600_000,
189+model: { provider: "ollama", id: "ollama/gpt-oss:cloud", baseUrl: "http://ollama-host" },
190+}),
191+).toBe(CRON_LLM_IDLE_TIMEOUT_MS);
46192});
4719348194it("disables the idle watchdog when an explicit run timeout disables timeouts", () => {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。