File size: 3,309 Bytes
90f0300
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
import fsSync from 'node:fs';
import path from 'node:path';
import readline from 'node:readline';
import { CODEX_SESSIONS_DIR } from './codex-config.js';
import { readMobileSessionMessages } from './mobile-session-index.js';
import { filterDeletedMessages, readDeletedMessageIds } from './codex-data-hidden-state.js';
import { extractContent, isVisibleUserMessage, sanitizeVisibleUserMessage, walkJsonlFiles } from './codex-data-parser.js';

async function findSessionFile(cache, sessionId) {
  const cached = cache.sessionById.get(sessionId)?.filePath;
  if (cached) {
    return cached;
  }
  const files = await walkJsonlFiles(CODEX_SESSIONS_DIR);
  return files.find((file) => path.basename(file).includes(sessionId)) || null;
}

function paginateMessages(messages, { limit = 120, offset = null, latest = true } = {}) {
  const total = messages.length;
  const count = Number(limit) || 0;
  const hasOffset = offset !== null && offset !== undefined;
  const start = hasOffset
    ? Math.max(0, Number(offset) || 0)
    : latest && count
      ? Math.max(0, total - count)
      : 0;
  const end = count ? start + count : undefined;
  return {
    messages: messages.slice(start, end),
    total,
    offset: start,
    hasMore: end ? end < total : false,
    hasMoreBefore: start > 0
  };
}

export async function readSessionMessagesFromCache(cache, sessionId, { limit = 120, offset = null, latest = true } = {}) {
  const filePath = await findSessionFile(cache, sessionId);
  const mobileMessages = await readMobileSessionMessages(sessionId);
  const deletedIds = await readDeletedMessageIds(sessionId);
  if (!filePath) {
    return paginateMessages(filterDeletedMessages(mobileMessages, deletedIds), { limit, offset, latest });
  }

  const messages = [];
  const stream = fsSync.createReadStream(filePath, { encoding: 'utf8' });
  const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });

  for await (const line of rl) {
    if (!line.trim()) {
      continue;
    }
    try {
      const entry = JSON.parse(line);
      const timestamp = entry.timestamp || null;

      if (entry.type === 'event_msg' && isVisibleUserMessage(entry.payload)) {
        messages.push({
          id: `${entry.timestamp || messages.length}-user`,
          role: 'user',
          content: sanitizeVisibleUserMessage(entry.payload.message),
          timestamp
        });
      }

      if (
        entry.type === 'response_item' &&
        entry.payload?.type === 'message' &&
        entry.payload.role === 'assistant' &&
        entry.payload.phase !== 'commentary'
      ) {
        const content = extractContent(entry.payload.content);
        if (content.trim()) {
          messages.push({
            id: entry.payload.id || `${entry.timestamp || messages.length}-assistant`,
            role: entry.payload.role || 'assistant',
            content,
            timestamp
          });
        }
      }

    } catch {
      // Skip malformed rows.
    }
  }

  for (const message of mobileMessages) {
    if (!messages.some((item) => item.id === message.id)) {
      messages.push(message);
    }
  }
  messages.sort((a, b) => new Date(a.timestamp || 0) - new Date(b.timestamp || 0));

  return paginateMessages(filterDeletedMessages(messages, deletedIds), { limit, offset, latest });
}