@@ -144,32 +144,54 @@ export class LLMStream extends llm.LLMStream {
144144 } ) )
145145 : undefined ;
146146
147- const stream = await this . #client. chat . stream (
148- {
149- model : this . #opts. model ,
150- // eslint-disable-next-line @typescript-eslint/no-explicit-any
151- messages : messages as any ,
152- tools,
153- temperature : this . #opts. temperature ,
154- maxTokens : this . #opts. maxTokens ,
155- // Only send tool-related params when tools are present
156- ...( tools && {
157- parallelToolCalls : this . #parallelToolCalls,
158- toolChoice : toMistralToolChoice ( this . #toolChoice) ,
159- } ) ,
160- } ,
161- {
162- fetchOptions : {
163- // Combine the caller's abort signal with a per-request timeout so the
164- // Mistral API call always respects connOptions.timeoutMs.
165- signal : AbortSignal . any ( [
166- this . abortController . signal ,
167- AbortSignal . timeout ( this . connOptions . timeoutMs ) ,
168- ] ) ,
169- } ,
170- } ,
147+ // Apply connection timeout only to the initial chat.stream() call so long
148+ // streaming responses are not prematurely aborted. Once the HTTP connection
149+ // is established the timer is cleared; the user's abort signal continues
150+ // to work throughout the stream iteration.
151+ const connAbortController = new AbortController ( ) ;
152+ const connTimeoutId = setTimeout (
153+ ( ) =>
154+ connAbortController . abort (
155+ new DOMException (
156+ `Mistral LLM: connection timed out after ${ this . connOptions . timeoutMs } ms` ,
157+ 'TimeoutError' ,
158+ ) ,
159+ ) ,
160+ this . connOptions . timeoutMs ,
171161 ) ;
172162
163+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
164+ let stream : any ;
165+ try {
166+ stream = await this . #client. chat . stream (
167+ {
168+ model : this . #opts. model ,
169+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
170+ messages : messages as any ,
171+ tools,
172+ temperature : this . #opts. temperature ,
173+ maxTokens : this . #opts. maxTokens ,
174+ // Only send tool-related params when tools are present
175+ ...( tools && {
176+ parallelToolCalls : this . #parallelToolCalls,
177+ toolChoice : toMistralToolChoice ( this . #toolChoice) ,
178+ } ) ,
179+ } ,
180+ {
181+ fetchOptions : {
182+ signal : AbortSignal . any ( [
183+ this . abortController . signal ,
184+ connAbortController . signal ,
185+ ] ) ,
186+ } ,
187+ } ,
188+ ) ;
189+ } finally {
190+ // Connection established (or failed) — clear the connection timeout so
191+ // the stream body can be read for as long as the model needs.
192+ clearTimeout ( connTimeoutId ) ;
193+ }
194+
173195 // Track each in-progress tool call by its stream index.
174196 // With parallel tool calls, Mistral sends all tool call definitions in the first
175197 // delta (each with an id + index), then streams argument fragments in subsequent
0 commit comments