From a359437de91116bdfa7767faee2f9034e46970ea Mon Sep 17 00:00:00 2001 From: epahunov Date: Mon, 14 Sep 2026 13:36:59 +0300 Subject: [PATCH 1/9] 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/9] 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/9] 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 = { From 9f62c805275db8eb7aa6ba48eb80b7d4c797e910 Mon Sep 17 00:00:00 2001 From: epahunov Date: Tue, 15 Sep 2026 17:24:08 +0300 Subject: [PATCH 4/9] try new tests --- .../src/suite/providers/deepseek-v4.test.ts | 22 ++++++++++++++----- src/api/providers/openai.ts | 12 +++++----- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/apps/vscode-e2e/src/suite/providers/deepseek-v4.test.ts b/apps/vscode-e2e/src/suite/providers/deepseek-v4.test.ts index d5be2eba2f..3353355950 100644 --- a/apps/vscode-e2e/src/suite/providers/deepseek-v4.test.ts +++ b/apps/vscode-e2e/src/suite/providers/deepseek-v4.test.ts @@ -3,12 +3,20 @@ import * as assert from "assert" import * as fs from "fs/promises" import * as path from "path" import * as vscode from "vscode" - +import * as undici from "undici" import { RooCodeEventName, type ClineMessage } from "@roo-code/types" import { setDefaultSuiteTimeout } from "../test-utils" import { sleep, waitFor, waitUntilAborted } from "../utils" +interface UndiciModule { + fetch: typeof fetch +} + +interface UndiciRequestInit extends RequestInit { + dispatcher?: unknown +} + const DEEPSEEK_API_KEY = process.env.DEEPSEEK_API_KEY type DeepSeekModelId = "deepseek-v4-flash" | "deepseek-v4-pro" @@ -71,10 +79,11 @@ function getRequestBody(init?: RequestInit): } function installDeepSeekRequestCapture(capture: CapturedDeepSeekRequest[], baseUrl: string): () => void { - const originalFetch = globalThis.fetch + const undiciModule = undici as UndiciModule + const originalFetch = undiciModule.fetch const targetOrigin = new URL(baseUrl).origin - globalThis.fetch = async function (input: RequestInfo | URL, init?: RequestInit): Promise { + undiciModule.fetch = async function (input: RequestInfo | URL, init?: RequestInit): Promise { const url = getRequestUrl(input) if (isUrlWithOrigin(url, targetOrigin) && isChatCompletionsUrl(url)) { @@ -106,11 +115,12 @@ function installDeepSeekRequestCapture(capture: CapturedDeepSeekRequest[], baseU capture.push(request) } - return originalFetch.call(globalThis, input, init as RequestInit) - } as typeof globalThis.fetch + // Передаём init как есть (включая dispatcher из OpenAiHandler) оригинальному undici.fetch + return originalFetch.call(undiciModule, input, init as UndiciRequestInit) + } return () => { - globalThis.fetch = originalFetch + undiciModule.fetch = originalFetch } } diff --git a/src/api/providers/openai.ts b/src/api/providers/openai.ts index 8195242f47..9ff54020ba 100644 --- a/src/api/providers/openai.ts +++ b/src/api/providers/openai.ts @@ -83,13 +83,13 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl const customFetch: typeof fetch = (url, init) => { const undiciInit = { ...init, dispatcher: agent } as UndiciRequestInit - return globalThis.fetch(url, undiciInit as RequestInit) - // 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 = { From eb0378d438d53b36127b89b687396640683e1ee7 Mon Sep 17 00:00:00 2001 From: epahunov Date: Tue, 15 Sep 2026 17:36:10 +0300 Subject: [PATCH 5/9] add undici --- package.json | 3 ++- pnpm-lock.yaml | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 1fd9ddc8fe..f0938f31c5 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,8 @@ "rimraf": "6.0.1", "tsx": "4.22.4", "turbo": "2.10.0", - "typescript": "5.9.3" + "typescript": "5.9.3", + "undici": "^6.21.3" }, "lint-staged": { "*.{js,jsx,ts,tsx,json,css,md}": [ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 47f526185f..8e6e7a2add 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -74,6 +74,9 @@ importers: typescript: specifier: 5.9.3 version: 5.9.3 + undici: + specifier: ^6.21.3 + version: 6.28.0 apps/cli: dependencies: From 396ec2425b1058d5afa0af8102795cd1832d0427 Mon Sep 17 00:00:00 2001 From: epahunov Date: Tue, 15 Sep 2026 17:41:31 +0300 Subject: [PATCH 6/9] fix --- apps/vscode-e2e/src/suite/providers/deepseek-v4.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/vscode-e2e/src/suite/providers/deepseek-v4.test.ts b/apps/vscode-e2e/src/suite/providers/deepseek-v4.test.ts index 3353355950..ab0f4d8914 100644 --- a/apps/vscode-e2e/src/suite/providers/deepseek-v4.test.ts +++ b/apps/vscode-e2e/src/suite/providers/deepseek-v4.test.ts @@ -79,7 +79,7 @@ function getRequestBody(init?: RequestInit): } function installDeepSeekRequestCapture(capture: CapturedDeepSeekRequest[], baseUrl: string): () => void { - const undiciModule = undici as UndiciModule + const undiciModule = undici as unknown as UndiciModule const originalFetch = undiciModule.fetch const targetOrigin = new URL(baseUrl).origin From a143236b7a04571f750b6076994f044dceb82912 Mon Sep 17 00:00:00 2001 From: epahunov Date: Tue, 15 Sep 2026 17:46:05 +0300 Subject: [PATCH 7/9] fix --- .vscode/settings.json | 17 +---------------- pnpm-lock.yaml | 2 +- 2 files changed, 2 insertions(+), 17 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index bf5c483fc3..9d429ef11f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -13,20 +13,5 @@ // 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, - "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" - } + "editor.unicodeHighlight.ambiguousCharacters": true } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8e6e7a2add..7575f6f215 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -75,7 +75,7 @@ importers: specifier: 5.9.3 version: 5.9.3 undici: - specifier: ^6.21.3 + specifier: 6.28.0 version: 6.28.0 apps/cli: From b10570cbb87ef14bec900df149f9e5e10fa20dfc Mon Sep 17 00:00:00 2001 From: epahunov Date: Tue, 15 Sep 2026 18:05:03 +0300 Subject: [PATCH 8/9] fix --- apps/vscode-e2e/package.json | 5 +++-- package.json | 3 +-- pnpm-lock.yaml | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/vscode-e2e/package.json b/apps/vscode-e2e/package.json index 4a2bed8a87..55983817e0 100644 --- a/apps/vscode-e2e/package.json +++ b/apps/vscode-e2e/package.json @@ -18,11 +18,11 @@ "clean": "rimraf out .turbo" }, "devDependencies": { + "@copilotkit/aimock": "1.35.0", "@playwright/test": "1.62.1", "@roo-code/config-eslint": "workspace:^", "@roo-code/config-typescript": "workspace:^", "@roo-code/types": "workspace:^", - "@copilotkit/aimock": "1.35.0", "@types/mocha": "10.0.10", "@types/node": "22.20.1", "@types/vscode": "1.100.0", @@ -30,6 +30,7 @@ "dotenv-cli": "11.0.0", "glob": "11.1.0", "mocha": "11.2.2", - "rimraf": "6.0.1" + "rimraf": "6.0.1", + "undici": "^6.21.3" } } diff --git a/package.json b/package.json index f0938f31c5..1fd9ddc8fe 100644 --- a/package.json +++ b/package.json @@ -46,8 +46,7 @@ "rimraf": "6.0.1", "tsx": "4.22.4", "turbo": "2.10.0", - "typescript": "5.9.3", - "undici": "^6.21.3" + "typescript": "5.9.3" }, "lint-staged": { "*.{js,jsx,ts,tsx,json,css,md}": [ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7575f6f215..68bbe42d8d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -74,9 +74,6 @@ importers: typescript: specifier: 5.9.3 version: 5.9.3 - undici: - specifier: 6.28.0 - version: 6.28.0 apps/cli: dependencies: @@ -198,6 +195,9 @@ importers: rimraf: specifier: 6.0.1 version: 6.0.1 + undici: + specifier: ^6.21.3 + version: 6.28.0 packages/build: devDependencies: From 681c98502c10905969ed76b61b2bd51a1574760a Mon Sep 17 00:00:00 2001 From: epahunov Date: Tue, 15 Sep 2026 18:07:29 +0300 Subject: [PATCH 9/9] fix --- pnpm-lock.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 68bbe42d8d..705c27eaea 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -196,7 +196,7 @@ importers: specifier: 6.0.1 version: 6.0.1 undici: - specifier: ^6.21.3 + specifier: 6.28.0 version: 6.28.0 packages/build: