{"version":3,"file":"dedup.mjs","sources":["../../../src/messages/dedup.ts"],"sourcesContent":["// src/messages/dedup.ts\nimport type { BaseMessage } from '@langchain/core/messages';\nimport { MessageTypes } from '@/common';\nimport { DEDUP_MAX_CONTENT_LENGTH } from '@/common/constants';\n\n/**\n * Deduplicates consecutive identical system messages in the context window.\n *\n * Problem: In long tool-use chains, the same system messages (e.g., post-prune notes,\n * conversation summaries) can accumulate when the context is rebuilt on each iteration.\n * These duplicates waste tokens without adding information.\n *\n * Strategy: Only deduplicate system messages that appear consecutively or are exact\n * duplicates of an earlier system message. The FIRST occurrence is always kept.\n * Non-system messages (human, ai, tool) are never touched.\n *\n * Important constraints:\n * - The first system message (index 0) is ALWAYS preserved (it's the main system prompt)\n * - Only system messages are candidates for deduplication\n * - Messages with content longer than DEDUP_MAX_CONTENT_LENGTH are skipped (too expensive to compare)\n * - Content comparison is by string equality (fast and deterministic)\n *\n * @param messages - The message array to deduplicate (not mutated)\n * @returns A new array with duplicate system messages removed, and the count of removed messages\n */\nexport function deduplicateSystemMessages(messages: BaseMessage[]): {\n  messages: BaseMessage[];\n  removedCount: number;\n} {\n  if (messages.length <= 1) {\n    return { messages, removedCount: 0 };\n  }\n\n  const seenSystemContents = new Set<string>();\n  const result: BaseMessage[] = [];\n  let removedCount = 0;\n\n  for (let i = 0; i < messages.length; i++) {\n    const msg = messages[i];\n    const type = msg.getType();\n\n    // Non-system messages are always kept\n    if (type !== MessageTypes.SYSTEM) {\n      result.push(msg);\n      continue;\n    }\n\n    // First system message (main prompt) is always kept\n    if (i === 0) {\n      result.push(msg);\n      // Track its content for dedup of later duplicates\n      const content = getContentString(msg);\n      if (content != null) {\n        seenSystemContents.add(content);\n      }\n      continue;\n    }\n\n    // Get string content for comparison\n    const content = getContentString(msg);\n\n    // Skip dedup for very long or non-string content\n    if (content == null) {\n      result.push(msg);\n      continue;\n    }\n\n    // Check if this exact system message was already seen\n    if (seenSystemContents.has(content)) {\n      removedCount++;\n      continue; // Skip this duplicate\n    }\n\n    // New unique system message — keep it and track\n    seenSystemContents.add(content);\n    result.push(msg);\n  }\n\n  return { messages: result, removedCount };\n}\n\n/**\n * Extracts a comparable string from a message's content.\n * Returns null if the content is too large or non-string (skip dedup for those).\n */\nfunction getContentString(msg: BaseMessage): string | null {\n  if (typeof msg.content === 'string') {\n    if (msg.content.length > DEDUP_MAX_CONTENT_LENGTH) {\n      return null;\n    }\n    return msg.content;\n  }\n  // Array content (e.g., Anthropic cache_control blocks) — serialize for comparison\n  // but only if reasonably sized\n  try {\n    const serialized = JSON.stringify(msg.content);\n    if (serialized.length > DEDUP_MAX_CONTENT_LENGTH) {\n      return null;\n    }\n    return serialized;\n  } catch {\n    return null;\n  }\n}\n"],"names":[],"mappings":";;;;AAKA;;;;;;;;;;;;;;;;;;;AAmBG;AACG,SAAU,yBAAyB,CAAC,QAAuB,EAAA;AAI/D,IAAA,IAAI,QAAQ,CAAC,MAAM,IAAI,CAAC,EAAE;AACxB,QAAA,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,CAAC,EAAE;IACtC;AAEA,IAAA,MAAM,kBAAkB,GAAG,IAAI,GAAG,EAAU;IAC5C,MAAM,MAAM,GAAkB,EAAE;IAChC,IAAI,YAAY,GAAG,CAAC;AAEpB,IAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;AACxC,QAAA,MAAM,GAAG,GAAG,QAAQ,CAAC,CAAC,CAAC;AACvB,QAAA,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,EAAE;;AAG1B,QAAA,IAAI,IAAI,KAAK,YAAY,CAAC,MAAM,EAAE;AAChC,YAAA,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC;YAChB;QACF;;AAGA,QAAA,IAAI,CAAC,KAAK,CAAC,EAAE;AACX,YAAA,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC;;AAEhB,YAAA,MAAM,OAAO,GAAG,gBAAgB,CAAC,GAAG,CAAC;AACrC,YAAA,IAAI,OAAO,IAAI,IAAI,EAAE;AACnB,gBAAA,kBAAkB,CAAC,GAAG,CAAC,OAAO,CAAC;YACjC;YACA;QACF;;AAGA,QAAA,MAAM,OAAO,GAAG,gBAAgB,CAAC,GAAG,CAAC;;AAGrC,QAAA,IAAI,OAAO,IAAI,IAAI,EAAE;AACnB,YAAA,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC;YAChB;QACF;;AAGA,QAAA,IAAI,kBAAkB,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE;AACnC,YAAA,YAAY,EAAE;AACd,YAAA,SAAS;QACX;;AAGA,QAAA,kBAAkB,CAAC,GAAG,CAAC,OAAO,CAAC;AAC/B,QAAA,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC;IAClB;AAEA,IAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE;AAC3C;AAEA;;;AAGG;AACH,SAAS,gBAAgB,CAAC,GAAgB,EAAA;AACxC,IAAA,IAAI,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ,EAAE;QACnC,IAAI,GAAG,CAAC,OAAO,CAAC,MAAM,GAAG,wBAAwB,EAAE;AACjD,YAAA,OAAO,IAAI;QACb;QACA,OAAO,GAAG,CAAC,OAAO;IACpB;;;AAGA,IAAA,IAAI;QACF,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC;AAC9C,QAAA,IAAI,UAAU,CAAC,MAAM,GAAG,wBAAwB,EAAE;AAChD,YAAA,OAAO,IAAI;QACb;AACA,QAAA,OAAO,UAAU;IACnB;AAAE,IAAA,MAAM;AACN,QAAA,OAAO,IAAI;IACb;AACF;;;;"}