@@ -54,6 +54,38 @@ export const OLLAMA_NATIVE_BASE_URL = OLLAMA_DEFAULT_BASE_URL;
5454
5555const OLLAMA_STREAM_COOPERATIVE_YIELD_INTERVAL_MS = 12 ;
5656const OLLAMA_STREAM_COOPERATIVE_YIELD_MAX_EVENTS = 64 ;
57+
58+ /**
59+ * Determines if a base URL represents a remote (non-localhost) host.
60+ * Remote hosts may have higher latency for model loading and token generation.
61+ */
62+ function isRemoteOllamaHost ( baseUrl : string | undefined ) : boolean {
63+ if ( ! baseUrl ) return false ;
64+ try {
65+ const parsed = new URL ( baseUrl . trim ( ) ) ;
66+ const hostname = parsed . hostname . toLowerCase ( ) ;
67+ // Localhost variants
68+ if (
69+ hostname === "localhost" ||
70+ hostname === "127.0.0.1" ||
71+ hostname === "::1" ||
72+ hostname === "[::1]"
73+ ) {
74+ return false ;
75+ }
76+ // Private IP ranges (10.x.x.x, 172.16-31.x.x, 192.168.x.x)
77+ if (
78+ / ^ 1 0 \. \d { 1 , 3 } \. \d { 1 , 3 } \. \d { 1 , 3 } $ / . test ( hostname ) ||
79+ / ^ 1 7 2 \. ( 1 [ 6 - 9 ] | 2 \d | 3 [ 0 1 ] ) \. \d { 1 , 3 } \. \d { 1 , 3 } $ / . test ( hostname ) ||
80+ / ^ 1 9 2 \. 1 6 8 \. \d { 1 , 3 } \. \d { 1 , 3 } $ / . test ( hostname )
81+ ) {
82+ return false ; // Treat LAN as "local" for timeout purposes
83+ }
84+ return true ;
85+ } catch {
86+ return false ; // Invalid URL, assume local
87+ }
88+ }
5789const GARBLED_VISIBLE_TEXT_MODEL_RE = / \b (?: g l m | k i m i ) \b / i;
5890const GARBLED_VISIBLE_TEXT_MIN_CHARS = 80 ;
5991const GARBLED_VISIBLE_TEXT_SYMBOL_RE = / [ $ # % & = " ' _ ~ ` ^ | \\ / * + \- [ \] { } ( ) < > : ; , . ! ? ] / gu;
@@ -71,7 +103,14 @@ function throwIfOllamaStreamAborted(signal?: AbortSignal): void {
71103
72104function createOllamaStreamCooperativeScheduler (
73105 signal ?: AbortSignal ,
106+ params ?: {
107+ yieldIntervalMs ?: number ;
108+ maxEventsBeforeYield ?: number ;
109+ } ,
74110) : OllamaStreamCooperativeScheduler {
111+ const yieldIntervalMs = params ?. yieldIntervalMs ?? OLLAMA_STREAM_COOPERATIVE_YIELD_INTERVAL_MS ;
112+ const maxEventsBeforeYield = params ?. maxEventsBeforeYield ?? OLLAMA_STREAM_COOPERATIVE_YIELD_MAX_EVENTS ;
113+
75114 let lastYieldedAt = Date . now ( ) ;
76115 let eventsSinceYield = 0 ;
77116 return {
@@ -80,8 +119,8 @@ function createOllamaStreamCooperativeScheduler(
80119 eventsSinceYield += 1 ;
81120 const now = Date . now ( ) ;
82121 if (
83- eventsSinceYield < OLLAMA_STREAM_COOPERATIVE_YIELD_MAX_EVENTS &&
84- now - lastYieldedAt < OLLAMA_STREAM_COOPERATIVE_YIELD_INTERVAL_MS
122+ eventsSinceYield < maxEventsBeforeYield &&
123+ now - lastYieldedAt < yieldIntervalMs
85124 ) {
86125 return ;
87126 }
@@ -1131,12 +1170,27 @@ function resolveOllamaModelHeaders(model: {
11311170function resolveOllamaRequestTimeoutMs (
11321171 model : object ,
11331172 options : { requestTimeoutMs ?: unknown ; timeoutMs ?: unknown } | undefined ,
1134- ) : number | undefined {
1173+ baseUrl ?: string ,
1174+ ) : number {
11351175 const raw =
11361176 options ?. requestTimeoutMs ??
11371177 options ?. timeoutMs ??
11381178 ( model as { requestTimeoutMs ?: unknown } ) . requestTimeoutMs ;
1139- return typeof raw === "number" && Number . isFinite ( raw ) && raw > 0 ? Math . floor ( raw ) : undefined ;
1179+
1180+ if ( typeof raw === "number" && Number . isFinite ( raw ) && raw > 0 ) {
1181+ return Math . floor ( raw ) ;
1182+ }
1183+
1184+ // Provide different defaults for local vs remote Ollama hosts
1185+ // Remote hosts may need more time for model loading and network latency
1186+ const DEFAULT_LOCAL_TIMEOUT_MS = 30000 ; // 30s for localhost
1187+ const DEFAULT_REMOTE_TIMEOUT_MS = 180000 ; // 3min for remote/LAN hosts
1188+
1189+ if ( baseUrl && isRemoteOllamaHost ( baseUrl ) ) {
1190+ return DEFAULT_REMOTE_TIMEOUT_MS ;
1191+ }
1192+
1193+ return DEFAULT_LOCAL_TIMEOUT_MS ;
11401194}
11411195
11421196function createRawOllamaStreamFn (
@@ -1193,6 +1247,7 @@ function createRawOllamaStreamFn(
11931247 headers . Authorization = `Bearer ${ options . apiKey } ` ;
11941248 }
11951249
1250+ const remoteHost = isRemoteOllamaHost ( baseUrl ) ;
11961251 const { response, release } = await fetchWithSsrFGuard ( {
11971252 url : chatUrl ,
11981253 init : {
@@ -1205,8 +1260,9 @@ function createRawOllamaStreamFn(
12051260 timeoutMs : resolveOllamaRequestTimeoutMs (
12061261 model ,
12071262 options as { requestTimeoutMs ?: unknown ; timeoutMs ?: unknown } | undefined ,
1263+ baseUrl ,
12081264 ) ,
1209- auditContext : " ollama-stream.chat" ,
1265+ auditContext : ` ollama-stream.chat${ remoteHost ? ".remote" : "" } ` ,
12101266 } ) ;
12111267
12121268 try {
@@ -1234,7 +1290,14 @@ function createRawOllamaStreamFn(
12341290 } ;
12351291 const shouldEmitThinking = model . reasoning ?? true ;
12361292 const visibleContentSanitizer = createOllamaVisibleContentSanitizer ( model . id ) ;
1237- const cooperativeScheduler = createOllamaStreamCooperativeScheduler ( options ?. signal ) ;
1293+ // For remote hosts, use a more relaxed cooperative scheduler to accommodate
1294+ // higher latency in token generation and network transmission
1295+ const cooperativeScheduler = remoteHost
1296+ ? createOllamaStreamCooperativeScheduler ( options ?. signal , {
1297+ yieldIntervalMs : 50 , // Increased from 12ms to 50ms for remote
1298+ maxEventsBeforeYield : 128 , // Increased from 64 to 128 for remote
1299+ } )
1300+ : createOllamaStreamCooperativeScheduler ( options ?. signal ) ;
12381301 let streamStarted = false ;
12391302 let thinkingStarted = false ;
12401303 let thinkingEnded = false ;
0 commit comments