openclaw-navigator 5.7.0 → 5.7.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/cli.mjs +46 -63
- package/package.json +1 -1
package/cli.mjs
CHANGED
|
@@ -1501,78 +1501,62 @@ function handleRequest(req, res) {
|
|
|
1501
1501
|
return;
|
|
1502
1502
|
}
|
|
1503
1503
|
|
|
1504
|
-
// ── SSE
|
|
1505
|
-
//
|
|
1506
|
-
//
|
|
1507
|
-
//
|
|
1508
|
-
// and
|
|
1504
|
+
// ── SSE→JSON for streaming endpoints (/api/chat) ──────────────────
|
|
1505
|
+
// The BFF returns SSE but the web UI's parser can't handle SSE through
|
|
1506
|
+
// a reverse proxy (it does JSON.parse on raw chunks without stripping
|
|
1507
|
+
// the "data: " prefix). So we collect the full SSE stream, extract the
|
|
1508
|
+
// text, and return a single JSON response. Also store + broadcast via WS.
|
|
1509
1509
|
if (isSSE && isStreamingEndpoint) {
|
|
1510
|
-
console.log(` ${DIM}SSE
|
|
1511
|
-
|
|
1512
|
-
// Disable TCP Nagle for immediate per-event delivery
|
|
1513
|
-
if (res.socket) res.socket.setNoDelay(true);
|
|
1514
|
-
|
|
1515
|
-
// Clean transfer headers — let Node.js manage chunked encoding
|
|
1516
|
-
delete headers["content-length"];
|
|
1517
|
-
delete headers["transfer-encoding"];
|
|
1518
|
-
res.writeHead(proxyRes.statusCode ?? 200, headers);
|
|
1510
|
+
console.log(` ${DIM}SSE→JSON collect: ${path}${RESET}`);
|
|
1519
1511
|
|
|
1520
1512
|
let fullText = "";
|
|
1521
|
-
let
|
|
1513
|
+
let sseData = "";
|
|
1522
1514
|
|
|
1523
1515
|
proxyRes.setEncoding("utf-8");
|
|
1524
1516
|
proxyRes.on("data", (chunk) => {
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
// Split by double-newline (SSE event boundary)
|
|
1528
|
-
const events = sseBuffer.split("\n\n");
|
|
1529
|
-
sseBuffer = events.pop() || ""; // Keep incomplete event in buffer
|
|
1530
|
-
|
|
1531
|
-
for (const event of events) {
|
|
1532
|
-
const trimmed = event.trim();
|
|
1533
|
-
if (!trimmed) continue;
|
|
1534
|
-
|
|
1535
|
-
// Forward the COMPLETE SSE event to browser (preserving data: prefix)
|
|
1536
|
-
res.write(trimmed + "\n\n");
|
|
1537
|
-
|
|
1538
|
-
// Tap for WebSocket broadcast
|
|
1539
|
-
for (const line of trimmed.split("\n")) {
|
|
1540
|
-
if (line.startsWith("data: ")) {
|
|
1541
|
-
const raw = line.slice(6).trim();
|
|
1542
|
-
if (raw === "[DONE]" || !raw) continue;
|
|
1543
|
-
try {
|
|
1544
|
-
const evt = JSON.parse(raw);
|
|
1545
|
-
const delta =
|
|
1546
|
-
evt.choices?.[0]?.delta?.content ||
|
|
1547
|
-
evt.delta?.text ||
|
|
1548
|
-
evt.text ||
|
|
1549
|
-
evt.content ||
|
|
1550
|
-
"";
|
|
1551
|
-
if (delta) {
|
|
1552
|
-
fullText += delta;
|
|
1553
|
-
broadcastToWS({
|
|
1554
|
-
type: "chat.delta",
|
|
1555
|
-
text: fullText,
|
|
1556
|
-
delta,
|
|
1557
|
-
sessionKey: "main",
|
|
1558
|
-
timestamp: Date.now(),
|
|
1559
|
-
});
|
|
1560
|
-
}
|
|
1561
|
-
} catch {
|
|
1562
|
-
/* non-JSON SSE event */
|
|
1563
|
-
}
|
|
1564
|
-
}
|
|
1565
|
-
}
|
|
1566
|
-
}
|
|
1517
|
+
sseData += chunk;
|
|
1567
1518
|
});
|
|
1568
1519
|
|
|
1569
1520
|
proxyRes.on("end", () => {
|
|
1570
|
-
//
|
|
1571
|
-
|
|
1572
|
-
|
|
1521
|
+
// Extract text from all SSE events
|
|
1522
|
+
for (const line of sseData.split("\n")) {
|
|
1523
|
+
if (line.startsWith("data: ")) {
|
|
1524
|
+
const raw = line.slice(6).trim();
|
|
1525
|
+
if (raw === "[DONE]" || !raw) continue;
|
|
1526
|
+
try {
|
|
1527
|
+
const evt = JSON.parse(raw);
|
|
1528
|
+
const delta =
|
|
1529
|
+
evt.choices?.[0]?.delta?.content ||
|
|
1530
|
+
evt.delta?.text ||
|
|
1531
|
+
evt.text ||
|
|
1532
|
+
evt.content ||
|
|
1533
|
+
"";
|
|
1534
|
+
if (delta) fullText += delta;
|
|
1535
|
+
} catch {
|
|
1536
|
+
if (raw) fullText += raw;
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1573
1539
|
}
|
|
1574
1540
|
|
|
1575
|
-
//
|
|
1541
|
+
// Return as a single OpenAI-compatible JSON response
|
|
1542
|
+
const result = {
|
|
1543
|
+
id: "chatcmpl_bridge_" + Date.now(),
|
|
1544
|
+
object: "chat.completion",
|
|
1545
|
+
created: Math.floor(Date.now() / 1000),
|
|
1546
|
+
choices: [{
|
|
1547
|
+
index: 0,
|
|
1548
|
+
message: { role: "assistant", content: fullText },
|
|
1549
|
+
finish_reason: "stop",
|
|
1550
|
+
}],
|
|
1551
|
+
};
|
|
1552
|
+
|
|
1553
|
+
headers["content-type"] = "application/json";
|
|
1554
|
+
delete headers["content-length"];
|
|
1555
|
+
delete headers["transfer-encoding"];
|
|
1556
|
+
res.writeHead(200, headers);
|
|
1557
|
+
res.end(JSON.stringify(result));
|
|
1558
|
+
|
|
1559
|
+
// Store in bridge session + broadcast via WebSocket
|
|
1576
1560
|
if (fullText) {
|
|
1577
1561
|
const session = getChatSession("main");
|
|
1578
1562
|
session.messages.push({ role: "assistant", content: fullText, timestamp: Date.now() });
|
|
@@ -1589,12 +1573,11 @@ function handleRequest(req, res) {
|
|
|
1589
1573
|
} else {
|
|
1590
1574
|
console.log(` ${DIM}SSE stream ended with no content${RESET}`);
|
|
1591
1575
|
}
|
|
1592
|
-
res.end();
|
|
1593
1576
|
});
|
|
1594
1577
|
|
|
1595
1578
|
proxyRes.on("error", (err) => {
|
|
1596
1579
|
console.log(` ${DIM}SSE stream error: ${err.message}${RESET}`);
|
|
1597
|
-
res
|
|
1580
|
+
sendJSON(res, 502, { ok: false, error: "Stream error" });
|
|
1598
1581
|
});
|
|
1599
1582
|
return;
|
|
1600
1583
|
}
|