Hacktoberfest 2026:メンテナが10月に向けて印を付けた、オープンで初心者向けの issue。 Hacktoberfest の issue を見る

feat(ai): add a timeout option to chat(), matching the media activities

オープン
#1,517 コメント 0 件 リアクション 0 件 担当者 1 名 GitHub で見る

メンテナーはふだん 1 日以内に返信

@AlemTuzlak がすでに取り組んでいます。

2026年9月26日 から。

評価

この issue はまだ評価されていません。

説明

waiting-on: maintainer

Problem

#981 / #1047 gave the media activities and summarize a shared timeout contract: timeout + abortSignal, composed by createActivityAbortControls. chat() never got one.

packages/ai/src/utilities/activity-abort.ts provides createActivityAbortControls({ abortSignal, timeout }), combineAbortSignals, raceWithAbort and isActivityAbortError. Together they compose the timeout with the caller signal into one effective signal and race the adapter call, so a hung provider still rejects. They also clear the timer on settle and route the abort to middleware onAbort rather than onError. Every generate* activity and summarize uses them.

chat() takes only abortController (activities/chat/index.ts L1019-L1022), keeps its own private copy of combineAbortSignals, and has no timeout option. The abortController doc in types.ts suggests building a timeout by hand with setTimeout(() => abortController.abort(), 5000). So a chat stream that never sends headers, or starts and then goes silent, is bounded only by whatever the caller aborts with.

Provider SDK timeouts don't reliably fill the gap either. Some of them drop their own timeout once a signal is passed, and adapters pass the chat signal down. For example, @openrouter/sdk applies timeoutMs only when no signal is present (OpenRouterTeam/typescript-sdk#1123). A core-level timeout composed into the signal avoids that for every adapter.

Verified in @tanstack/ai 0.58.0 and on main at 3e30cde.

Proposal

await chat({
  adapter,
  messages,
  timeout: 30_000,
  abortController,
})
  • timeout?: number: the maximum duration in ms of one chat() invocation, with no default. It would be built on createActivityAbortControls, and the effective signal passed to the adapter the same way the media activities do it. chat() would also use the shared combineAbortSignals.
  • Because adapters may ignore the signal, the stream's reads should also be raced against it (the raceWithAbort idea applied per next()), so a stalled iterator still rejects.
  • A timeout is an abort for middleware (onAbort, not onError), with a distinguishable reason: the existing TimeoutError from createTimeoutReason. Callers can then tell "timed out" from "caller aborted" and fall back to another model or retry.
  • The timer is cleared when the run settles.
Follow-up (or an option in the same change): streaming deadlines

For long reasoning or streaming outputs, a total cap has to be set high enough not to cut off legitimate slow answers, and then it catches stalls late. Two opt-in deadlines would complement timeout:

  • a first-chunk timeout: from request start to the first parsed stream chunk;
  • an idle timeout: the maximum gap between parsed chunks.

"Chunk" here means a parsed StreamChunk reaching core. SSE keep-alive comments may be dropped by the adapter or SDK before core sees them, so they neither count as progress nor mask a stall. For tool loops, these deadlines would re-arm for each model call.

Motivation

In a production app running many parallel structured chat() calls per request, a rare provider hang held one call until the whole request's deadline (80-118s observed) instead of failing fast and falling back to another model. The workaround today is an app-level AbortController + setTimeout at every call site. That doesn't get onAbort timeout classification, and it duplicates what createActivityAbortControls already does.

Tests (mirroring #1047)

  • A timeout aborts a hung chatStream, including one that ignores the signal.
  • The first of caller abort or timeout wins and keeps its reason.
  • A timeout triggers onAbort exactly once, not onError.
  • Completion clears the timer.
主要言語
TypeScript
スター
3.2k
フォーク
372
平均マージ
1日 16時間
マージ済み PR(30日)
244

環境構築

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

TanStack/ai のほかの issue

TanStack/ai の issue をすべて見る

似ている issue

TypeScript の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。