From 2cb9fea514ce58faf7914bfd6c66fc9d17fb3b90 Mon Sep 17 00:00:00 2001 From: Gemini AI Date: Wed, 24 Dec 2025 21:53:00 +0400 Subject: [PATCH] Add server-side timeout handling to Ollama Cloud streaming - Added 60 second timeout per chunk in parseStreamingResponse - Added 120 second timeout to makeRequest with AbortController - This prevents the server from hanging indefinitely on slow/unresponsive API This should fix the UI freeze when sending messages to Ollama Cloud models. --- .../server/src/integrations/ollama-cloud.ts | 48 ++++++++++++++++--- 1 file changed, 42 insertions(+), 6 deletions(-) diff --git a/packages/server/src/integrations/ollama-cloud.ts b/packages/server/src/integrations/ollama-cloud.ts index b53ad9c..9da6392 100644 --- a/packages/server/src/integrations/ollama-cloud.ts +++ b/packages/server/src/integrations/ollama-cloud.ts @@ -338,12 +338,39 @@ export class OllamaCloudClient { const reader = response.body.getReader() const decoder = new TextDecoder() + const STREAM_TIMEOUT_MS = 60000 // 60 second timeout per chunk + let lastActivity = Date.now() + + const checkTimeout = () => { + if (Date.now() - lastActivity > STREAM_TIMEOUT_MS) { + reader.cancel().catch(() => { }) + throw new Error("Stream timeout - no data received for 60 seconds") + } + } try { while (true) { - const { done, value } = await reader.read() + checkTimeout() + + // Create a timeout promise + const timeoutPromise = new Promise((_, reject) => { + setTimeout(() => reject(new Error("Read timeout")), STREAM_TIMEOUT_MS) + }) + + // Race the read against the timeout + let result: ReadableStreamReadResult + try { + result = await Promise.race([reader.read(), timeoutPromise]) + } catch (timeoutError) { + reader.cancel().catch(() => { }) + throw new Error("Stream read timeout") + } + + const { done, value } = result if (done) break + lastActivity = Date.now() + const lines = decoder.decode(value, { stream: true }).split('\n').filter(line => line.trim()) for (const line of lines) { @@ -371,7 +398,7 @@ export class OllamaCloudClient { } } - private async makeRequest(endpoint: string, options: RequestInit): Promise { + private async makeRequest(endpoint: string, options: RequestInit, timeoutMs: number = 120000): Promise { // Ensure endpoint starts with /api const apiEndpoint = endpoint.startsWith('/api') ? endpoint : `/api${endpoint}` const url = `${this.baseUrl}${apiEndpoint}` @@ -386,10 +413,19 @@ export class OllamaCloudClient { console.log(`[OllamaCloud] Making request to: ${url}`) - return fetch(url, { - ...options, - headers - }) + // Add timeout to prevent indefinite hangs + const controller = new AbortController() + const timeoutId = setTimeout(() => controller.abort(), timeoutMs) + + try { + return await fetch(url, { + ...options, + headers, + signal: controller.signal + }) + } finally { + clearTimeout(timeoutId) + } } async getCloudModels(): Promise {