fix(jobs): make timeout messages tool-agnostic and CPU-aware (#596)

Replace the job timeout message that hardcoded "background-removal" for
every tool with a tool-agnostic one that sets the CPU-vs-GPU expectation,
the usual reason heavy AI times out on modest hardware. The client-side
SSE stall message gets the same treatment. Both stay under friendlyError's
280-char limit so the guidance reaches the user instead of collapsing to a
generic "Processing failed".

Refs #591
This commit is contained in:
SnapOtter
2026-07-21 15:22:57 +08:00
committed by GitHub
parent e6718ada71
commit 37c915c3da
4 changed files with 57 additions and 5 deletions
+2 -1
View File
@@ -52,6 +52,7 @@ import {
} from "../lib/object-storage.js";
import { OCR_MAX_ENCODED_INPUT_BYTES } from "../lib/ocr-limits.js";
import { SCRUB_PDF_PRODUCER_TOOLS, scrubPdfProducer } from "../lib/pdf-producer.js";
import { timeoutMessage } from "../lib/timeout.js";
import { InputValidationError } from "../modality/contract.js";
import {
publishEphemeral,
@@ -508,7 +509,7 @@ async function processToolJob(job: Job<ToolJobData>): Promise<ToolJobResult> {
const finalError = isCanceled
? "Canceled"
: isTimeout
? `Timed out after ${Math.round(timeoutMs / 1000)}s. On the first run the background-removal model may still be downloading; the image may be too large for CPU inference; or the worker may be busy or unavailable. Retry once the model has downloaded, or try a smaller image.`
? timeoutMessage(timeoutMs)
: errorMessage;
// Log genuine processing faults at error level (clients only ever see
+19
View File
@@ -24,3 +24,22 @@ export function computeExternalToolTimeout(megapixels: number): number {
}
return Math.max(60_000, megapixels * TIMEOUT_RATES.external * 1000);
}
/**
* User-facing message for a job that exceeded its worker timeout. Tool-agnostic
* on purpose: the previous copy hardcoded "background-removal", so an AI upscale
* that timed out on a modest CPU told the user a background-removal model was
* still downloading. Set the CPU-vs-GPU expectation instead, which is the usual
* reason heavy AI times out on self-hosted hardware (#591).
*/
export function timeoutMessage(timeoutMs: number): string {
const seconds = Math.round(timeoutMs / 1000);
// Must stay under friendlyError's 280-char / 3-line limit, or the whole
// message collapses to the generic "Processing failed" fallback
// (worker-timeout.test.ts guards this).
return (
`Timed out after ${seconds}s. Heavy tools run much slower on CPU than a GPU, ` +
`so a large input can exceed the limit; on the first run the model may still ` +
`be downloading. Try a smaller input or retry.`
);
}