pi-freeflow 1.8.0 → 1.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -1
- package/package.json +1 -1
- package/src/config.ts +13 -0
- package/src/health.ts +11 -6
- package/src/index.ts +140 -26
- package/src/proxy.ts +59 -1
package/README.md
CHANGED
|
@@ -269,7 +269,7 @@ Current size: about 11.3k lines including tests. 230 tests pass, typecheck clean
|
|
|
269
269
|
### FAQ
|
|
270
270
|
|
|
271
271
|
**Do I need API keys?**
|
|
272
|
-
No. Kilo uses `Bearer kilo-free`, OpenCode
|
|
272
|
+
No. Kilo uses `Bearer kilo-free`, OpenCode free models are anonymous (no header needed).
|
|
273
273
|
|
|
274
274
|
**What if all relays are 429?**
|
|
275
275
|
Proxy tries direct. If that is also 429, Pi shows the rate limit. That number is the global upstream cap; without relays you would hit the same wall.
|
|
@@ -277,6 +277,12 @@ Proxy tries direct. If that is also 429, Pi shows the rate limit. That number is
|
|
|
277
277
|
**Can I use without relays?**
|
|
278
278
|
Yes. `/freeflow off` → direct. Add relays later to scale.
|
|
279
279
|
|
|
280
|
+
**What happens when I update to a new version?**
|
|
281
|
+
The local proxy daemon is shared across sessions on port 28180. On upgrade, the new extension
|
|
282
|
+
detects a stale daemon (mismatched internal version) and replaces it automatically — no manual
|
|
283
|
+
kill, no restart of other sessions required. If an old daemon cannot be replaced (e.g. port held
|
|
284
|
+
by an unrelated process), it falls back to reusing it with a warning.
|
|
285
|
+
|
|
280
286
|
**Where's the normalizer?**
|
|
281
287
|
Deleted in 1.3.0. If zai/qwen/deepseek thinking broke before, it's fixed now because host handles it.
|
|
282
288
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-freeflow",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.8.
|
|
4
|
+
"version": "1.8.1",
|
|
5
5
|
"description": "Thin provider for OMP/Pi — model list + dumb relay proxy + log; host pi-ai owns thinking/normalization",
|
|
6
6
|
"main": "extensions/index.ts",
|
|
7
7
|
"types": "src/index.ts",
|
package/src/config.ts
CHANGED
|
@@ -6,8 +6,21 @@ import { randomUUID } from "node:crypto";
|
|
|
6
6
|
import { homedir } from "node:os";
|
|
7
7
|
import path from "node:path";
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
9
|
+
import { readFileSync } from "node:fs";
|
|
9
10
|
import type { Upstream } from "./types.ts";
|
|
10
11
|
|
|
12
|
+
// Package version — stale-daemon detection in the shared-port reuse path.
|
|
13
|
+
let PKG_VERSION = "0.0.0";
|
|
14
|
+
try {
|
|
15
|
+
const pkgPath = path.join(path.dirname(fileURLToPath(import.meta.url)), "../package.json");
|
|
16
|
+
const raw: unknown = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
17
|
+
if (raw && typeof raw === "object" && "version" in raw) {
|
|
18
|
+
const v = raw.version;
|
|
19
|
+
if (typeof v === "string" && v) PKG_VERSION = v;
|
|
20
|
+
}
|
|
21
|
+
} catch {}
|
|
22
|
+
export { PKG_VERSION };
|
|
23
|
+
|
|
11
24
|
// ── Upstream endpoints ──────────────────────────────────────────────
|
|
12
25
|
export const UPSTREAM_OPENCODE = "https://opencode.ai/zen";
|
|
13
26
|
export const KILO_CHAT_URL = "https://api.kilo.ai/api/gateway/chat/completions";
|
package/src/health.ts
CHANGED
|
@@ -6,8 +6,7 @@
|
|
|
6
6
|
import type * as http from "node:http";
|
|
7
7
|
import { ALL_MODELS } from "./models.ts";
|
|
8
8
|
import { getActiveRelayState, getRelayHealth, isRelayHealthy } from "./relay-state.ts";
|
|
9
|
-
import { PORT } from "./config.ts";
|
|
10
|
-
import { getClientIP } from "./proxy.ts";
|
|
9
|
+
import { PKG_VERSION, PORT } from "./config.ts";
|
|
11
10
|
|
|
12
11
|
export interface HealthRelayInfo {
|
|
13
12
|
url: string;
|
|
@@ -24,8 +23,8 @@ export interface HealthData {
|
|
|
24
23
|
enabled: boolean;
|
|
25
24
|
relays: HealthRelayInfo[];
|
|
26
25
|
catalog: number;
|
|
26
|
+
version: string;
|
|
27
27
|
}
|
|
28
|
-
|
|
29
28
|
/**
|
|
30
29
|
* Collect current health snapshot.
|
|
31
30
|
* @param portOverride - actual listening port (defaults to config PORT)
|
|
@@ -50,6 +49,7 @@ export function getHealthData(portOverride?: number): HealthData {
|
|
|
50
49
|
enabled: Boolean(state.enabled),
|
|
51
50
|
relays,
|
|
52
51
|
catalog: ALL_MODELS.length,
|
|
52
|
+
version: PKG_VERSION,
|
|
53
53
|
};
|
|
54
54
|
}
|
|
55
55
|
|
|
@@ -76,13 +76,18 @@ export function handleHealthRequest(
|
|
|
76
76
|
return false;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
const isHealthPath =
|
|
80
|
-
pathname === "/_health" || pathname === "/health" || pathname.endsWith("/health");
|
|
79
|
+
const isHealthPath = pathname === "/_health" || pathname === "/health";
|
|
81
80
|
if (req.method !== "GET" || !isHealthPath) {
|
|
82
81
|
return false;
|
|
83
82
|
}
|
|
84
83
|
|
|
85
|
-
const
|
|
84
|
+
const sock: unknown = req.socket;
|
|
85
|
+
let rawIp = "";
|
|
86
|
+
if (sock && typeof sock === "object" && "remoteAddress" in sock) {
|
|
87
|
+
const v = sock.remoteAddress;
|
|
88
|
+
if (typeof v === "string") rawIp = v;
|
|
89
|
+
}
|
|
90
|
+
const clientIP = rawIp.startsWith("::ffff:") ? rawIp.slice(7) : rawIp;
|
|
86
91
|
if (!isLoopbackIP(clientIP)) {
|
|
87
92
|
const body = JSON.stringify({ error: "forbidden" });
|
|
88
93
|
res.writeHead(403, { "content-type": "application/json", "content-length": Buffer.byteLength(body) });
|
package/src/index.ts
CHANGED
|
@@ -18,10 +18,10 @@ import {
|
|
|
18
18
|
setAliveCatalog,
|
|
19
19
|
} from "./catalog.ts";
|
|
20
20
|
import { createCommandSpec, updateStatusBar } from "./commands.ts";
|
|
21
|
-
import { HOST, LEGACY_PORT, ONBOARDED_FLAG_FILE, PORT } from "./config.ts";
|
|
21
|
+
import { HOST, LEGACY_PORT, ONBOARDED_FLAG_FILE, PKG_VERSION, PORT } from "./config.ts";
|
|
22
22
|
import { log, logInfo, logWarn } from "./logger.ts";
|
|
23
23
|
import { ALL_MODELS, KILO_MODEL_IDS, MODEL_MAP, resolveCanonicalModelId } from "./models.ts";
|
|
24
|
-
import { isProxyAlive, startProxy } from "./proxy.ts";
|
|
24
|
+
import { getDaemonVersion, isProxyAlive, killPortHolder, startProxy } from "./proxy.ts";
|
|
25
25
|
import { resetRateLimits } from "./rate-limiter.ts";
|
|
26
26
|
import { checkForUpdateInBackground } from "./update-checker.ts";
|
|
27
27
|
import {
|
|
@@ -174,18 +174,81 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
174
174
|
// 2. Single-Port Shared Pattern: Check if daemon is already running (e.g. parent session)
|
|
175
175
|
// Dual-probe: probe current PORT first; if missing, check LEGACY_PORT so existing v1.4.9
|
|
176
176
|
// sessions on 18080 are seamlessly reused without split-brain or duplicate daemons.
|
|
177
|
+
// Stale-daemon heal: if the alive daemon reports a different version (e.g. 1.7.1 vs 1.8.0
|
|
178
|
+
// after an upgrade), the auth fix never loads for the new session. Detect via /_health
|
|
179
|
+
// and best-effort replace the stale holder so old users are auto-healed.
|
|
177
180
|
let alreadyRunning = await isProxyAlive(PORT);
|
|
178
181
|
if (alreadyRunning) {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
182
|
+
const remoteVer = await getDaemonVersion(PORT);
|
|
183
|
+
if (remoteVer !== null && remoteVer !== PKG_VERSION) {
|
|
184
|
+
logWarn(`stale proxy daemon v${remoteVer} on :${PORT} (need v${PKG_VERSION}) — replacing`, {
|
|
185
|
+
remoteVer,
|
|
186
|
+
expected: PKG_VERSION,
|
|
187
|
+
});
|
|
188
|
+
const killed = await killPortHolder(PORT);
|
|
189
|
+
if (killed) {
|
|
190
|
+
for (let i = 0; i < 10; i++) {
|
|
191
|
+
await new Promise<void>((r) => setTimeout(r, 200));
|
|
192
|
+
if (!(await isProxyAlive(PORT))) break;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if (!(await isProxyAlive(PORT))) {
|
|
196
|
+
try {
|
|
197
|
+
const r = await startProxy();
|
|
198
|
+
server = r.server;
|
|
199
|
+
actualPort = r.port;
|
|
200
|
+
alreadyRunning = false;
|
|
201
|
+
} catch (e) {
|
|
202
|
+
log("error", "stale daemon replaced but fresh bind failed — reusing stale as fallback", {
|
|
203
|
+
error: String(e),
|
|
204
|
+
});
|
|
205
|
+
actualPort = PORT;
|
|
206
|
+
}
|
|
207
|
+
} else {
|
|
208
|
+
logInfo(`Reusing existing pi-freeflow proxy daemon on http://${HOST}:${PORT} (stale kill did not free port)`);
|
|
209
|
+
actualPort = PORT;
|
|
210
|
+
}
|
|
211
|
+
} else {
|
|
212
|
+
logInfo(`Reusing existing pi-freeflow proxy daemon on http://${HOST}:${PORT}`);
|
|
213
|
+
actualPort = PORT;
|
|
214
|
+
}
|
|
183
215
|
} else if (PORT !== LEGACY_PORT && (await isProxyAlive(LEGACY_PORT))) {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
216
|
+
const remoteVer = await getDaemonVersion(LEGACY_PORT);
|
|
217
|
+
if (remoteVer !== null && remoteVer !== PKG_VERSION) {
|
|
218
|
+
logWarn(`stale legacy daemon v${remoteVer} on :${LEGACY_PORT} (need v${PKG_VERSION}) — replacing`, {
|
|
219
|
+
remoteVer,
|
|
220
|
+
expected: PKG_VERSION,
|
|
221
|
+
});
|
|
222
|
+
const killed = await killPortHolder(LEGACY_PORT);
|
|
223
|
+
if (killed) {
|
|
224
|
+
for (let i = 0; i < 10; i++) {
|
|
225
|
+
await new Promise<void>((r) => setTimeout(r, 200));
|
|
226
|
+
if (!(await isProxyAlive(LEGACY_PORT))) break;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
if (!(await isProxyAlive(LEGACY_PORT)) && !(await isProxyAlive(PORT))) {
|
|
230
|
+
try {
|
|
231
|
+
const r = await startProxy();
|
|
232
|
+
server = r.server;
|
|
233
|
+
actualPort = r.port;
|
|
234
|
+
alreadyRunning = false;
|
|
235
|
+
} catch (e) {
|
|
236
|
+
log("error", "stale legacy daemon replaced but fresh bind failed — reusing as fallback", {
|
|
237
|
+
error: String(e),
|
|
238
|
+
});
|
|
239
|
+
alreadyRunning = true;
|
|
240
|
+
actualPort = LEGACY_PORT;
|
|
241
|
+
}
|
|
242
|
+
} else {
|
|
243
|
+
logInfo(`Reusing existing legacy pi-freeflow proxy daemon on http://${HOST}:${LEGACY_PORT}`);
|
|
244
|
+
alreadyRunning = true;
|
|
245
|
+
actualPort = LEGACY_PORT;
|
|
246
|
+
}
|
|
247
|
+
} else {
|
|
248
|
+
logInfo(`Reusing existing legacy pi-freeflow proxy daemon on http://${HOST}:${LEGACY_PORT}`);
|
|
249
|
+
alreadyRunning = true;
|
|
250
|
+
actualPort = LEGACY_PORT;
|
|
251
|
+
}
|
|
189
252
|
} else {
|
|
190
253
|
try {
|
|
191
254
|
const r = await startProxy();
|
|
@@ -200,8 +263,6 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
200
263
|
return;
|
|
201
264
|
}
|
|
202
265
|
}
|
|
203
|
-
|
|
204
|
-
// 3. Instant 0ms Static Catalog Registration
|
|
205
266
|
// Register static models immediately on boot so Pi/OMP picker is populated with zero latency!
|
|
206
267
|
const registeredCatalog: RegisteredModel[] = ALL_MODELS.map((m) => ({
|
|
207
268
|
...m,
|
|
@@ -215,27 +276,80 @@ export default async function (pi: ExtensionAPI): Promise<void> {
|
|
|
215
276
|
buildProviderConfig(models, actualPort),
|
|
216
277
|
);
|
|
217
278
|
};
|
|
218
|
-
registerCatalog(registeredCatalog);
|
|
219
|
-
// Self-heal: if this session attached to an external daemon that later died,
|
|
220
|
-
// re-bind on the next lifecycle event so model calls recover without a restart.
|
|
221
279
|
let ensuringDaemon = false;
|
|
222
280
|
const ensureDaemon = async (): Promise<void> => {
|
|
223
281
|
if (ensuringDaemon) return;
|
|
224
282
|
ensuringDaemon = true;
|
|
225
283
|
try {
|
|
226
|
-
if (await isProxyAlive(actualPort))
|
|
227
|
-
|
|
284
|
+
if (await isProxyAlive(actualPort)) {
|
|
285
|
+
const v = await getDaemonVersion(actualPort);
|
|
286
|
+
if (v === null || v === PKG_VERSION) return;
|
|
287
|
+
logWarn(`proxy on :${actualPort} is stale v${v} (need v${PKG_VERSION}) — replacing`, {
|
|
288
|
+
remoteVer: v,
|
|
289
|
+
expected: PKG_VERSION,
|
|
290
|
+
});
|
|
291
|
+
const killed = await killPortHolder(actualPort);
|
|
292
|
+
if (killed) {
|
|
293
|
+
for (let i = 0; i < 10; i++) {
|
|
294
|
+
await new Promise<void>((r) => setTimeout(r, 200));
|
|
295
|
+
if (!(await isProxyAlive(actualPort))) break;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
if (await isProxyAlive(actualPort)) return;
|
|
299
|
+
}
|
|
228
300
|
if (await isProxyAlive(PORT)) {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
301
|
+
const v = await getDaemonVersion(PORT);
|
|
302
|
+
if (v !== null && v !== PKG_VERSION) {
|
|
303
|
+
logWarn(`proxy on :${PORT} is stale v${v} (need v${PKG_VERSION}) — replacing`, {
|
|
304
|
+
remoteVer: v,
|
|
305
|
+
expected: PKG_VERSION,
|
|
306
|
+
});
|
|
307
|
+
const killed = await killPortHolder(PORT);
|
|
308
|
+
if (killed) {
|
|
309
|
+
for (let i = 0; i < 10; i++) {
|
|
310
|
+
await new Promise<void>((r) => setTimeout(r, 200));
|
|
311
|
+
if (!(await isProxyAlive(PORT))) break;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
if (await isProxyAlive(PORT)) {
|
|
315
|
+
actualPort = PORT;
|
|
316
|
+
registerCatalog(getAliveCatalog());
|
|
317
|
+
logInfo(`Re-attached to proxy daemon on http://${HOST}:${PORT} (stale kill did not free port)`);
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
} else {
|
|
321
|
+
actualPort = PORT;
|
|
322
|
+
registerCatalog(getAliveCatalog());
|
|
323
|
+
logInfo(`Re-attached to proxy daemon on http://${HOST}:${PORT}`);
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
233
326
|
}
|
|
234
327
|
if (PORT !== LEGACY_PORT && (await isProxyAlive(LEGACY_PORT))) {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
328
|
+
const v = await getDaemonVersion(LEGACY_PORT);
|
|
329
|
+
if (v !== null && v !== PKG_VERSION) {
|
|
330
|
+
logWarn(`legacy proxy on :${LEGACY_PORT} is stale v${v} (need v${PKG_VERSION}) — replacing`, {
|
|
331
|
+
remoteVer: v,
|
|
332
|
+
expected: PKG_VERSION,
|
|
333
|
+
});
|
|
334
|
+
const killed = await killPortHolder(LEGACY_PORT);
|
|
335
|
+
if (killed) {
|
|
336
|
+
for (let i = 0; i < 10; i++) {
|
|
337
|
+
await new Promise<void>((r) => setTimeout(r, 200));
|
|
338
|
+
if (!(await isProxyAlive(LEGACY_PORT))) break;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
if (await isProxyAlive(LEGACY_PORT)) {
|
|
342
|
+
actualPort = LEGACY_PORT;
|
|
343
|
+
registerCatalog(getAliveCatalog());
|
|
344
|
+
logInfo(`Re-attached to legacy proxy daemon on http://${HOST}:${LEGACY_PORT} (stale kill did not free port)`);
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
} else {
|
|
348
|
+
actualPort = LEGACY_PORT;
|
|
349
|
+
registerCatalog(getAliveCatalog());
|
|
350
|
+
logInfo(`Re-attached to legacy proxy daemon on http://${HOST}:${LEGACY_PORT}`);
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
239
353
|
}
|
|
240
354
|
const r = await startProxy();
|
|
241
355
|
if (r.server) server = r.server;
|
package/src/proxy.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { randomUUID } from "node:crypto";
|
|
9
|
+
import { execSync } from "node:child_process";
|
|
9
10
|
import * as http from "node:http";
|
|
10
11
|
import * as https from "node:https";
|
|
11
12
|
import { handleHealthRequest } from "./health.ts";
|
|
@@ -112,6 +113,7 @@ export function sanitizeHeaders(
|
|
|
112
113
|
return sanitized;
|
|
113
114
|
}
|
|
114
115
|
export async function isProxyAlive(port: number): Promise<boolean> {
|
|
116
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) return false;
|
|
115
117
|
try {
|
|
116
118
|
const res = await fetch(`http://${HOST}:${port}/v1/models`, {
|
|
117
119
|
signal: AbortSignal.timeout(500),
|
|
@@ -123,6 +125,62 @@ export async function isProxyAlive(port: number): Promise<boolean> {
|
|
|
123
125
|
}
|
|
124
126
|
}
|
|
125
127
|
|
|
128
|
+
export async function getDaemonVersion(port: number): Promise<string | null> {
|
|
129
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) return null;
|
|
130
|
+
try {
|
|
131
|
+
const res = await fetch(`http://${HOST}:${port}/_health`, {
|
|
132
|
+
signal: AbortSignal.timeout(800),
|
|
133
|
+
});
|
|
134
|
+
if (!res.ok) return null;
|
|
135
|
+
const data: unknown = await res.json();
|
|
136
|
+
if (data && typeof data === "object" && "version" in data) {
|
|
137
|
+
const v = data.version;
|
|
138
|
+
if (typeof v === "string" && v) return v;
|
|
139
|
+
}
|
|
140
|
+
// Alive but no version field = pre-1.8.0 daemon (never embedded it) — stale.
|
|
141
|
+
return "";
|
|
142
|
+
} catch {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export async function killPortHolder(port: number): Promise<boolean> {
|
|
148
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) return false;
|
|
149
|
+
try {
|
|
150
|
+
if (process.platform === "win32") {
|
|
151
|
+
try {
|
|
152
|
+
const out = execSync(`netstat -ano | findstr :${port}`, { encoding: "utf8", timeout: 3000 }) as string;
|
|
153
|
+
for (const line of out.split("\n")) {
|
|
154
|
+
if (!line.includes("LISTENING")) continue;
|
|
155
|
+
if (!line.includes(`:${port}`)) continue;
|
|
156
|
+
const parts = line.trim().split(/\s+/);
|
|
157
|
+
const pid = parts[parts.length - 1];
|
|
158
|
+
if (!pid || !/^\d+$/.test(pid)) continue;
|
|
159
|
+
// Windows netstat report: 127.0.0.1:38180 ... LISTENING/PID
|
|
160
|
+
if (Number(pid) === process.pid) continue; // never self-kill
|
|
161
|
+
execSync(`taskkill /F /PID ${pid}`, { timeout: 3000, stdio: "ignore" });
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
} catch {}
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
try {
|
|
168
|
+
const out = execSync(`lsof -ti tcp:${port} 2>/dev/null || fuser -n tcp ${port} 2>/dev/null`, {
|
|
169
|
+
encoding: "utf8",
|
|
170
|
+
timeout: 3000,
|
|
171
|
+
}) as string;
|
|
172
|
+
const pid = out.trim().split(/\s+/)[0];
|
|
173
|
+
if (!pid || !/^\d+$/.test(pid)) return false;
|
|
174
|
+
execSync(`kill -9 ${pid}`, { timeout: 3000, stdio: "ignore" });
|
|
175
|
+
return true;
|
|
176
|
+
} catch {
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
} catch {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
126
184
|
/**
|
|
127
185
|
* Tagged abort reason for the proxy-internal header-wait timeout.
|
|
128
186
|
* relayFetch rethrows AbortErrors untouched, and stream-pipe recognizes
|
|
@@ -179,7 +237,7 @@ export function startProxy(
|
|
|
179
237
|
reqPathname = new URL(req.url ?? "/", `http://${HOST}`).pathname;
|
|
180
238
|
} catch {}
|
|
181
239
|
// Loopback-only health endpoint — always accessible even when widget hidden
|
|
182
|
-
if (req.method === "GET" && reqPathname !== null && (reqPathname === "/_health" || reqPathname === "/health"
|
|
240
|
+
if (req.method === "GET" && reqPathname !== null && (reqPathname === "/_health" || reqPathname === "/health")) {
|
|
183
241
|
const addr = server.address();
|
|
184
242
|
const realPort = addr && typeof addr === "object" ? (addr as { port: number }).port : basePort;
|
|
185
243
|
if (handleHealthRequest(req, res, realPort)) return;
|