fix(core): add stream idle watchdog for silent responses#4256
fix(core): add stream idle watchdog for silent responses#4256Alexxigang wants to merge 1 commit into
Conversation
| * | ||
| * @example | ||
| * ```ts | ||
| * ``ts |
There was a problem hiding this comment.
[Critical] JSDoc @example code fence markers are damaged — double backticks (``ts / ``) instead of triple backticks (```ts / ```). This breaks JSDoc rendering for sendMessageStream.
| * ``ts | |
| * ```ts | |
| * const chat = ai.chats.create({model: 'gemini-2.0-flash'}); | |
| * const response = await chat.sendMessageStream({ | |
| * message: 'Why is the sky blue?' | |
| * }); | |
| * for await (const chunk of response) { | |
| * console.log(chunk.text); | |
| * } | |
| * ``` |
— DeepSeek/deepseek-v4-pro via Qwen Code /review
| }, timeoutMs); | ||
| }); | ||
|
|
||
| return Promise.race([nextPromise, timeoutPromise]).finally(clearTimers); |
There was a problem hiding this comment.
[Suggestion] Each stream chunk in the hot while(true) loop allocates a new Promise.race + 2 setTimeout/clearTimeout pairs + 3 Promise objects via streamWatchdog.next(). For a stream with 20-50 chunks this creates measurable overhead. Consider lifting the timeout timer outside the loop and using timeoutId.refresh() per chunk (Node.js timer refresh API) to eliminate per-chunk timer churn while preserving the idle-detection guarantee.
— DeepSeek/deepseek-v4-pro via Qwen Code /review
| }, | ||
| authType: this.config.getContentGeneratorConfig()?.authType, | ||
| persistentMode: isUnattendedMode(), | ||
| signal: params.config?.abortSignal, |
There was a problem hiding this comment.
[Suggestion] retryWithBackoff receives only params.config?.abortSignal (the user's signal), not the internal streamAbortController.signal. When the stream idle watchdog fires during a retry delay, retryWithBackoff won't notice the internal abort and will wait the full delay before calling apiCall — which will then immediately fail because streamAbortController.signal is already aborted. Consider passing streamAbortController.signal (or a combined signal) to retryWithBackoff so internal aborts skip unnecessary retry delays.
— DeepSeek/deepseek-v4-pro via Qwen Code /review
Summary
next()calls inGeminiChatInvalidStreamError('STREAM_IDLE_TIMEOUT')and reuses existing transient retry handlingSTREAM_IDLE_TIMEOUTQWEN_CODE_STREAM_IDLE_TIMEOUT_MS,QWEN_CODE_DISABLE_STREAM_WATCHDOG)Validation
geminiChat.test.ts, lint, typecheck, and buildQWEN_CODE_STREAM_IDLE_TIMEOUT_MS=50stream idle watchdogtests insrc/core/geminiChat.test.tsmakeApiCallAndProcessStream()andprocessStreamResponse()for cleanup and retry flow91tests passed insrc/core/geminiChat.test.tsnpm run lint,npm run typecheck, andnpm run buildall completed successfullyScope / Risk
QWEN_CODE_STREAM_IDLE_TIMEOUT_MS,QWEN_CODE_DISABLE_STREAM_WATCHDOGTesting Matrix
Testing matrix notes:
packages/coreworkspace only.Linked Issues / Bugs
Fixes #4177