Skip to content

Commit 40d7445

Browse files
Merge pull request #281 from modelstudioai/fix/node26-fetch-dispatcher
fix: resolve Node 26 fetch and dispatcher incompatibility
2 parents 0d449ca + d038e2a commit 40d7445

50 files changed

Lines changed: 427 additions & 224 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎packages/commands/src/commands/knowledge/upload-stream.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { httpTransport } from "bailian-cli-core";
12
import { createHash } from "node:crypto";
23
import { createReadStream } from "node:fs";
34

@@ -29,7 +30,7 @@ export async function putFileStream(
2930
duplex: "half",
3031
signal,
3132
};
32-
return await fetch(lease.url, request);
33+
return await httpTransport.fetch(lease.url, request);
3334
} finally {
3435
source.destroy();
3536
}

‎packages/commands/src/commands/managed-agent/_engine/playground-launcher.ts‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { httpTransport } from "bailian-cli-core";
12
import { execFile, spawn, type ChildProcess } from "node:child_process";
23
import { createHash, randomBytes } from "node:crypto";
34
import { existsSync, readFileSync } from "node:fs";
@@ -310,7 +311,7 @@ async function waitForPlaygroundReady(
310311
while (Date.now() <= deadline) {
311312
if (child.exitCode !== null) return null;
312313
try {
313-
const response = await fetch(`http://localhost:${port}/health`, {
314+
const response = await httpTransport.fetch(`http://localhost:${port}/health`, {
314315
signal: AbortSignal.timeout(1_000),
315316
});
316317
const body = response.ok
@@ -327,7 +328,7 @@ async function waitForPlaygroundReady(
327328

328329
async function probeExistingPlayground(port: number): Promise<ExistingPlayground | null> {
329330
try {
330-
const response = await fetch(`http://localhost:${port}/health`, {
331+
const response = await httpTransport.fetch(`http://localhost:${port}/health`, {
331332
signal: AbortSignal.timeout(2_000),
332333
});
333334
if (!response.ok) return null;
@@ -383,7 +384,7 @@ async function resolvePlaygroundBrowserTarget(
383384
): Promise<PlaygroundBrowserTarget> {
384385
const baseUrl = `http://localhost:${port}`;
385386
try {
386-
const response = await fetch(`${baseUrl}/api/project`, {
387+
const response = await httpTransport.fetch(`${baseUrl}/api/project`, {
387388
signal: AbortSignal.timeout(3_000),
388389
});
389390
if (!response.ok) return { url: baseUrl };

‎packages/commands/src/commands/shared/telemetry.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { httpTransport } from "bailian-cli-core";
12
import {
23
BailianError,
34
ExitCode,
@@ -204,7 +205,7 @@ export async function resolveOssPayload<T>(resp: Record<string, unknown>): Promi
204205
};
205206
if (originData !== undefined && originData !== null) return originData;
206207
if (dataDownloadUrl) {
207-
const res = await fetch(dataDownloadUrl.replace(/^http:\/\//, "https://"));
208+
const res = await httpTransport.fetch(dataDownloadUrl.replace(/^http:\/\//, "https://"));
208209
if (!res.ok) {
209210
throw new BailianError(
210211
`Failed to download telemetry data (HTTP ${res.status}).`,

‎packages/commands/src/commands/speech/recognize.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { httpTransport } from "bailian-cli-core";
12
import { writeFileSync } from "fs";
23
import {
34
BailianError,
@@ -435,7 +436,7 @@ async function handleAsyncMode(
435436
}
436437

437438
// Fetch transcription JSON
438-
const transRes = await fetch(subResult.transcription_url);
439+
const transRes = await httpTransport.fetch(subResult.transcription_url);
439440
if (!transRes.ok) {
440441
throw new BailianError(
441442
`Failed to download transcription: HTTP ${transRes.status}`,

‎packages/commands/tests/auth-api-key.test.ts‎

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { httpTransport } from "bailian-cli-core";
12
import { BailianError, ExitCode, type Identity, type Settings } from "bailian-cli-core";
23
import { expect, test } from "vite-plus/test";
34
import {
@@ -105,9 +106,9 @@ test("显式 Base URL 只校验指定站点,并根据 Key 类型选择 /models
105106
});
106107

107108
test("Token Plan 多地区并行校验选择实际接受 Key 的国际站点", async () => {
108-
const originalFetch = globalThis.fetch;
109+
const originalFetch = httpTransport.fetch;
109110
const requests: Array<{ authorization: string; url: string }> = [];
110-
globalThis.fetch = async (input, init) => {
111+
httpTransport.fetch = async (input, init) => {
111112
const url = requestUrl(input);
112113
requests.push({
113114
authorization: new Headers(init?.headers).get("authorization") ?? "",
@@ -135,30 +136,30 @@ test("Token Plan 多地区并行校验选择实际接受 Key 的国际站点", a
135136
},
136137
]);
137138
} finally {
138-
globalThis.fetch = originalFetch;
139+
httpTransport.fetch = originalFetch;
139140
}
140141
});
141142

142143
test("所有候选站点明确拒绝 Key 时返回 AUTH 校验失败", async () => {
143-
const originalFetch = globalThis.fetch;
144-
globalThis.fetch = async () =>
144+
const originalFetch = httpTransport.fetch;
145+
httpTransport.fetch = async () =>
145146
jsonResponse({ code: "InvalidApiKey", message: "invalid key" }, 401);
146147

147148
try {
148149
const error = await captureValidationError("sk-sp-placeholder");
149150
expect(error.exitCode).toBe(ExitCode.AUTH);
150151
expect(error.message).toMatch(/API key validation failed/);
151152
} finally {
152-
globalThis.fetch = originalFetch;
153+
httpTransport.fetch = originalFetch;
153154
}
154155
});
155156

156157
test.each([
157158
{ name: "5xx", status: 503, message: "temporary outage" },
158159
{ name: "429", status: 429, message: "too many requests" },
159160
])("存在 $name 时校验结论不确定,不误报 Key 无效", async ({ status, message }) => {
160-
const originalFetch = globalThis.fetch;
161-
globalThis.fetch = async (input) => {
161+
const originalFetch = httpTransport.fetch;
162+
httpTransport.fetch = async (input) => {
162163
if (requestUrl(input).startsWith("https://dashscope.aliyuncs.com/")) {
163164
return jsonResponse({ code: "TemporaryFailure", message }, status);
164165
}
@@ -170,6 +171,6 @@ test.each([
170171
expect(error.exitCode).toBe(ExitCode.GENERAL);
171172
expect(error.message).toBe(message);
172173
} finally {
173-
globalThis.fetch = originalFetch;
174+
httpTransport.fetch = originalFetch;
174175
}
175176
});

‎packages/commands/tests/deploy-capacity-write.test.ts‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { httpTransport } from "bailian-cli-core";
12
import {
23
BailianError,
34
ExitCode,
@@ -242,12 +243,14 @@ beforeEach(() => {
242243
stderr += String(chunk);
243244
return true;
244245
});
245-
vi.stubGlobal("fetch", vi.fn().mockRejectedValue(new Error("Real network is forbidden")));
246+
vi.spyOn(httpTransport, "fetch").mockImplementation(
247+
vi.fn().mockRejectedValue(new Error("Real network is forbidden")),
248+
);
246249
});
247250

248251
afterEach(() => {
249252
try {
250-
expect(fetch).not.toHaveBeenCalled();
253+
expect(httpTransport.fetch).not.toHaveBeenCalled();
251254
for (const mock of mocks) expect(mock.request).not.toHaveBeenCalled();
252255
expect(process.listeners("SIGINT")).toEqual(interruptListeners);
253256
expect(vi.getTimerCount()).toBe(0);

‎packages/commands/tests/deploy-legacy-write.test.ts‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { httpTransport } from "bailian-cli-core";
12
import {
23
BailianError,
34
ExitCode,
@@ -124,12 +125,14 @@ beforeEach(() => {
124125
stderr += String(chunk);
125126
return true;
126127
});
127-
vi.stubGlobal("fetch", vi.fn().mockRejectedValue(new Error("Real network is forbidden")));
128+
vi.spyOn(httpTransport, "fetch").mockImplementation(
129+
vi.fn().mockRejectedValue(new Error("Real network is forbidden")),
130+
);
128131
});
129132

130133
afterEach(() => {
131134
try {
132-
expect(fetch).not.toHaveBeenCalled();
135+
expect(httpTransport.fetch).not.toHaveBeenCalled();
133136
for (const mock of mocks) expect(mock.request).not.toHaveBeenCalled();
134137
if (vi.isFakeTimers()) expect(vi.getTimerCount()).toBe(0);
135138
} finally {

‎packages/commands/tests/deploy-query.test.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { httpTransport } from "bailian-cli-core";
12
import {
23
BailianError,
34
ExitCode,
@@ -137,8 +138,7 @@ beforeEach(() => {
137138
stdout += String(chunk);
138139
return true;
139140
});
140-
vi.stubGlobal(
141-
"fetch",
141+
vi.spyOn(httpTransport, "fetch").mockImplementation(
142142
vi.fn().mockRejectedValue(new Error("Real network is forbidden in deploy query tests")),
143143
);
144144
});
@@ -153,7 +153,7 @@ afterEach(() => {
153153
expect(request).not.toHaveProperty("body");
154154
}
155155
}
156-
expect(fetch).not.toHaveBeenCalled();
156+
expect(httpTransport.fetch).not.toHaveBeenCalled();
157157
if (vi.isFakeTimers()) expect(vi.getTimerCount()).toBe(0);
158158
} finally {
159159
vi.useRealTimers();

‎packages/commands/tests/deploy-wait-http.test.ts‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,11 @@ const CONFIG_DIRECTORY = fileURLToPath(
1515
// Keep real loopback fetch, but block background npm/telemetry requests before
1616
// they reach the network. Disallow redirects so local responses cannot escape.
1717
const LOCAL_FETCH_ONLY = `
18-
const realFetch = globalThis.fetch;
19-
globalThis.fetch = async (input, init) => {
18+
import { createRequire } from "node:module";
19+
const require = createRequire(${JSON.stringify(fileURLToPath(new URL("../../core/package.json", import.meta.url)))});
20+
const undici = require("undici");
21+
const realFetch = undici.fetch;
22+
undici.fetch = globalThis.fetch = async (input, init) => {
2023
const url = new URL(input instanceof Request ? input.url : input);
2124
if (url.protocol !== "http:" || url.hostname !== "127.0.0.1") {
2225
throw new Error("Remote fetch blocked for deploy HTTP regression");

‎packages/commands/tests/e2e/auth.e2e.test.ts‎

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { httpTransport } from "bailian-cli-core";
12
import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "fs";
23
import { createServer, type Server } from "http";
34
import { tmpdir } from "os";
@@ -163,9 +164,9 @@ describe("e2e: auth", () => {
163164
console_switch_agent: 123,
164165
};
165166
await writeConfigFile(previous, "token-plan");
166-
const originalFetch = globalThis.fetch;
167+
const originalFetch = httpTransport.fetch;
167168
const requests: string[] = [];
168-
globalThis.fetch = async (input, init) => {
169+
httpTransport.fetch = async (input, init) => {
169170
const endpoint = new URL(input instanceof Request ? input.url : input);
170171
requests.push(endpoint.hostname);
171172
expect(readConfigFile("token-plan")).toEqual(previous);
@@ -246,7 +247,7 @@ describe("e2e: auth", () => {
246247
],
247248
);
248249
} finally {
249-
globalThis.fetch = originalFetch;
250+
httpTransport.fetch = originalFetch;
250251
}
251252
});
252253
},
@@ -354,9 +355,9 @@ describe("e2e: auth", () => {
354355
const stdin = vi
355356
.spyOn(process, "stdin", "get")
356357
.mockReturnValue(input as typeof process.stdin);
357-
const originalFetch = globalThis.fetch;
358+
const originalFetch = httpTransport.fetch;
358359
let validations = 0;
359-
globalThis.fetch = async (url, init) => {
360+
httpTransport.fetch = async (url, init) => {
360361
const endpoint = new URL(url instanceof Request ? url.url : url);
361362
expect(endpoint.hostname).toBe("bailian-singapore-cs.alibabacloud.com");
362363
expect(endpoint.searchParams.get("api")).toBe(
@@ -424,7 +425,7 @@ describe("e2e: auth", () => {
424425
} finally {
425426
stdin.mockRestore();
426427
input.destroy();
427-
globalThis.fetch = originalFetch;
428+
httpTransport.fetch = originalFetch;
428429
}
429430
});
430431
},

0 commit comments

Comments
 (0)