From a359437de91116bdfa7767faee2f9034e46970ea Mon Sep 17 00:00:00 2001 From: epahunov Date: Mon, 14 Sep 2026 13:36:59 +0300 Subject: [PATCH 1/3] fix --- src/api/providers/openai.ts | 55 ++++++++++++++++++++++++++++++++++--- 1 file changed, 51 insertions(+), 4 deletions(-) diff --git a/src/api/providers/openai.ts b/src/api/providers/openai.ts index 04b12f233d..dca15957e4 100644 --- a/src/api/providers/openai.ts +++ b/src/api/providers/openai.ts @@ -1,6 +1,7 @@ import { Anthropic } from "@anthropic-ai/sdk" import OpenAI, { AzureOpenAI } from "openai" import axios from "axios" +import { Agent, fetch as undiciFetch, Dispatcher } from "undici" import { type ModelInfo, @@ -52,34 +53,80 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl ...(this.options.openAiHeaders || {}), } + function resolveTimeoutMs(configuredMs: number | undefined): number { + if (configuredMs === undefined || configuredMs === 0) { + return 0 + // return 60 * 60 * 1000; + } + return configuredMs + } + + const timeoutMs = resolveTimeoutMs(this.timeoutMs) + + // VS Code bundles its own undici with a 5-minute `bodyTimeout` default. + // For streaming LLM requests, that default terminates the connection. + // We bypass the VS Code-bundled undici by injecting our own Agent-backed + // fetch into the OpenAI SDK. + const agent = new Agent({ + headersTimeout: timeoutMs, + bodyTimeout: timeoutMs, + keepAliveTimeout: timeoutMs, + keepAliveMaxTimeout: timeoutMs, + connect: { + timeout: Math.min(timeoutMs, 60_000), + }, + }) + + interface UndiciRequestInit extends RequestInit { + dispatcher?: Dispatcher + } + + type MockedFunction = { mock?: { calls: unknown[] } } + + const customFetch: typeof fetch = (url, init) => { + const undiciInit = { ...init, dispatcher: agent } as UndiciRequestInit + const fetchImpl = undiciFetch as unknown as ( + url: RequestInfo | URL, + init: UndiciRequestInit, + ) => Promise + + return fetchImpl(url, undiciInit) + } + + const timeoutConfig = { + timeout: timeoutMs, + } + if (isAzureAiInference) { - // Azure AI Inference Service (e.g., for DeepSeek) uses a different path structure this.client = new OpenAI({ baseURL, apiKey, defaultHeaders: headers, defaultQuery: { "api-version": this.options.azureApiVersion || "2024-05-01-preview" }, - timeout: this.timeoutMs, + ...timeoutConfig, }) } else if (isAzureOpenAi) { // Azure API shape slightly differs from the core API shape: // https://github.com/openai/openai-node?tab=readme-ov-file#microsoft-azure-openai + const azureBaseURL = `${baseURL.replace(/\/openai\/?$/i, "").replace(/\/$/, "")}/openai` this.client = new AzureOpenAI({ baseURL: azureBaseURL, apiKey, apiVersion: this.options.azureApiVersion || azureOpenAiDefaultApiVersion, defaultHeaders: headers, - timeout: this.timeoutMs, + ...timeoutConfig, }) } else { this.client = new OpenAI({ baseURL, apiKey, defaultHeaders: headers, - timeout: this.timeoutMs, + ...timeoutConfig, }) } + + ;(this.client as unknown as { fetch: typeof fetch }).fetch = customFetch } override async *createMessage( From e2a39747f76da35aabebfc56f04dc286cf708f4a Mon Sep 17 00:00:00 2001 From: epahunov Date: Mon, 14 Sep 2026 15:36:03 +0300 Subject: [PATCH 2/3] fix kimi tests --- .vscode/settings.json | 17 ++++++++++++++++- src/api/providers/__tests__/kimi-code.spec.ts | 10 ++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 9d429ef11f..bf5c483fc3 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -13,5 +13,20 @@ // Surface invisible (zero-width) and ambiguous/homoglyph Unicode in the // editor. Pairs with the code-qa.yml invisible-chars CI check. "editor.unicodeHighlight.invisibleCharacters": true, - "editor.unicodeHighlight.ambiguousCharacters": true + "editor.unicodeHighlight.ambiguousCharacters": true, + "workbench.colorCustomizations": { + "editor.selectionBackground": "#135564ef", + "editor.selectionHighlightBackground": "#13556496", + "activityBar.background": "#A02C18", + "titleBar.activeBackground": "#DE3F24", + "titleBar.activeForeground": "#FEF9F9", + "titleBar.inactiveBackground": "#A02C18", + "titleBar.inactiveForeground": "#FEF9F9", + "statusBar.background": "#A02C18", + "statusBar.foreground": "#FEF9F9", + "statusBar.debuggingBackground": "#A02C18", + "statusBar.debuggingForeground": "#FEF9F9", + "statusBar.noFolderBackground": "#A02C18", + "statusBar.noFolderForeground": "#FEF9F9" + } } diff --git a/src/api/providers/__tests__/kimi-code.spec.ts b/src/api/providers/__tests__/kimi-code.spec.ts index fe229910b8..edaa5d5ba7 100644 --- a/src/api/providers/__tests__/kimi-code.spec.ts +++ b/src/api/providers/__tests__/kimi-code.spec.ts @@ -10,6 +10,16 @@ const { mockGetAccessToken, mockForceRefreshAccessToken, mockGetModels } = vi.ho mockGetModels: vi.fn(), })) +vi.mock("undici", async (importOriginal) => { + const actual = await importOriginal() + return { + ...actual, + fetch: vi.fn().mockImplementation(async (url: RequestInfo | URL, init?: RequestInit) => { + return globalThis.fetch(url, init) + }), + } +}) + vi.mock("../../../integrations/kimi-code/oauth", () => ({ kimiCodeOAuthManager: { getAccessToken: mockGetAccessToken, From 4c9a833457bc7b0c8a262cf75539804ad4605f31 Mon Sep 17 00:00:00 2001 From: epahunov Date: Mon, 14 Sep 2026 19:37:24 +0300 Subject: [PATCH 3/3] fix globalthis --- src/api/providers/openai.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/api/providers/openai.ts b/src/api/providers/openai.ts index dca15957e4..8195242f47 100644 --- a/src/api/providers/openai.ts +++ b/src/api/providers/openai.ts @@ -81,16 +81,15 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl dispatcher?: Dispatcher } - type MockedFunction = { mock?: { calls: unknown[] } } - const customFetch: typeof fetch = (url, init) => { const undiciInit = { ...init, dispatcher: agent } as UndiciRequestInit - const fetchImpl = undiciFetch as unknown as ( - url: RequestInfo | URL, - init: UndiciRequestInit, - ) => Promise + return globalThis.fetch(url, undiciInit as RequestInit) + // const fetchImpl = undiciFetch as unknown as ( + // url: RequestInfo | URL, + // init: UndiciRequestInit, + // ) => Promise - return fetchImpl(url, undiciInit) + // return fetchImpl(url, undiciInit) } const timeoutConfig = {