diff --git a/src/tts/tts-core.test.ts b/src/tts/tts-core.test.ts new file mode 100644 index 00000000000..10e2d2e7972 --- /dev/null +++ b/src/tts/tts-core.test.ts @@ -0,0 +1,42 @@ +import { describe, expect, it, vi } from "vitest"; +import { MAX_TIMER_TIMEOUT_MS } from "../shared/number-coercion.js"; +import { summarizeText } from "./tts-core.js"; +import type { ResolvedTtsConfig } from "./tts-types.js"; + +describe("TTS core", () => { + it("clamps oversized summarization timeout timers", async () => { + const setTimeoutSpy = vi.spyOn(globalThis, "setTimeout"); + try { + const model = { provider: { id: "test-provider" } }; + const config = { + summarizeModel: { primary: "test-provider/test-model" }, + } as ResolvedTtsConfig; + + const result = await summarizeText( + { + text: "Long text that should be summarized for speech.", + targetLength: 120, + cfg: {}, + config, + timeoutMs: MAX_TIMER_TIMEOUT_MS + 1, + }, + { + completeSimple: vi.fn(async () => ({ + content: [{ type: "text", text: "Short summary." }], + stopReason: "stop", + usage: {}, + })), + getApiKeyForModel: vi.fn(async () => "key"), + prepareModelForSimpleCompletion: vi.fn(() => model as never), + requireApiKey: vi.fn(() => "key"), + resolveModelAsync: vi.fn(async () => ({ model })), + }, + ); + + expect(result.summary).toBe("Short summary."); + expect(setTimeoutSpy).toHaveBeenCalledWith(expect.any(Function), MAX_TIMER_TIMEOUT_MS); + } finally { + setTimeoutSpy.mockRestore(); + } + }); +}); diff --git a/src/tts/tts-core.ts b/src/tts/tts-core.ts index 8df372e7958..084ad6045ad 100644 --- a/src/tts/tts-core.ts +++ b/src/tts/tts-core.ts @@ -11,6 +11,7 @@ import { prepareModelForSimpleCompletion } from "../agents/simple-completion-tra import type { OpenClawConfig } from "../config/types.js"; import { completeSimple } from "../llm/stream.js"; import type { TextContent } from "../llm/types.js"; +import { resolveTimerTimeoutMs } from "../shared/number-coercion.js"; import type { ResolvedTtsConfig } from "./tts-types.js"; export { normalizeApplyTextNormalization, @@ -105,7 +106,8 @@ export async function summarizeText( try { const controller = new AbortController(); - const timeout = setTimeout(() => controller.abort(), timeoutMs); + const resolvedTimeoutMs = resolveTimerTimeoutMs(timeoutMs, 1); + const timeout = setTimeout(() => controller.abort(), resolvedTimeoutMs); try { const res = await deps.completeSimple(