lib/dm-digest-cursor.js
Rama main · réplica de lectura del repositorio original.
var CONVERSATION_CAP = 6000;
// SDK text is persisted as delta before onTurnDone; the response preview
// is only a notification excerpt. Historical complete text entries remain readable.
function isDigestEntry(entry) {
return entry && (entry.type === "user_message" || entry.type === "assistant_message" || entry.type === "delta" || entry.type === "text");
}
function copyCursor(cursor) {
return {
historyIndex: Math.max(0, cursor.historyIndex || 0),
textOffset: Math.max(0, cursor.textOffset || 0),
};
}
function readState(history) {
var state = { cursor: { historyIndex: 0, textOffset: 0 } };
for (var i = history.length - 1; i >= 0; i--) {
var entry = history[i];
if (!entry || entry.type !== "digest_checkpoint") continue;
if (entry.cursor && typeof entry.cursor.historyIndex === "number") {
state.cursor = copyCursor(entry.cursor);
} else if (typeof entry.digestedIndex === "number") {
// Legacy checkpoints pointed at the next history entry and had no
// within-entry progress. Treat them as fully consumed entries.
state.cursor = { historyIndex: Math.max(0, entry.digestedIndex), textOffset: 0 };
}
break;
}
return state;
}
function readCursor(history) {
return readState(history).cursor;
}
function sameCursor(left, right) {
return left.historyIndex === right.historyIndex && left.textOffset === right.textOffset;
}
function safeTextEnd(text, start, maximum) {
var end = Math.min(text.length, start + maximum);
if (end < text.length && end > start) {
var previous = text.charCodeAt(end - 1);
if (previous >= 0xD800 && previous <= 0xDBFF) end--;
}
// The normal cap leaves enough room for a code point. Keep this fallback
// defensive so a future smaller cap cannot make the cursor stall.
if (end === start && end < text.length) {
end = start + (text.charCodeAt(start) >= 0xD800 && text.charCodeAt(start) <= 0xDBFF ? 2 : 1);
}
return Math.min(end, text.length);
}
function collectChunk(history, startCursor) {
var cursor = copyCursor(startCursor);
var parts = [];
var totalLength = 0;
var hadHistoryProgress = false;
var partCount = 0;
function appendPart(value) {
var separatorLength = partCount > 0 ? 1 : 0;
if (totalLength + separatorLength + value.length > CONVERSATION_CAP) return false;
parts.push(value);
totalLength += separatorLength + value.length;
partCount++;
return true;
}
while (cursor.historyIndex < history.length) {
var entry = history[cursor.historyIndex];
if (!isDigestEntry(entry) || typeof entry.text !== "string" || !entry.text) {
cursor.historyIndex++;
cursor.textOffset = 0;
hadHistoryProgress = true;
continue;
}
var text = entry.text;
var offset = Math.min(cursor.textOffset, text.length);
var label = entry.type === "user_message" ? "User: " : "Mate: ";
var available = CONVERSATION_CAP - totalLength - (partCount > 0 ? 1 : 0) - label.length;
if (available <= 0 && partCount > 0) break;
if (available <= 0) available = CONVERSATION_CAP - label.length;
if (offset < text.length) {
var end = safeTextEnd(text, offset, available);
var value = label + text.substring(offset, end);
if (!appendPart(value)) break;
cursor.textOffset = end;
hadHistoryProgress = true;
if (end < text.length) break;
}
cursor.historyIndex++;
cursor.textOffset = 0;
}
return {
conversationContent: parts.join("\n"),
cursor: cursor,
hasHistoryProgress: hadHistoryProgress,
};
}
function hasPending(history, cursor) {
var probe = collectChunk(history, cursor, "");
return !!probe.conversationContent;
}
module.exports = {
CONVERSATION_CAP: CONVERSATION_CAP,
collectChunk: collectChunk,
hasPending: hasPending,
readCursor: readCursor,
readState: readState,
sameCursor: sameCursor,
};