description: "Shared timeout arithmetic, deadline fusion, and timeout-versus-cancel classification for capabilities that clamp a caller's hint, arm a deadline, and must tell the two apart later."
English | 中文
dsh-timeout lets callers apply bounded deadlines to work, distinguish local timeout from upstream cancellation, and monitor streamed reads for inactivity. clampTimeout fills a missing hint from a backend default, caps it at the allowed maximum, and rejects invalid values before work starts. deadline combines the chosen timeout with upstream cancellation in one signal, while the caller remains responsible for actually stopping its process, socket, or task. idleWatchdog counts only time spent waiting for provider reads, and zero remains reserved for backend-owned untimed work rather than public configuration.
Use deadline when a capability runs one unit of work under a caller-visible timeout, and idleWatchdog when it reads a streamed transport. Validate caller hints with clampTimeout first so the timeoutMs that reaches deadline is always positive and finite.
import { clampTimeout } from '@deepseek-ai/dsh-timeout'
declare const requested: number | undefined
declare const DEFAULT_TIMEOUT_MS: number
declare const MAX_TIMEOUT_MS: number
const timeoutMs = clampTimeout(requested, DEFAULT_TIMEOUT_MS, MAX_TIMEOUT_MS, 'bash-local: request.timeoutMs')
clampTimeout fills the backend default when the hint is absent, caps the result at the backend maximum, and rejects a non-positive or non-finite hint with the caller-provided name. Zero is never accepted here: it is not a public disable-timeout value.
import { deadline, timeoutOf } from '@deepseek-ai/dsh-timeout'
using d = deadline(upstream, timeoutMs, 'BASH_TIMEOUT')
const outcome = await runWork({ signal: d.signal }) // work listens on d.signal and terminates itself
const timedOut = timeoutOf(d.signal, 'BASH_TIMEOUT') !== undefined
const aborted = d.signal.aborted && !timedOut
The signal only notifies: the caller must attach its own termination — hand d.signal to fetch, or listen for abort and kill the child. Racing a promise against a timer would resolve the tool call while the child process or socket leaks on.
timeoutOf(signal, code) recovers the timeout reason only when this deadline's timer fired first. Pass your own code so classification composes under nesting: when upstream is itself a deadline signal, a foreign timeout reads as an ordinary upstream cancellation instead of claiming that the local timer expired.
import { idleWatchdog } from '@deepseek-ai/dsh-timeout'
declare const upstream: AbortSignal | undefined
declare const idleMs: number
declare const providerIterator: AsyncIterator<unknown>
using watchdog = idleWatchdog(upstream, idleMs, 'LLM_STREAM_IDLE_TIMEOUT')
const next = await watchdog.next(providerIterator) // timer runs only while this read is outstanding
The timer is armed only while an iterator next() is outstanding and rearms on pulse() for transport activity that yields no value, so consumer think time between reads never counts as idle. The interval must be positive, finite, and no greater than MAX_TIMER_DELAY_MS.
Local file read/write/edit take no timeoutMs: file IO runs untimed because a deadline would kill work the OS will still finish.
Read these pages when you need the consumers or the boundary decision behind the library.
Indirectly, through the timeout consumers that render timeout outcomes.
No direct invalidation; the timeout consumers own any request-prefix changes.
These limits define what the library deliberately does not do. They are current package constraints, not a task backlog.
timeoutMs <= 0 is internal vocabulary — it disables the local timer only after an owning backend has resolved policy, never as a public model- or plugin-facing knob.