fix(voice-call): cap CLI gateway timeouts

This commit is contained in:
Peter Steinberger
2026-05-30 03:00:00 -04:00
parent 3fbd2432b6
commit 26bf8f0dc8
2 changed files with 56 additions and 6 deletions

View File

@@ -1,3 +1,4 @@
import { MAX_TIMER_TIMEOUT_MS } from "openclaw/plugin-sdk/number-runtime";
import { describe, expect, it } from "vitest";
import { testing } from "./cli.js";
@@ -35,3 +36,35 @@ describe("parseVoiceCallIntOption", () => {
).toThrow("Invalid numeric value for --port: 65536");
});
});
describe("voice-call CLI timeout helpers", () => {
it("caps gateway operation timeout grace", () => {
expect(testing.resolveGatewayOperationTimeoutMs({ ringTimeoutMs: 10_000 } as never)).toBe(
30_000,
);
expect(testing.resolveGatewayOperationTimeoutMs({ ringTimeoutMs: 60_000 } as never)).toBe(
65_000,
);
expect(
testing.resolveGatewayOperationTimeoutMs({ ringTimeoutMs: Number.MAX_SAFE_INTEGER } as never),
).toBe(MAX_TIMER_TIMEOUT_MS);
});
it("caps gateway continue timeout totals", () => {
expect(testing.resolveGatewayContinueTimeoutMs({ transcriptTimeoutMs: 180_000 } as never)).toBe(
220_000,
);
expect(
testing.resolveGatewayContinueTimeoutMs({
transcriptTimeoutMs: Number.MAX_SAFE_INTEGER,
} as never),
).toBe(MAX_TIMER_TIMEOUT_MS);
});
it("caps gateway polling deadlines", () => {
expect(testing.resolveVoiceCallDeadlineMs(5_000, 10_000)).toBe(15_000);
expect(testing.resolveVoiceCallDeadlineMs(Number.MAX_SAFE_INTEGER, 10_000)).toBe(
10_000 + MAX_TIMER_TIMEOUT_MS,
);
});
});

View File

@@ -5,7 +5,12 @@ import { format } from "node:util";
import type { Command } from "commander";
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
import { callGatewayFromCli } from "openclaw/plugin-sdk/gateway-runtime";
import { MAX_TCP_PORT, parseStrictNonNegativeInteger } from "openclaw/plugin-sdk/number-runtime";
import {
clampTimerTimeoutMs,
MAX_TIMER_TIMEOUT_MS,
MAX_TCP_PORT,
parseStrictNonNegativeInteger,
} from "openclaw/plugin-sdk/number-runtime";
import {
isRecord,
normalizeOptionalLowercaseString,
@@ -66,6 +71,9 @@ export const testing = {
},
isGatewayUnavailableForLocalFallback,
parseVoiceCallIntOption,
resolveGatewayContinueTimeoutMs,
resolveGatewayOperationTimeoutMs,
resolveVoiceCallDeadlineMs,
};
function writeStdoutLine(...values: unknown[]): void {
@@ -128,17 +136,26 @@ async function callVoiceCallGateway(
}
function resolveGatewayOperationTimeoutMs(config: VoiceCallConfig): number {
return Math.max(VOICE_CALL_GATEWAY_OPERATION_TIMEOUT_MS, config.ringTimeoutMs + 5000);
return Math.max(
VOICE_CALL_GATEWAY_OPERATION_TIMEOUT_MS,
clampTimerTimeoutMs(config.ringTimeoutMs + 5000) ?? 1,
);
}
function resolveGatewayContinueTimeoutMs(config: VoiceCallConfig): number {
return (
config.transcriptTimeoutMs +
VOICE_CALL_GATEWAY_OPERATION_TIMEOUT_MS +
VOICE_CALL_GATEWAY_TRANSCRIPT_BUFFER_MS
clampTimerTimeoutMs(
config.transcriptTimeoutMs +
VOICE_CALL_GATEWAY_OPERATION_TIMEOUT_MS +
VOICE_CALL_GATEWAY_TRANSCRIPT_BUFFER_MS,
) ?? 1
);
}
function resolveVoiceCallDeadlineMs(timeoutMs: number, nowMs = Date.now()): number {
return nowMs + (clampTimerTimeoutMs(timeoutMs) ?? MAX_TIMER_TIMEOUT_MS);
}
function isUnknownGatewayMethod(err: unknown, method: VoiceCallGatewayMethod): boolean {
return formatErrorMessage(err).includes(`unknown method: ${method}`);
}
@@ -185,7 +202,7 @@ async function pollVoiceCallContinueGateway(params: {
operationId: string;
timeoutMs: number;
}): Promise<unknown> {
const deadlineMs = Date.now() + params.timeoutMs;
const deadlineMs = resolveVoiceCallDeadlineMs(params.timeoutMs);
while (Date.now() <= deadlineMs) {
const gateway = await callVoiceCallGateway(