Skip to content

Commit 6609f67

Browse files
committed
fix: scope connection timeout to chat.stream() only, not stream iteration
1 parent 2edfdff commit 6609f67

1 file changed

Lines changed: 46 additions & 24 deletions

File tree

plugins/mistral/src/llm.ts

Lines changed: 46 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)