Ctrl K
ComunidadGuías…

lib/dm-digest-cursor.js

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
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,
};