@dev-anywhere/relay 0.4.2 → 0.4.3
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/dist/{chunk-ERH2EO6I.js → chunk-OJABHE5C.js} +212 -17
- package/dist/chunk-OJABHE5C.js.map +1 -0
- package/dist/handlers/client.d.ts.map +1 -1
- package/dist/handlers/proxy.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/latency-probes.d.ts +18 -0
- package/dist/latency-probes.d.ts.map +1 -0
- package/dist/server.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-ERH2EO6I.js.map +0 -1
|
@@ -536,6 +536,7 @@ var SessionHistoryMessageSchema = z8.object({
|
|
|
536
536
|
cursor: z8.string().optional()
|
|
537
537
|
});
|
|
538
538
|
var RequestIdShape = { requestId: IdSchema.optional() };
|
|
539
|
+
var RequiredRequestIdShape = { requestId: IdSchema };
|
|
539
540
|
var ControlErrorCodeSchema = z8.enum(Object.values(ControlErrorCode));
|
|
540
541
|
var RequestErrorShape = {
|
|
541
542
|
error: z8.string().optional(),
|
|
@@ -618,6 +619,30 @@ var relayControlDefinitions = [
|
|
|
618
619
|
...RequestErrorShape,
|
|
619
620
|
capabilities: VoiceCapabilitiesSchema.optional()
|
|
620
621
|
}),
|
|
622
|
+
// Lightweight latency probes. These measure synthetic round-trip latency for the transport
|
|
623
|
+
// segments and intentionally stay separate from PTY input echo tracing.
|
|
624
|
+
control("latency_web_relay_ping", RequiredRequestIdShape),
|
|
625
|
+
control("latency_web_relay_pong", {
|
|
626
|
+
...RequiredRequestIdShape,
|
|
627
|
+
relayNow: z8.number().optional()
|
|
628
|
+
}),
|
|
629
|
+
control("latency_relay_proxy_request", RequiredRequestIdShape),
|
|
630
|
+
control("latency_relay_proxy_response", {
|
|
631
|
+
...RequiredRequestIdShape,
|
|
632
|
+
success: z8.boolean(),
|
|
633
|
+
rttMs: z8.number().nonnegative().optional(),
|
|
634
|
+
error: z8.string().optional()
|
|
635
|
+
}),
|
|
636
|
+
control("latency_relay_proxy_ping", {
|
|
637
|
+
...RequiredRequestIdShape,
|
|
638
|
+
relayNow: z8.number().optional()
|
|
639
|
+
}),
|
|
640
|
+
control("latency_relay_proxy_pong", {
|
|
641
|
+
...RequiredRequestIdShape,
|
|
642
|
+
proxyNow: z8.number().optional()
|
|
643
|
+
}),
|
|
644
|
+
control("latency_web_proxy_ping", RequiredRequestIdShape, "client_to_proxy"),
|
|
645
|
+
control("latency_web_proxy_pong", { ...RequiredRequestIdShape, proxyNow: z8.number().optional() }, "proxy_to_client"),
|
|
621
646
|
// 客户端注册协议
|
|
622
647
|
control("client_register", {
|
|
623
648
|
clientId: IdSchema
|
|
@@ -1244,7 +1269,7 @@ function healthRouter(registry, options = {}) {
|
|
|
1244
1269
|
}
|
|
1245
1270
|
|
|
1246
1271
|
// src/handlers/proxy.ts
|
|
1247
|
-
import { WebSocket as
|
|
1272
|
+
import { WebSocket as WebSocket4 } from "ws";
|
|
1248
1273
|
|
|
1249
1274
|
// src/router.ts
|
|
1250
1275
|
import { WebSocket as WebSocket2 } from "ws";
|
|
@@ -1331,6 +1356,100 @@ function routeClientMessage(raw, proxyId, clientWs, registry, logger, chaos) {
|
|
|
1331
1356
|
else proxyWs.send(raw);
|
|
1332
1357
|
}
|
|
1333
1358
|
|
|
1359
|
+
// src/latency-probes.ts
|
|
1360
|
+
import { performance } from "perf_hooks";
|
|
1361
|
+
import { WebSocket as WebSocket3 } from "ws";
|
|
1362
|
+
var DEFAULT_RELAY_PROXY_PROBE_TIMEOUT_MS = 3e3;
|
|
1363
|
+
var pendingRelayProxyProbes = /* @__PURE__ */ new Map();
|
|
1364
|
+
function keyFor(proxyId, requestId) {
|
|
1365
|
+
return `${proxyId}:${requestId}`;
|
|
1366
|
+
}
|
|
1367
|
+
function sendClientResponse(clientWs, response, chaos) {
|
|
1368
|
+
if (clientWs.readyState !== WebSocket3.OPEN) return;
|
|
1369
|
+
const raw = serializeControl(response);
|
|
1370
|
+
if (chaos) {
|
|
1371
|
+
chaos.send(clientWs, raw, {
|
|
1372
|
+
direction: "proxy_to_client",
|
|
1373
|
+
type: "latency_relay_proxy_response"
|
|
1374
|
+
});
|
|
1375
|
+
return;
|
|
1376
|
+
}
|
|
1377
|
+
clientWs.send(raw);
|
|
1378
|
+
}
|
|
1379
|
+
function startRelayProxyLatencyProbe({
|
|
1380
|
+
requestId,
|
|
1381
|
+
proxyId,
|
|
1382
|
+
proxyWs,
|
|
1383
|
+
clientWs,
|
|
1384
|
+
logger,
|
|
1385
|
+
chaos,
|
|
1386
|
+
timeoutMs = DEFAULT_RELAY_PROXY_PROBE_TIMEOUT_MS
|
|
1387
|
+
}) {
|
|
1388
|
+
const key = keyFor(proxyId, requestId);
|
|
1389
|
+
const existing = pendingRelayProxyProbes.get(key);
|
|
1390
|
+
if (existing) {
|
|
1391
|
+
clearTimeout(existing.timer);
|
|
1392
|
+
pendingRelayProxyProbes.delete(key);
|
|
1393
|
+
}
|
|
1394
|
+
const startedAt = performance.now();
|
|
1395
|
+
const timer = setTimeout(() => {
|
|
1396
|
+
pendingRelayProxyProbes.delete(key);
|
|
1397
|
+
sendClientResponse(
|
|
1398
|
+
clientWs,
|
|
1399
|
+
{
|
|
1400
|
+
type: "latency_relay_proxy_response",
|
|
1401
|
+
requestId,
|
|
1402
|
+
success: false,
|
|
1403
|
+
error: "Relay \u5230\u5F00\u53D1\u673A\u6D4B\u901F\u8D85\u65F6"
|
|
1404
|
+
},
|
|
1405
|
+
chaos
|
|
1406
|
+
);
|
|
1407
|
+
logger.warn({ proxyId, requestId }, "Relay-proxy latency probe timed out");
|
|
1408
|
+
}, timeoutMs);
|
|
1409
|
+
pendingRelayProxyProbes.set(key, {
|
|
1410
|
+
proxyId,
|
|
1411
|
+
requestId,
|
|
1412
|
+
clientWs,
|
|
1413
|
+
startedAt,
|
|
1414
|
+
timer,
|
|
1415
|
+
chaos
|
|
1416
|
+
});
|
|
1417
|
+
const raw = serializeControl({
|
|
1418
|
+
type: "latency_relay_proxy_ping",
|
|
1419
|
+
requestId,
|
|
1420
|
+
relayNow: Date.now()
|
|
1421
|
+
});
|
|
1422
|
+
if (chaos) {
|
|
1423
|
+
chaos.send(proxyWs, raw, { direction: "client_to_proxy", type: "latency_relay_proxy_ping" });
|
|
1424
|
+
return;
|
|
1425
|
+
}
|
|
1426
|
+
proxyWs.send(raw);
|
|
1427
|
+
}
|
|
1428
|
+
function completeRelayProxyLatencyProbe({
|
|
1429
|
+
proxyId,
|
|
1430
|
+
requestId,
|
|
1431
|
+
logger
|
|
1432
|
+
}) {
|
|
1433
|
+
const key = keyFor(proxyId, requestId);
|
|
1434
|
+
const pending = pendingRelayProxyProbes.get(key);
|
|
1435
|
+
if (!pending) return false;
|
|
1436
|
+
pendingRelayProxyProbes.delete(key);
|
|
1437
|
+
clearTimeout(pending.timer);
|
|
1438
|
+
const rttMs = performance.now() - pending.startedAt;
|
|
1439
|
+
sendClientResponse(
|
|
1440
|
+
pending.clientWs,
|
|
1441
|
+
{
|
|
1442
|
+
type: "latency_relay_proxy_response",
|
|
1443
|
+
requestId,
|
|
1444
|
+
success: true,
|
|
1445
|
+
rttMs
|
|
1446
|
+
},
|
|
1447
|
+
pending.chaos
|
|
1448
|
+
);
|
|
1449
|
+
logger.debug({ proxyId, requestId, rttMs }, "Relay-proxy latency probe completed");
|
|
1450
|
+
return true;
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1334
1453
|
// src/handlers/proxy.ts
|
|
1335
1454
|
var MAX_BINARY_FRAME_SIZE = 10 * 1024 * 1024;
|
|
1336
1455
|
var MAX_JSON_MESSAGE_SIZE = 1 * 1024 * 1024;
|
|
@@ -1399,7 +1518,7 @@ function handleProxyConnection(ws, registry, logger, chaos) {
|
|
|
1399
1518
|
}
|
|
1400
1519
|
const clients = registry.getClientsForProxy(proxyWs.proxyId);
|
|
1401
1520
|
for (const clientWs of clients) {
|
|
1402
|
-
if (clientWs.readyState ===
|
|
1521
|
+
if (clientWs.readyState === WebSocket4.OPEN) {
|
|
1403
1522
|
clientWs.send(data);
|
|
1404
1523
|
}
|
|
1405
1524
|
}
|
|
@@ -1453,6 +1572,24 @@ function handleProxyConnection(ws, registry, logger, chaos) {
|
|
|
1453
1572
|
logger.info({ proxyId: proxyWs.proxyId, count: sessions.length }, "Session sync received");
|
|
1454
1573
|
return;
|
|
1455
1574
|
}
|
|
1575
|
+
if (result.kind === "control" && result.message.type === "latency_relay_proxy_pong") {
|
|
1576
|
+
if (!proxyWs.proxyId) {
|
|
1577
|
+
rejectNotRegistered(proxyWs);
|
|
1578
|
+
return;
|
|
1579
|
+
}
|
|
1580
|
+
const completed = completeRelayProxyLatencyProbe({
|
|
1581
|
+
proxyId: proxyWs.proxyId,
|
|
1582
|
+
requestId: result.message.requestId,
|
|
1583
|
+
logger
|
|
1584
|
+
});
|
|
1585
|
+
if (!completed) {
|
|
1586
|
+
logger.debug(
|
|
1587
|
+
{ proxyId: proxyWs.proxyId, requestId: result.message.requestId },
|
|
1588
|
+
"Unmatched relay-proxy latency pong ignored"
|
|
1589
|
+
);
|
|
1590
|
+
}
|
|
1591
|
+
return;
|
|
1592
|
+
}
|
|
1456
1593
|
if (result.kind === "control") {
|
|
1457
1594
|
if (isProxyToClientRelayControlType(result.message.type)) {
|
|
1458
1595
|
if (!proxyWs.proxyId) {
|
|
@@ -1461,7 +1598,7 @@ function handleProxyConnection(ws, registry, logger, chaos) {
|
|
|
1461
1598
|
}
|
|
1462
1599
|
const clients = registry.getClientsForProxy(proxyWs.proxyId);
|
|
1463
1600
|
for (const clientWs of clients) {
|
|
1464
|
-
if (clientWs.readyState ===
|
|
1601
|
+
if (clientWs.readyState === WebSocket4.OPEN) {
|
|
1465
1602
|
if (chaos) {
|
|
1466
1603
|
chaos.send(clientWs, raw, {
|
|
1467
1604
|
direction: "proxy_to_client",
|
|
@@ -1533,13 +1670,13 @@ function handleProxyConnection(ws, registry, logger, chaos) {
|
|
|
1533
1670
|
}
|
|
1534
1671
|
|
|
1535
1672
|
// src/handlers/client.ts
|
|
1536
|
-
import { WebSocket as
|
|
1673
|
+
import { WebSocket as WebSocket7 } from "ws";
|
|
1537
1674
|
import { nanoid as nanoid3 } from "nanoid";
|
|
1538
1675
|
|
|
1539
1676
|
// src/voice/bailian-asr.ts
|
|
1540
1677
|
import { EventEmitter } from "events";
|
|
1541
1678
|
import { nanoid } from "nanoid";
|
|
1542
|
-
import { WebSocket as
|
|
1679
|
+
import { WebSocket as WebSocket5 } from "ws";
|
|
1543
1680
|
|
|
1544
1681
|
// src/voice/bailian-endpoints.ts
|
|
1545
1682
|
var BAILIAN_HOSTS = {
|
|
@@ -1558,7 +1695,7 @@ function bailianInferenceUrl(region) {
|
|
|
1558
1695
|
var OPEN = 1;
|
|
1559
1696
|
var END_OF_SPEECH_SILENCE_MS = 1200;
|
|
1560
1697
|
function defaultSocketFactory(url, options) {
|
|
1561
|
-
return new
|
|
1698
|
+
return new WebSocket5(url, options);
|
|
1562
1699
|
}
|
|
1563
1700
|
function extractRealtimePreview(payload) {
|
|
1564
1701
|
if (!payload || typeof payload !== "object") return null;
|
|
@@ -1735,10 +1872,10 @@ function createBailianAsrClient(config, options = {}) {
|
|
|
1735
1872
|
// src/voice/bailian-tts.ts
|
|
1736
1873
|
import { EventEmitter as EventEmitter2 } from "events";
|
|
1737
1874
|
import { nanoid as nanoid2 } from "nanoid";
|
|
1738
|
-
import { WebSocket as
|
|
1875
|
+
import { WebSocket as WebSocket6 } from "ws";
|
|
1739
1876
|
var OPEN2 = 1;
|
|
1740
1877
|
function defaultSocketFactory2(url, options) {
|
|
1741
|
-
return new
|
|
1878
|
+
return new WebSocket6(url, options);
|
|
1742
1879
|
}
|
|
1743
1880
|
function errorFromPayload(payload) {
|
|
1744
1881
|
if (!payload || typeof payload !== "object") return new Error("Bailian TTS error");
|
|
@@ -2240,6 +2377,22 @@ function rejectProxySelect(ws, requestId, proxyId) {
|
|
|
2240
2377
|
})
|
|
2241
2378
|
);
|
|
2242
2379
|
}
|
|
2380
|
+
function sendRelayProxyProbeFailure(ws, requestId, error, chaos) {
|
|
2381
|
+
const response = serializeControl({
|
|
2382
|
+
type: "latency_relay_proxy_response",
|
|
2383
|
+
requestId,
|
|
2384
|
+
success: false,
|
|
2385
|
+
error
|
|
2386
|
+
});
|
|
2387
|
+
if (chaos) {
|
|
2388
|
+
chaos.send(ws, response, {
|
|
2389
|
+
direction: "proxy_to_client",
|
|
2390
|
+
type: "latency_relay_proxy_response"
|
|
2391
|
+
});
|
|
2392
|
+
return;
|
|
2393
|
+
}
|
|
2394
|
+
ws.send(response);
|
|
2395
|
+
}
|
|
2243
2396
|
function handleClientConnection(ws, registry, logger, chaos, voiceConfigStore, voiceProviders) {
|
|
2244
2397
|
const clientWs = ws;
|
|
2245
2398
|
clientWs.isAlive = true;
|
|
@@ -2290,6 +2443,48 @@ function handleClientConnection(ws, registry, logger, chaos, voiceConfigStore, v
|
|
|
2290
2443
|
}
|
|
2291
2444
|
return;
|
|
2292
2445
|
}
|
|
2446
|
+
if (msg.type === "latency_web_relay_ping") {
|
|
2447
|
+
const response = serializeControl({
|
|
2448
|
+
type: "latency_web_relay_pong",
|
|
2449
|
+
requestId: msg.requestId,
|
|
2450
|
+
relayNow: Date.now()
|
|
2451
|
+
});
|
|
2452
|
+
if (chaos) {
|
|
2453
|
+
chaos.send(clientWs, response, {
|
|
2454
|
+
direction: "proxy_to_client",
|
|
2455
|
+
type: "latency_web_relay_pong"
|
|
2456
|
+
});
|
|
2457
|
+
} else {
|
|
2458
|
+
clientWs.send(response);
|
|
2459
|
+
}
|
|
2460
|
+
return;
|
|
2461
|
+
}
|
|
2462
|
+
if (msg.type === "latency_relay_proxy_request") {
|
|
2463
|
+
const targetProxyId = clientWs.boundProxyId;
|
|
2464
|
+
if (!targetProxyId) {
|
|
2465
|
+
sendRelayProxyProbeFailure(clientWs, msg.requestId, "\u5F53\u524D\u672A\u8FDE\u63A5\u5F00\u53D1\u673A", chaos);
|
|
2466
|
+
return;
|
|
2467
|
+
}
|
|
2468
|
+
const proxyWs = registry.getProxy(targetProxyId);
|
|
2469
|
+
if (!proxyWs || proxyWs.readyState !== WebSocket7.OPEN) {
|
|
2470
|
+
sendRelayProxyProbeFailure(
|
|
2471
|
+
clientWs,
|
|
2472
|
+
msg.requestId,
|
|
2473
|
+
`\u5F00\u53D1\u673A ${targetProxyId} \u4E0D\u5728\u7EBF`,
|
|
2474
|
+
chaos
|
|
2475
|
+
);
|
|
2476
|
+
return;
|
|
2477
|
+
}
|
|
2478
|
+
startRelayProxyLatencyProbe({
|
|
2479
|
+
requestId: msg.requestId,
|
|
2480
|
+
proxyId: targetProxyId,
|
|
2481
|
+
proxyWs,
|
|
2482
|
+
clientWs,
|
|
2483
|
+
logger,
|
|
2484
|
+
chaos
|
|
2485
|
+
});
|
|
2486
|
+
return;
|
|
2487
|
+
}
|
|
2293
2488
|
if (voiceConfigStore && handleVoiceConfigControl(msg, clientWs, voiceConfigStore, logger, voiceProviders)) {
|
|
2294
2489
|
return;
|
|
2295
2490
|
}
|
|
@@ -2300,7 +2495,7 @@ function handleClientConnection(ws, registry, logger, chaos, voiceConfigStore, v
|
|
|
2300
2495
|
return;
|
|
2301
2496
|
}
|
|
2302
2497
|
const proxyWs = registry.getProxy(targetProxyId);
|
|
2303
|
-
if (proxyWs && proxyWs.readyState ===
|
|
2498
|
+
if (proxyWs && proxyWs.readyState === WebSocket7.OPEN) {
|
|
2304
2499
|
if (chaos) chaos.send(proxyWs, raw, { direction: "client_to_proxy", type: msg.type });
|
|
2305
2500
|
else proxyWs.send(raw);
|
|
2306
2501
|
} else {
|
|
@@ -2408,7 +2603,7 @@ function setupHeartbeat(wss, interval = 3e4) {
|
|
|
2408
2603
|
}
|
|
2409
2604
|
|
|
2410
2605
|
// src/chaos.ts
|
|
2411
|
-
import { WebSocket as
|
|
2606
|
+
import { WebSocket as WebSocket8 } from "ws";
|
|
2412
2607
|
function parseRelayChaosFromEnv(env) {
|
|
2413
2608
|
const enabled = env.DEV_ANYWHERE_RELAY_CHAOS === "1";
|
|
2414
2609
|
const types = env.DEV_ANYWHERE_RELAY_CHAOS_TYPES?.split(",").map((type) => type.trim()).filter(Boolean);
|
|
@@ -2430,7 +2625,7 @@ function createRelayChaos(options, logger) {
|
|
|
2430
2625
|
return true;
|
|
2431
2626
|
}
|
|
2432
2627
|
function sendNow(ws, data) {
|
|
2433
|
-
if (ws.readyState ===
|
|
2628
|
+
if (ws.readyState === WebSocket8.OPEN) {
|
|
2434
2629
|
ws.send(data);
|
|
2435
2630
|
}
|
|
2436
2631
|
}
|
|
@@ -2547,9 +2742,9 @@ function createVoiceConfigStore(options = {}) {
|
|
|
2547
2742
|
}
|
|
2548
2743
|
|
|
2549
2744
|
// src/voice/asr-ws.ts
|
|
2550
|
-
import { WebSocket as
|
|
2745
|
+
import { WebSocket as WebSocket9 } from "ws";
|
|
2551
2746
|
function sendJson(ws, payload) {
|
|
2552
|
-
if (ws.readyState ===
|
|
2747
|
+
if (ws.readyState === WebSocket9.OPEN) {
|
|
2553
2748
|
ws.send(JSON.stringify(payload));
|
|
2554
2749
|
}
|
|
2555
2750
|
}
|
|
@@ -2622,9 +2817,9 @@ function handleVoiceAsrConnection(ws, store, logger, providers) {
|
|
|
2622
2817
|
}
|
|
2623
2818
|
|
|
2624
2819
|
// src/voice/tts-ws.ts
|
|
2625
|
-
import { WebSocket as
|
|
2820
|
+
import { WebSocket as WebSocket10 } from "ws";
|
|
2626
2821
|
function sendJson2(ws, payload) {
|
|
2627
|
-
if (ws.readyState ===
|
|
2822
|
+
if (ws.readyState === WebSocket10.OPEN) {
|
|
2628
2823
|
ws.send(JSON.stringify(payload));
|
|
2629
2824
|
}
|
|
2630
2825
|
}
|
|
@@ -2707,7 +2902,7 @@ function handleVoiceTtsConnection(ws, store, logger, providers) {
|
|
|
2707
2902
|
activeStats.audioBytes += chunk.byteLength;
|
|
2708
2903
|
activeStats.audioChunks += 1;
|
|
2709
2904
|
}
|
|
2710
|
-
if (ws.readyState ===
|
|
2905
|
+
if (ws.readyState === WebSocket10.OPEN) ws.send(chunk);
|
|
2711
2906
|
});
|
|
2712
2907
|
provider.on("finished", () => {
|
|
2713
2908
|
if (activeStats) logger.info(statsLogFields(activeStats), "Voice TTS finished");
|
|
@@ -3161,4 +3356,4 @@ export {
|
|
|
3161
3356
|
parseRelayChaosFromEnv,
|
|
3162
3357
|
createRelayServer
|
|
3163
3358
|
};
|
|
3164
|
-
//# sourceMappingURL=chunk-
|
|
3359
|
+
//# sourceMappingURL=chunk-OJABHE5C.js.map
|