diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index 87fa07ec60f..38b3eb5ac69 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -1143,14 +1143,21 @@ export namespace Provider { const fetchFn = customFetch ?? fetch const opts = init ?? {} - // kilocode_change - apply default timeout when not explicitly configured + // kilocode_change start - apply connection-phase timeout only + // Use an AbortController so we can cancel the timer once headers arrive, + // preventing healthy streaming responses from being aborted mid-stream. const ms = options["timeout"] ?? REQUEST_TIMEOUT_MS - if (ms !== false) { - const signals: AbortSignal[] = [] + const controller = ms !== false ? new AbortController() : undefined + if (controller) { + const signals: AbortSignal[] = [controller.signal] if (opts.signal) signals.push(opts.signal) - signals.push(AbortSignal.timeout(ms)) opts.signal = signals.length > 1 ? AbortSignal.any(signals) : signals[0] } + const timer = + controller && typeof ms === "number" + ? setTimeout(() => controller.abort(new DOMException("The operation timed out.", "TimeoutError")), ms) + : undefined + // kilocode_change end // Strip openai itemId metadata following what codex does // Codex uses #[serde(skip_serializing)] on id fields for all item types: @@ -1170,11 +1177,20 @@ export namespace Provider { } } - return fetchFn(input, { - ...opts, - // @ts-ignore see here: https://github.com/oven-sh/bun/issues/16682 - timeout: false, - }) + // kilocode_change start - clear timeout once headers arrive + try { + const response = await fetchFn(input, { + ...opts, + // @ts-ignore see here: https://github.com/oven-sh/bun/issues/16682 + timeout: false, + }) + if (timer !== undefined) clearTimeout(timer) + return response + } catch (err) { + if (timer !== undefined) clearTimeout(timer) + throw err + } + // kilocode_change end } const bundledFn = BUNDLED_PROVIDERS[model.api.npm]