You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
timeoutMs is ignored whenever the request also passes an AbortSignal #1123
timeoutMs, whether set on the client or per request, stops applying as soon as the request also carries an AbortSignal. The timeout isn't composed with the signal; it's skipped entirely.
The timeoutMs doc comment says that fetchOptions.signal "will take precedence over this option", so this is the documented behaviour. The problem is that "precedence" here means the timeout is dropped altogether.
Why it matters
Framework adapters forward their own cancellation signal on every request. For example, @tanstack/ai-openrouter passes { signal } to chat.send / responses.send whenever its caller cancels, which is typical for server code that aborts on client disconnect. Anyone using the SDK through such an adapter can't use timeoutMs, because a hung request is bounded only by whatever eventually fires that signal.
Expected
When both are given, compose them. The request aborts on whichever fires first, and a timeout surfaces as a timeout (RequestTimeoutError), distinguishable from a caller abort (RequestAbortedError).
On main most of this already exists: _do builds a per-attempt AbortSignal.timeout(timeoutMs) and runs it through combineSignals(cloned.signal, timeoutSignal) (L284-L289). So dropping the !fetchOptions?.signal condition at L230 looks like enough to get "first to fire wins". The abort reason of AbortSignal.timeout is a TimeoutErrorDOMException, which the existing isTimeoutError branch already maps to RequestTimeoutError. (If this file is generator-owned, the change may belong in the Speakeasy template or a persistent edit.)
For comparison, #773 already merges the caller signal with replicated timeoutMs behaviour inside callModel. This request would give chat.send, responses.send and the other raw methods the same guarantee.
Repro
constclient=newOpenRouter({ apiKey,timeoutMs: 5_000})constcontroller=newAbortController()// never aborted// Against an endpoint that accepts the connection but never responds,// this waits indefinitely instead of failing after ~5s.awaitclient.chat.send({ chatRequest },{signal: controller.signal})
Activity