@ccpocket/bridge 1.51.0 → 1.53.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.
@@ -1345,6 +1345,219 @@ async function getAllRecentCodexSessions(options = {}) {
1345
1345
  }
1346
1346
  return entries;
1347
1347
  }
1348
+ export function codexUserTurnUuid(ordinal) {
1349
+ return `codex:user-turn:${ordinal}`;
1350
+ }
1351
+ function numberToIsoTimestamp(value) {
1352
+ return typeof value === "number" && Number.isFinite(value)
1353
+ ? new Date(value * 1000).toISOString()
1354
+ : undefined;
1355
+ }
1356
+ function stringValue(value) {
1357
+ return typeof value === "string" ? value : undefined;
1358
+ }
1359
+ function arrayValue(value) {
1360
+ return Array.isArray(value) ? value : [];
1361
+ }
1362
+ function codexToolResultContent(value) {
1363
+ if (value == null)
1364
+ return "";
1365
+ if (typeof value === "string")
1366
+ return value;
1367
+ try {
1368
+ return JSON.stringify(value, null, 2);
1369
+ }
1370
+ catch {
1371
+ return String(value);
1372
+ }
1373
+ }
1374
+ function codexUserInputTextAndImages(content) {
1375
+ const textParts = [];
1376
+ let imageCount = 0;
1377
+ for (const entry of arrayValue(content)) {
1378
+ const item = asObject(entry);
1379
+ if (!item)
1380
+ continue;
1381
+ if (item.type === "text" && typeof item.text === "string") {
1382
+ textParts.push(item.text);
1383
+ }
1384
+ else if (item.type === "image" || item.type === "localImage") {
1385
+ imageCount += 1;
1386
+ }
1387
+ }
1388
+ return { text: textParts.join("\n"), imageCount };
1389
+ }
1390
+ function appendCodexThinkingMessage(messages, text, timestamp) {
1391
+ const normalized = text.trim();
1392
+ if (!normalized)
1393
+ return;
1394
+ messages.push({
1395
+ role: "assistant",
1396
+ content: [{ type: "thinking", thinking: normalized }],
1397
+ ...(timestamp ? { timestamp } : {}),
1398
+ });
1399
+ }
1400
+ function appendCodexOfficialToolResult(messages, id, name, content, timestamp) {
1401
+ appendToolResultMessage(messages, id, name, content, {
1402
+ ...(timestamp ? { timestamp } : {}),
1403
+ });
1404
+ }
1405
+ export function codexThreadToSessionHistory(thread) {
1406
+ const messages = [];
1407
+ const turns = arrayValue(asObject(thread)?.turns);
1408
+ let userTurnOrdinal = 0;
1409
+ for (const rawTurn of turns) {
1410
+ const turn = asObject(rawTurn);
1411
+ if (!turn)
1412
+ continue;
1413
+ const turnStartedAt = numberToIsoTimestamp(turn.startedAt);
1414
+ const turnCompletedAt = numberToIsoTimestamp(turn.completedAt);
1415
+ for (const rawItem of arrayValue(turn.items)) {
1416
+ const item = asObject(rawItem);
1417
+ if (!item || typeof item.type !== "string")
1418
+ continue;
1419
+ const itemId = stringValue(item.id) ?? `codex-item-${messages.length}`;
1420
+ const itemTimestamp = turnCompletedAt ?? turnStartedAt;
1421
+ switch (item.type) {
1422
+ case "userMessage": {
1423
+ const { text, imageCount } = codexUserInputTextAndImages(item.content);
1424
+ const displayText = text.trim().length > 0
1425
+ ? text
1426
+ : imageCount > 0
1427
+ ? `[Image attached${imageCount > 1 ? ` x${imageCount}` : ""}]`
1428
+ : "";
1429
+ if (displayText.trim().length === 0)
1430
+ break;
1431
+ userTurnOrdinal += 1;
1432
+ messages.push({
1433
+ role: "user",
1434
+ uuid: codexUserTurnUuid(userTurnOrdinal),
1435
+ content: [{ type: "text", text: displayText }],
1436
+ ...(imageCount > 0 ? { imageCount } : {}),
1437
+ ...(turnStartedAt ? { timestamp: turnStartedAt } : {}),
1438
+ });
1439
+ break;
1440
+ }
1441
+ case "agentMessage": {
1442
+ appendTextMessage(messages, "assistant", stringValue(item.text) ?? "", itemTimestamp);
1443
+ break;
1444
+ }
1445
+ case "plan": {
1446
+ appendTextMessage(messages, "assistant", stringValue(item.text) ?? "", itemTimestamp);
1447
+ break;
1448
+ }
1449
+ case "reasoning": {
1450
+ const summary = arrayValue(item.summary)
1451
+ .filter((value) => typeof value === "string");
1452
+ const content = arrayValue(item.content)
1453
+ .filter((value) => typeof value === "string");
1454
+ appendCodexThinkingMessage(messages, [...summary, ...content].join("\n"), itemTimestamp);
1455
+ break;
1456
+ }
1457
+ case "commandExecution": {
1458
+ const command = stringValue(item.command) ?? "";
1459
+ appendToolUseMessage(messages, itemId, "Bash", {
1460
+ command,
1461
+ ...(typeof item.cwd === "string" ? { cwd: item.cwd } : {}),
1462
+ });
1463
+ const outputParts = [];
1464
+ if (typeof item.status === "string") {
1465
+ outputParts.push(`status: ${item.status}`);
1466
+ }
1467
+ if (typeof item.exitCode === "number") {
1468
+ outputParts.push(`exitCode: ${item.exitCode}`);
1469
+ }
1470
+ if (typeof item.aggregatedOutput === "string") {
1471
+ outputParts.push(item.aggregatedOutput);
1472
+ }
1473
+ appendCodexOfficialToolResult(messages, itemId, "Bash", outputParts.join("\n").trim(), itemTimestamp);
1474
+ break;
1475
+ }
1476
+ case "fileChange": {
1477
+ appendToolUseMessage(messages, itemId, "FileChange", {
1478
+ changes: Array.isArray(item.changes) ? item.changes : [],
1479
+ ...(typeof item.status === "string" ? { status: item.status } : {}),
1480
+ });
1481
+ break;
1482
+ }
1483
+ case "mcpToolCall": {
1484
+ const server = stringValue(item.server) ?? "mcp";
1485
+ const tool = stringValue(item.tool) ?? "tool";
1486
+ appendToolUseMessage(messages, itemId, `mcp:${server}/${tool}`, {
1487
+ arguments: item.arguments ?? {},
1488
+ ...(typeof item.status === "string" ? { status: item.status } : {}),
1489
+ });
1490
+ if (item.result != null || item.error != null) {
1491
+ const normalized = normalizeCodexMcpResult(item.result ?? item.error);
1492
+ appendToolResultMessage(messages, itemId, `mcp:${server}/${tool}`, normalized.content, {
1493
+ imageBase64: normalized.imageBase64,
1494
+ ...(itemTimestamp ? { timestamp: itemTimestamp } : {}),
1495
+ });
1496
+ }
1497
+ break;
1498
+ }
1499
+ case "dynamicToolCall": {
1500
+ const tool = stringValue(item.tool) ?? "tool";
1501
+ appendToolUseMessage(messages, itemId, tool, {
1502
+ arguments: item.arguments ?? {},
1503
+ ...(typeof item.status === "string" ? { status: item.status } : {}),
1504
+ });
1505
+ const contentItems = arrayValue(item.contentItems);
1506
+ const resultText = contentItems
1507
+ .map((entry) => {
1508
+ const contentItem = asObject(entry);
1509
+ if (!contentItem)
1510
+ return "";
1511
+ if (contentItem.type === "inputText" &&
1512
+ typeof contentItem.text === "string") {
1513
+ return contentItem.text;
1514
+ }
1515
+ if (contentItem.type === "inputImage" &&
1516
+ typeof contentItem.imageUrl === "string") {
1517
+ return contentItem.imageUrl;
1518
+ }
1519
+ return codexToolResultContent(contentItem);
1520
+ })
1521
+ .filter(Boolean)
1522
+ .join("\n");
1523
+ appendCodexOfficialToolResult(messages, itemId, tool, resultText, itemTimestamp);
1524
+ break;
1525
+ }
1526
+ case "webSearch": {
1527
+ appendToolUseMessage(messages, itemId, "WebSearch", {
1528
+ query: stringValue(item.query) ?? "",
1529
+ ...(item.action != null ? { action: item.action } : {}),
1530
+ });
1531
+ break;
1532
+ }
1533
+ case "imageGeneration": {
1534
+ appendToolUseMessage(messages, itemId, "ImageGeneration", {
1535
+ ...(typeof item.status === "string" ? { status: item.status } : {}),
1536
+ ...(typeof item.revisedPrompt === "string"
1537
+ ? { revisedPrompt: item.revisedPrompt }
1538
+ : {}),
1539
+ });
1540
+ appendImageGenerationResult(messages, {
1541
+ id: itemId,
1542
+ status: item.status,
1543
+ revisedPrompt: item.revisedPrompt,
1544
+ savedPath: item.savedPath,
1545
+ result: item.result,
1546
+ }, itemId, itemTimestamp);
1547
+ break;
1548
+ }
1549
+ case "enteredReviewMode":
1550
+ case "exitedReviewMode": {
1551
+ appendTextMessage(messages, "assistant", stringValue(item.review) ?? "", itemTimestamp);
1552
+ break;
1553
+ }
1554
+ default:
1555
+ break;
1556
+ }
1557
+ }
1558
+ }
1559
+ return messages;
1560
+ }
1348
1561
  function asObject(value) {
1349
1562
  if (!value || typeof value !== "object" || Array.isArray(value)) {
1350
1563
  return null;
@@ -1363,10 +1576,10 @@ function parseObjectLike(value) {
1363
1576
  }
1364
1577
  return asObject(value) ?? {};
1365
1578
  }
1366
- function appendTextMessage(messages, role, text, timestamp) {
1579
+ function appendTextMessage(messages, role, text, timestamp, uuid) {
1367
1580
  const normalized = text.trim();
1368
1581
  if (!normalized)
1369
- return;
1582
+ return false;
1370
1583
  const last = messages.at(-1);
1371
1584
  if (last
1372
1585
  && last.role === role
@@ -1375,13 +1588,35 @@ function appendTextMessage(messages, role, text, timestamp) {
1375
1588
  && last.content[0].type === "text"
1376
1589
  && typeof last.content[0].text === "string"
1377
1590
  && last.content[0].text.trim() === normalized) {
1378
- return;
1591
+ return false;
1379
1592
  }
1380
1593
  messages.push({
1381
1594
  role,
1595
+ ...(uuid ? { uuid } : {}),
1382
1596
  content: [{ type: "text", text }],
1383
1597
  ...(timestamp ? { timestamp } : {}),
1384
1598
  });
1599
+ return true;
1600
+ }
1601
+ function countCodexUserTurns(messages) {
1602
+ return messages.filter((message) => message.role === "user" && !message.isMeta)
1603
+ .length;
1604
+ }
1605
+ function applyCodexThreadRollback(messages, numTurns) {
1606
+ if (!Number.isFinite(numTurns) || numTurns <= 0)
1607
+ return;
1608
+ const userIndices = messages
1609
+ .map((message, index) => message.role === "user" && !message.isMeta ? index : -1)
1610
+ .filter((index) => index >= 0);
1611
+ if (userIndices.length === 0)
1612
+ return;
1613
+ if (numTurns >= userIndices.length) {
1614
+ messages.length = 0;
1615
+ return;
1616
+ }
1617
+ const keepUserTurns = userIndices.length - numTurns;
1618
+ const cutIndex = userIndices[keepUserTurns];
1619
+ messages.splice(cutIndex);
1385
1620
  }
1386
1621
  function appendImageGenerationResult(messages, payload, fallbackId, timestamp) {
1387
1622
  const id = typeof payload.call_id === "string"
@@ -1884,6 +2119,7 @@ export async function getCodexSessionHistory(threadId) {
1884
2119
  }
1885
2120
  const messages = [];
1886
2121
  const lines = raw.split("\n");
2122
+ let userTurnOrdinal = 0;
1887
2123
  for (const [index, line] of lines.entries()) {
1888
2124
  if (!line.trim())
1889
2125
  continue;
@@ -1899,6 +2135,13 @@ export async function getCodexSessionHistory(threadId) {
1899
2135
  const payload = asObject(entry.payload);
1900
2136
  if (!payload)
1901
2137
  continue;
2138
+ if (payload.type === "thread_rolled_back") {
2139
+ const rawNumTurns = payload.num_turns ?? payload.numTurns;
2140
+ const numTurns = typeof rawNumTurns === "number" ? rawNumTurns : Number(rawNumTurns);
2141
+ applyCodexThreadRollback(messages, numTurns);
2142
+ userTurnOrdinal = countCodexUserTurns(messages);
2143
+ continue;
2144
+ }
1902
2145
  if (payload.type === "user_message") {
1903
2146
  const rawMessage = typeof payload.message === "string" ? payload.message : "";
1904
2147
  const images = Array.isArray(payload.images) ? payload.images.length : 0;
@@ -1917,6 +2160,7 @@ export async function getCodexSessionHistory(threadId) {
1917
2160
  if (normalized) {
1918
2161
  messages.push({
1919
2162
  role: "user",
2163
+ uuid: codexUserTurnUuid(++userTurnOrdinal),
1920
2164
  content: [{ type: "text", text }],
1921
2165
  imageCount,
1922
2166
  ...(entryTimestamp ? { timestamp: entryTimestamp } : {}),
@@ -1924,7 +2168,9 @@ export async function getCodexSessionHistory(threadId) {
1924
2168
  }
1925
2169
  }
1926
2170
  else {
1927
- appendTextMessage(messages, "user", text, entryTimestamp);
2171
+ if (appendTextMessage(messages, "user", text, entryTimestamp, codexUserTurnUuid(userTurnOrdinal + 1))) {
2172
+ userTurnOrdinal += 1;
2173
+ }
1928
2174
  }
1929
2175
  continue;
1930
2176
  }
@@ -1971,7 +2217,9 @@ export async function getCodexSessionHistory(threadId) {
1971
2217
  .map((item) => item.text)
1972
2218
  .join("\n");
1973
2219
  if (!isCodexInjectedUserContext(text)) {
1974
- appendTextMessage(messages, "user", text, entryTimestamp);
2220
+ if (appendTextMessage(messages, "user", text, entryTimestamp, codexUserTurnUuid(userTurnOrdinal + 1))) {
2221
+ userTurnOrdinal += 1;
2222
+ }
1975
2223
  }
1976
2224
  continue;
1977
2225
  }