@makerbi/remodex 1.4.0 → 1.5.0
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/package.json +6 -1
- package/src/bridge.js +425 -68
- package/src/desktop-handler.js +100 -11
- package/src/desktop-ipc-action-follower.js +42 -12
- package/src/git-handler.js +48 -4
- package/src/macos-launch-agent.js +2 -2
- package/src/pet-handler.js +537 -0
- package/src/qr.js +21 -2
- package/src/rollout-watch.js +17 -7
- package/src/workspace-handler.js +312 -4
- package/src/private-defaults.json +0 -4
package/package.json
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@makerbi/remodex",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Local bridge between Codex and the Remodex mobile app. Run `remodex up` to start.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/AndersonBY/remodex.git",
|
|
8
|
+
"directory": "phodex-bridge"
|
|
9
|
+
},
|
|
5
10
|
"main": "src/index.js",
|
|
6
11
|
"bin": {
|
|
7
12
|
"remodex": "bin/remodex.js"
|
package/src/bridge.js
CHANGED
|
@@ -24,6 +24,7 @@ const { handleGitRequest } = require("./git-handler");
|
|
|
24
24
|
const { handleThreadContextRequest } = require("./thread-context-handler");
|
|
25
25
|
const { handleWorkspaceRequest } = require("./workspace-handler");
|
|
26
26
|
const { handleProjectRequest } = require("./project-handler");
|
|
27
|
+
const { handlePetRequest } = require("./pet-handler");
|
|
27
28
|
const { createNotificationsHandler } = require("./notifications-handler");
|
|
28
29
|
const { createVoiceHandler, resolveVoiceAuth } = require("./voice-handler");
|
|
29
30
|
const {
|
|
@@ -42,7 +43,6 @@ const { createBridgeSecureTransport } = require("./secure-transport");
|
|
|
42
43
|
const { createRolloutLiveMirrorController } = require("./rollout-live-mirror");
|
|
43
44
|
const {
|
|
44
45
|
createDesktopIpcActionFollower,
|
|
45
|
-
seedConversationStateFromThreadRead,
|
|
46
46
|
} = require("./desktop-ipc-action-follower");
|
|
47
47
|
const { version: bridgePackageVersion = "" } = require("../package.json");
|
|
48
48
|
const {
|
|
@@ -66,6 +66,24 @@ const RELAY_HISTORY_IMAGE_REFERENCE_URL = "remodex://history-image-elided";
|
|
|
66
66
|
const RELAY_THREAD_PAYLOAD_SOFT_LIMIT_BYTES = 4 * 1024 * 1024;
|
|
67
67
|
const RELAY_HISTORY_TEXT_TAIL_LIMIT_CHARS = 24_000;
|
|
68
68
|
const RELAY_HISTORY_RECENT_TURN_TARGET = 40;
|
|
69
|
+
const RELAY_TURNS_LIST_TARGET_BUDGET_MS = 5_500;
|
|
70
|
+
const RELAY_TURNS_LIST_BUDGET_RESERVE_MS = 1_000;
|
|
71
|
+
const RELAY_TURNS_LIST_RESULT_KEYS = ["data", "items", "turns"];
|
|
72
|
+
const RELAY_TURNS_LIST_PAGINATION_RESULT_KEYS = [
|
|
73
|
+
"nextCursor",
|
|
74
|
+
"next_cursor",
|
|
75
|
+
"cursor",
|
|
76
|
+
"hasNextCursor",
|
|
77
|
+
"has_next_cursor",
|
|
78
|
+
"hasNextPage",
|
|
79
|
+
"has_next_page",
|
|
80
|
+
"hasMore",
|
|
81
|
+
"has_more",
|
|
82
|
+
"prevCursor",
|
|
83
|
+
"prev_cursor",
|
|
84
|
+
"previousCursor",
|
|
85
|
+
"previous_cursor",
|
|
86
|
+
];
|
|
69
87
|
|
|
70
88
|
function buildRelayUserAgentHeader({ version = bridgePackageVersion } = {}) {
|
|
71
89
|
const normalizedVersion = typeof version === "string" && version.trim()
|
|
@@ -168,6 +186,7 @@ function startBridge({
|
|
|
168
186
|
const relaySanitizedRequestMethods = new Set([
|
|
169
187
|
"thread/read",
|
|
170
188
|
"thread/resume",
|
|
189
|
+
"thread/turns/list",
|
|
171
190
|
]);
|
|
172
191
|
const forwardedRequestMethodTTLms = 2 * 60_000;
|
|
173
192
|
const pendingAuthLogin = {
|
|
@@ -205,7 +224,6 @@ function startBridge({
|
|
|
205
224
|
const desktopIpcActionFollower = !config.codexEndpoint
|
|
206
225
|
? createDesktopIpcActionFollower({
|
|
207
226
|
sendApplicationResponse,
|
|
208
|
-
readConversationState: readDesktopConversationState,
|
|
209
227
|
socketPath: config.desktopIpcSocketPath || undefined,
|
|
210
228
|
})
|
|
211
229
|
: null;
|
|
@@ -554,6 +572,9 @@ function startBridge({
|
|
|
554
572
|
if (handleProjectRequest(rawMessage, sendApplicationResponse)) {
|
|
555
573
|
return;
|
|
556
574
|
}
|
|
575
|
+
if (handlePetRequest(rawMessage, sendApplicationResponse)) {
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
557
578
|
if (notificationsHandler.handleNotificationsRequest(rawMessage, sendApplicationResponse)) {
|
|
558
579
|
return;
|
|
559
580
|
}
|
|
@@ -576,6 +597,9 @@ function startBridge({
|
|
|
576
597
|
if (desktopIpcActionFollower?.observeInbound(rawMessage)) {
|
|
577
598
|
return;
|
|
578
599
|
}
|
|
600
|
+
if (handleBridgeManagedThreadTurnsListRequest(rawMessage)) {
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
579
603
|
rememberForwardedRequestMethod(rawMessage);
|
|
580
604
|
rememberThreadFromMessage("phone", rawMessage);
|
|
581
605
|
codex.send(rawMessage);
|
|
@@ -608,13 +632,33 @@ function startBridge({
|
|
|
608
632
|
}));
|
|
609
633
|
}
|
|
610
634
|
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
635
|
+
function handleBridgeManagedThreadTurnsListRequest(rawMessage) {
|
|
636
|
+
const request = parseAdaptiveThreadTurnsListRequest(rawMessage);
|
|
637
|
+
if (!request) {
|
|
638
|
+
return false;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
rememberThreadFromMessage("phone", rawMessage);
|
|
642
|
+
(async () => {
|
|
643
|
+
try {
|
|
644
|
+
const response = await fetchAdaptiveThreadTurnsListForRelay(request, {
|
|
645
|
+
fetchPage: (params) => sendCodexRequest("thread/turns/list", params),
|
|
646
|
+
});
|
|
647
|
+
relaySanitizedResponseMethodsById.set(String(request.id), {
|
|
648
|
+
method: "thread/turns/list",
|
|
649
|
+
createdAt: Date.now(),
|
|
650
|
+
});
|
|
651
|
+
sendApplicationResponse(JSON.stringify(response));
|
|
652
|
+
} catch (error) {
|
|
653
|
+
sendApplicationResponse(createJsonRpcErrorResponse(
|
|
654
|
+
request.id,
|
|
655
|
+
error,
|
|
656
|
+
"thread_turns_list_failed"
|
|
657
|
+
));
|
|
658
|
+
}
|
|
659
|
+
})();
|
|
660
|
+
|
|
661
|
+
return true;
|
|
618
662
|
}
|
|
619
663
|
|
|
620
664
|
// ─── Bridge-owned auth snapshot ─────────────────────────────
|
|
@@ -1450,81 +1494,242 @@ function normalizeNonEmptyString(value) {
|
|
|
1450
1494
|
return typeof value === "string" && value.trim() ? value.trim() : "";
|
|
1451
1495
|
}
|
|
1452
1496
|
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
return rawMessage;
|
|
1497
|
+
function parseAdaptiveThreadTurnsListRequest(rawMessage) {
|
|
1498
|
+
const parsed = parseBridgeJSON(rawMessage);
|
|
1499
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1500
|
+
return null;
|
|
1458
1501
|
}
|
|
1459
1502
|
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
if (!thread || typeof thread !== "object" || !Array.isArray(thread.turns)) {
|
|
1463
|
-
return rawMessage;
|
|
1503
|
+
if (parsed.method !== "thread/turns/list") {
|
|
1504
|
+
return null;
|
|
1464
1505
|
}
|
|
1465
1506
|
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
return turn;
|
|
1470
|
-
}
|
|
1507
|
+
if (parsed.id == null) {
|
|
1508
|
+
return null;
|
|
1509
|
+
}
|
|
1471
1510
|
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1511
|
+
const params = parsed.params;
|
|
1512
|
+
if (!params || typeof params !== "object" || Array.isArray(params)) {
|
|
1513
|
+
return null;
|
|
1514
|
+
}
|
|
1476
1515
|
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
}
|
|
1516
|
+
if (!Number.isInteger(params.limit) || params.limit <= 0) {
|
|
1517
|
+
return null;
|
|
1518
|
+
}
|
|
1481
1519
|
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
if (sanitizedItem !== item) {
|
|
1485
|
-
itemDidChange = true;
|
|
1486
|
-
}
|
|
1520
|
+
return parsed;
|
|
1521
|
+
}
|
|
1487
1522
|
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1523
|
+
async function fetchAdaptiveThreadTurnsListForRelay(request, {
|
|
1524
|
+
fetchPage,
|
|
1525
|
+
now = Date.now,
|
|
1526
|
+
targetBudgetMs = RELAY_TURNS_LIST_TARGET_BUDGET_MS,
|
|
1527
|
+
budgetReserveMs = RELAY_TURNS_LIST_BUDGET_RESERVE_MS,
|
|
1528
|
+
rawPageSoftLimitBytes = RELAY_THREAD_PAYLOAD_SOFT_LIMIT_BYTES,
|
|
1529
|
+
payloadSoftLimitBytes = RELAY_THREAD_PAYLOAD_SOFT_LIMIT_BYTES,
|
|
1530
|
+
sanitizeForRelay = sanitizeThreadHistoryImagesForRelay,
|
|
1531
|
+
} = {}) {
|
|
1532
|
+
if (typeof fetchPage !== "function") {
|
|
1533
|
+
throw new Error("fetchPage is required for adaptive turns-list pagination.");
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
const params = request?.params;
|
|
1537
|
+
const requestedLimit = Number.isInteger(params?.limit) && params.limit > 0
|
|
1538
|
+
? params.limit
|
|
1539
|
+
: 1;
|
|
1540
|
+
const startedAt = now();
|
|
1541
|
+
let nextCursor = params?.cursor;
|
|
1542
|
+
let turnsKey = null;
|
|
1543
|
+
let firstResult = null;
|
|
1544
|
+
let lastResult = null;
|
|
1545
|
+
let combinedTurns = [];
|
|
1546
|
+
let response = null;
|
|
1547
|
+
|
|
1548
|
+
while (combinedTurns.length < requestedLimit) {
|
|
1549
|
+
const remaining = requestedLimit - combinedTurns.length;
|
|
1550
|
+
const pageLimit = selectAdaptiveTurnsListBatchLimit(combinedTurns.length, remaining);
|
|
1551
|
+
const pageParams = buildAdaptiveTurnsListPageParams(params, pageLimit, nextCursor);
|
|
1552
|
+
let page;
|
|
1496
1553
|
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
}
|
|
1554
|
+
try {
|
|
1555
|
+
page = await fetchMeasuredAdaptiveTurnsListPage(fetchPage, pageParams, now);
|
|
1556
|
+
} catch (error) {
|
|
1557
|
+
if (response) {
|
|
1558
|
+
return response;
|
|
1503
1559
|
}
|
|
1560
|
+
throw error;
|
|
1561
|
+
}
|
|
1504
1562
|
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1563
|
+
const pageResult = page.result;
|
|
1564
|
+
const pageTurnsKey = findTurnsListResultKey(pageResult);
|
|
1565
|
+
if (!pageTurnsKey) {
|
|
1566
|
+
if (!response) {
|
|
1567
|
+
return {
|
|
1568
|
+
id: request.id,
|
|
1569
|
+
result: pageResult ?? null,
|
|
1570
|
+
};
|
|
1509
1571
|
}
|
|
1572
|
+
return response;
|
|
1573
|
+
}
|
|
1510
1574
|
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1575
|
+
if (!turnsKey) {
|
|
1576
|
+
turnsKey = pageTurnsKey;
|
|
1577
|
+
}
|
|
1578
|
+
if (!firstResult) {
|
|
1579
|
+
firstResult = pageResult;
|
|
1580
|
+
}
|
|
1581
|
+
lastResult = pageResult;
|
|
1514
1582
|
|
|
1515
|
-
|
|
1516
|
-
|
|
1583
|
+
const pageTurns = pageResult[pageTurnsKey];
|
|
1584
|
+
combinedTurns = combinedTurns.concat(pageTurns);
|
|
1585
|
+
response = {
|
|
1586
|
+
id: request.id,
|
|
1587
|
+
result: buildAdaptiveTurnsListResult(firstResult, lastResult, turnsKey, combinedTurns),
|
|
1588
|
+
};
|
|
1517
1589
|
|
|
1518
|
-
|
|
1519
|
-
|
|
1590
|
+
nextCursor = readTurnsListNextCursor(pageResult);
|
|
1591
|
+
if (combinedTurns.length >= requestedLimit || !hasRelayCursor(nextCursor) || pageTurns.length === 0) {
|
|
1592
|
+
break;
|
|
1520
1593
|
}
|
|
1521
1594
|
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1595
|
+
const rawPageBytes = jsonByteLength(pageResult);
|
|
1596
|
+
const sanitizedResponseBytes = measureSanitizedTurnsListResponseBytes(response, sanitizeForRelay);
|
|
1597
|
+
const elapsedMs = Math.max(0, now() - startedAt);
|
|
1598
|
+
const remainingBudgetMs = Math.max(0, targetBudgetMs - elapsedMs);
|
|
1599
|
+
if (
|
|
1600
|
+
rawPageBytes >= rawPageSoftLimitBytes
|
|
1601
|
+
|| sanitizedResponseBytes >= payloadSoftLimitBytes
|
|
1602
|
+
|| page.elapsedMs >= Math.max(0, targetBudgetMs - budgetReserveMs)
|
|
1603
|
+
|| remainingBudgetMs <= budgetReserveMs
|
|
1604
|
+
) {
|
|
1605
|
+
break;
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
return response ?? {
|
|
1610
|
+
id: request.id,
|
|
1611
|
+
result: {
|
|
1612
|
+
data: [],
|
|
1613
|
+
},
|
|
1614
|
+
};
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
async function fetchMeasuredAdaptiveTurnsListPage(fetchPage, params, now) {
|
|
1618
|
+
const startedAt = now();
|
|
1619
|
+
const result = await fetchPage(params);
|
|
1620
|
+
const elapsedMs = Math.max(0, now() - startedAt);
|
|
1621
|
+
return {
|
|
1622
|
+
result,
|
|
1623
|
+
elapsedMs,
|
|
1624
|
+
};
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1627
|
+
function selectAdaptiveTurnsListBatchLimit(fetchedTurnCount, remainingTurnCount) {
|
|
1628
|
+
if (fetchedTurnCount <= 0) {
|
|
1629
|
+
return Math.min(1, remainingTurnCount);
|
|
1630
|
+
}
|
|
1631
|
+
if (fetchedTurnCount <= 1) {
|
|
1632
|
+
return Math.min(4, remainingTurnCount);
|
|
1633
|
+
}
|
|
1634
|
+
return remainingTurnCount;
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
function buildAdaptiveTurnsListPageParams(baseParams, limit, cursor) {
|
|
1638
|
+
const params = {
|
|
1639
|
+
...baseParams,
|
|
1640
|
+
limit,
|
|
1641
|
+
};
|
|
1642
|
+
if (hasRelayCursor(cursor)) {
|
|
1643
|
+
params.cursor = cursor;
|
|
1644
|
+
} else {
|
|
1645
|
+
delete params.cursor;
|
|
1646
|
+
}
|
|
1647
|
+
return params;
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
function findTurnsListResultKey(result) {
|
|
1651
|
+
if (!result || typeof result !== "object" || Array.isArray(result)) {
|
|
1652
|
+
return null;
|
|
1653
|
+
}
|
|
1654
|
+
return RELAY_TURNS_LIST_RESULT_KEYS.find((key) => Array.isArray(result[key])) || null;
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
function buildAdaptiveTurnsListResult(firstResult, lastResult, turnsKey, turns) {
|
|
1658
|
+
const result = {
|
|
1659
|
+
...firstResult,
|
|
1660
|
+
};
|
|
1661
|
+
for (const key of RELAY_TURNS_LIST_RESULT_KEYS) {
|
|
1662
|
+
delete result[key];
|
|
1663
|
+
}
|
|
1664
|
+
result[turnsKey] = turns;
|
|
1665
|
+
|
|
1666
|
+
for (const key of RELAY_TURNS_LIST_PAGINATION_RESULT_KEYS) {
|
|
1667
|
+
if (Object.prototype.hasOwnProperty.call(lastResult, key)) {
|
|
1668
|
+
result[key] = lastResult[key];
|
|
1669
|
+
} else {
|
|
1670
|
+
delete result[key];
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1674
|
+
return result;
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
function readTurnsListNextCursor(result) {
|
|
1678
|
+
if (!result || typeof result !== "object") {
|
|
1679
|
+
return undefined;
|
|
1680
|
+
}
|
|
1681
|
+
if (hasRelayCursor(result.nextCursor)) {
|
|
1682
|
+
return result.nextCursor;
|
|
1683
|
+
}
|
|
1684
|
+
if (hasRelayCursor(result.next_cursor)) {
|
|
1685
|
+
return result.next_cursor;
|
|
1686
|
+
}
|
|
1687
|
+
return undefined;
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1690
|
+
function hasRelayCursor(cursor) {
|
|
1691
|
+
return cursor !== undefined && cursor !== null && cursor !== "";
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
function jsonByteLength(value) {
|
|
1695
|
+
try {
|
|
1696
|
+
return Buffer.byteLength(JSON.stringify(value), "utf8");
|
|
1697
|
+
} catch {
|
|
1698
|
+
return Number.POSITIVE_INFINITY;
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1702
|
+
function measureSanitizedTurnsListResponseBytes(response, sanitizeForRelay) {
|
|
1703
|
+
try {
|
|
1704
|
+
const rawResponse = JSON.stringify(response);
|
|
1705
|
+
const sanitizedResponse = sanitizeForRelay(rawResponse, "thread/turns/list");
|
|
1706
|
+
return Buffer.byteLength(sanitizedResponse, "utf8");
|
|
1707
|
+
} catch {
|
|
1708
|
+
return Number.POSITIVE_INFINITY;
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1712
|
+
// Shrinks thread history snapshots/pages for mobile relay delivery.
|
|
1713
|
+
// This elides bulky blobs and replaces oversized older history with a compact marker.
|
|
1714
|
+
function sanitizeThreadHistoryImagesForRelay(rawMessage, requestMethod) {
|
|
1715
|
+
if (requestMethod === "thread/turns/list") {
|
|
1716
|
+
return sanitizeThreadTurnsListForRelay(rawMessage);
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
if (requestMethod !== "thread/read" && requestMethod !== "thread/resume") {
|
|
1720
|
+
return rawMessage;
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
const parsed = parseBridgeJSON(rawMessage);
|
|
1724
|
+
const thread = parsed?.result?.thread;
|
|
1725
|
+
if (!thread || typeof thread !== "object" || !Array.isArray(thread.turns)) {
|
|
1726
|
+
return rawMessage;
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1729
|
+
const threadId = normalizeNonEmptyString(thread.id)
|
|
1730
|
+
|| normalizeNonEmptyString(thread.threadId)
|
|
1731
|
+
|| normalizeNonEmptyString(thread.thread_id);
|
|
1732
|
+
const { turns: sanitizedTurns, didSanitize } = sanitizeRelayHistoryTurns(thread.turns, threadId);
|
|
1528
1733
|
|
|
1529
1734
|
if (!didSanitize) {
|
|
1530
1735
|
const trimmedPayload = trimThreadPayloadForRelay(parsed, thread);
|
|
@@ -1545,6 +1750,108 @@ function sanitizeThreadHistoryImagesForRelay(rawMessage, requestMethod) {
|
|
|
1545
1750
|
return trimThreadPayloadForRelay(parseBridgeJSON(sanitizedPayload), null) ?? sanitizedPayload;
|
|
1546
1751
|
}
|
|
1547
1752
|
|
|
1753
|
+
function sanitizeThreadTurnsListForRelay(rawMessage) {
|
|
1754
|
+
const parsed = parseBridgeJSON(rawMessage);
|
|
1755
|
+
const result = parsed?.result;
|
|
1756
|
+
if (!result || typeof result !== "object" || Array.isArray(result)) {
|
|
1757
|
+
return rawMessage;
|
|
1758
|
+
}
|
|
1759
|
+
|
|
1760
|
+
const turnsKey = ["data", "items", "turns"].find((key) => Array.isArray(result[key]));
|
|
1761
|
+
if (!turnsKey) {
|
|
1762
|
+
return rawMessage;
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1765
|
+
const threadId = normalizeNonEmptyString(result.threadId)
|
|
1766
|
+
|| normalizeNonEmptyString(result.thread_id)
|
|
1767
|
+
|| normalizeNonEmptyString(result.thread?.id)
|
|
1768
|
+
|| normalizeNonEmptyString(result.thread?.threadId)
|
|
1769
|
+
|| normalizeNonEmptyString(result.thread?.thread_id);
|
|
1770
|
+
const { turns: sanitizedTurns, didSanitize } = sanitizeRelayHistoryTurns(result[turnsKey], threadId);
|
|
1771
|
+
const sanitizedParsed = didSanitize
|
|
1772
|
+
? {
|
|
1773
|
+
...parsed,
|
|
1774
|
+
result: {
|
|
1775
|
+
...result,
|
|
1776
|
+
[turnsKey]: sanitizedTurns,
|
|
1777
|
+
},
|
|
1778
|
+
}
|
|
1779
|
+
: parsed;
|
|
1780
|
+
|
|
1781
|
+
return trimTurnsListPayloadForRelay(sanitizedParsed, turnsKey, didSanitize ? null : rawMessage);
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
function sanitizeRelayHistoryTurns(turns, threadId = "") {
|
|
1785
|
+
let didSanitize = false;
|
|
1786
|
+
const sanitizedTurns = turns.map((turn) => {
|
|
1787
|
+
const sanitizedTurn = sanitizeRelayHistoryTurn(turn, threadId);
|
|
1788
|
+
if (sanitizedTurn !== turn) {
|
|
1789
|
+
didSanitize = true;
|
|
1790
|
+
}
|
|
1791
|
+
return sanitizedTurn;
|
|
1792
|
+
});
|
|
1793
|
+
|
|
1794
|
+
return { turns: sanitizedTurns, didSanitize };
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1797
|
+
function sanitizeRelayHistoryTurn(turn, threadId = "") {
|
|
1798
|
+
if (!turn || typeof turn !== "object" || !Array.isArray(turn.items)) {
|
|
1799
|
+
return turn;
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1802
|
+
let turnDidChange = false;
|
|
1803
|
+
const turnThreadId = normalizeNonEmptyString(threadId)
|
|
1804
|
+
|| normalizeNonEmptyString(turn.threadId)
|
|
1805
|
+
|| normalizeNonEmptyString(turn.thread_id);
|
|
1806
|
+
const sanitizedItems = turn.items.map((item) => {
|
|
1807
|
+
if (!item || typeof item !== "object") {
|
|
1808
|
+
return item;
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
let itemDidChange = false;
|
|
1812
|
+
let sanitizedItem = annotateImageGenerationHistoryItem(item, turnThreadId);
|
|
1813
|
+
if (sanitizedItem !== item) {
|
|
1814
|
+
itemDidChange = true;
|
|
1815
|
+
}
|
|
1816
|
+
|
|
1817
|
+
if (Array.isArray(sanitizedItem.content)) {
|
|
1818
|
+
const sanitizedContent = sanitizedItem.content.map((contentItem) => {
|
|
1819
|
+
const sanitizedEntry = sanitizeInlineHistoryImageContentItem(contentItem);
|
|
1820
|
+
if (sanitizedEntry !== contentItem) {
|
|
1821
|
+
itemDidChange = true;
|
|
1822
|
+
}
|
|
1823
|
+
return sanitizedEntry;
|
|
1824
|
+
});
|
|
1825
|
+
|
|
1826
|
+
if (itemDidChange) {
|
|
1827
|
+
sanitizedItem = {
|
|
1828
|
+
...sanitizedItem,
|
|
1829
|
+
content: sanitizedContent,
|
|
1830
|
+
};
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
const sanitizedCompactionItem = sanitizeCompactionHistoryItem(sanitizedItem);
|
|
1835
|
+
if (sanitizedCompactionItem !== sanitizedItem) {
|
|
1836
|
+
sanitizedItem = sanitizedCompactionItem;
|
|
1837
|
+
itemDidChange = true;
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1840
|
+
if (itemDidChange) {
|
|
1841
|
+
turnDidChange = true;
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1844
|
+
return itemDidChange ? sanitizedItem : item;
|
|
1845
|
+
});
|
|
1846
|
+
|
|
1847
|
+
return turnDidChange
|
|
1848
|
+
? {
|
|
1849
|
+
...turn,
|
|
1850
|
+
items: sanitizedItems,
|
|
1851
|
+
}
|
|
1852
|
+
: turn;
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1548
1855
|
// Annotates live image-generation notifications so the phone can render a local-file
|
|
1549
1856
|
// preview and does not receive the bulky inline base64 result over the relay.
|
|
1550
1857
|
function sanitizeLiveGeneratedImageMessageForRelay(rawMessage) {
|
|
@@ -1947,6 +2254,55 @@ function trimThreadPayloadForRelay(parsed, explicitThread = undefined) {
|
|
|
1947
2254
|
return encodeRelayThreadPayload(parsed, candidateThread);
|
|
1948
2255
|
}
|
|
1949
2256
|
|
|
2257
|
+
function trimTurnsListPayloadForRelay(parsed, turnsKey, originalRawMessage = null) {
|
|
2258
|
+
const result = parsed?.result;
|
|
2259
|
+
const turns = result?.[turnsKey];
|
|
2260
|
+
if (!parsed || !result || !Array.isArray(turns)) {
|
|
2261
|
+
return originalRawMessage ?? JSON.stringify(parsed);
|
|
2262
|
+
}
|
|
2263
|
+
|
|
2264
|
+
const encoded = JSON.stringify(parsed);
|
|
2265
|
+
if (Buffer.byteLength(encoded, "utf8") <= RELAY_THREAD_PAYLOAD_SOFT_LIMIT_BYTES) {
|
|
2266
|
+
return originalRawMessage ?? encoded;
|
|
2267
|
+
}
|
|
2268
|
+
|
|
2269
|
+
let fallbackCompactedPayload = null;
|
|
2270
|
+
for (const maxChars of [
|
|
2271
|
+
RELAY_HISTORY_TEXT_TAIL_LIMIT_CHARS,
|
|
2272
|
+
Math.floor(RELAY_HISTORY_TEXT_TAIL_LIMIT_CHARS / 4),
|
|
2273
|
+
1_000,
|
|
2274
|
+
0,
|
|
2275
|
+
]) {
|
|
2276
|
+
const compactedTurns = turns.map((turn) => compactTurnsListTurnForRelay(turn, maxChars));
|
|
2277
|
+
const compactedPayload = JSON.stringify({
|
|
2278
|
+
...parsed,
|
|
2279
|
+
result: {
|
|
2280
|
+
...result,
|
|
2281
|
+
[turnsKey]: compactedTurns,
|
|
2282
|
+
remodexPageCompactedForRelay: true,
|
|
2283
|
+
},
|
|
2284
|
+
});
|
|
2285
|
+
fallbackCompactedPayload = compactedPayload;
|
|
2286
|
+
if (Buffer.byteLength(compactedPayload, "utf8") <= RELAY_THREAD_PAYLOAD_SOFT_LIMIT_BYTES) {
|
|
2287
|
+
return compactedPayload;
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
2290
|
+
|
|
2291
|
+
return fallbackCompactedPayload ?? (originalRawMessage ?? encoded);
|
|
2292
|
+
}
|
|
2293
|
+
|
|
2294
|
+
function compactTurnsListTurnForRelay(turn, maxChars) {
|
|
2295
|
+
if (!turn || typeof turn !== "object" || !Array.isArray(turn.items)) {
|
|
2296
|
+
return turn;
|
|
2297
|
+
}
|
|
2298
|
+
|
|
2299
|
+
return {
|
|
2300
|
+
...turn,
|
|
2301
|
+
items: turn.items.map((item) => compactHistoryItemForRelay(item, maxChars)),
|
|
2302
|
+
remodexPageCompactedForRelay: true,
|
|
2303
|
+
};
|
|
2304
|
+
}
|
|
2305
|
+
|
|
1950
2306
|
function buildRelayHistoryCompactedThread(thread, turns, omittedTurnCount, keptTurnCount) {
|
|
1951
2307
|
return {
|
|
1952
2308
|
...thread,
|
|
@@ -2074,7 +2430,7 @@ function compactHistoryItemForRelay(item, maxChars) {
|
|
|
2074
2430
|
itemId: typeof item?.itemId === "string" ? item.itemId : undefined,
|
|
2075
2431
|
relayPayloadTruncated: true,
|
|
2076
2432
|
};
|
|
2077
|
-
const tailText = firstRelayTextTail(item, maxChars);
|
|
2433
|
+
const tailText = maxChars > 0 ? firstRelayTextTail(item, maxChars) : "";
|
|
2078
2434
|
if (tailText) {
|
|
2079
2435
|
compactItem.text = tailText;
|
|
2080
2436
|
}
|
|
@@ -2179,6 +2535,7 @@ module.exports = {
|
|
|
2179
2535
|
buildRelayAccessTokenHeaders,
|
|
2180
2536
|
buildRelayUserAgentHeader,
|
|
2181
2537
|
createMacOSBridgeWakeAssertion,
|
|
2538
|
+
fetchAdaptiveThreadTurnsListForRelay,
|
|
2182
2539
|
hasRelayConnectionGoneStale,
|
|
2183
2540
|
isTerminalRelayCloseCode,
|
|
2184
2541
|
persistBridgePreferences,
|