{"version":3,"file":"contextPruning.cjs","sources":["../../../src/messages/contextPruning.ts"],"sourcesContent":["/**\n * Position-based context pruning for tool results.\n *\n * Uses position-based age: the distance of a message\n * from the conversation end as a fraction of total messages.\n *\n * Two degradation levels:\n * - Soft-trim: Keep head + tail of tool result content, drop middle.\n * - Hard-clear: Replace entire content with a placeholder.\n *\n * Messages in the \"protected zone\" (recent assistant turns, system/pre-first-human\n * messages, and messages with image content) are never pruned.\n */\n\nimport { ToolMessage, type BaseMessage } from '@langchain/core/messages';\nimport type { ContextPruningConfig } from '@/types/graph';\nimport type { TokenCounter } from '@/types/run';\nimport type { ContextPruningSettings } from './contextPruningSettings';\nimport { resolveContextPruningSettings } from './contextPruningSettings';\n\n/**\n * Checks if a message contains image content blocks.\n * Messages with images are skipped by position-based content degradation\n * because images cannot be meaningfully soft-trimmed or replaced with placeholders.\n */\nfunction hasImageContent(message: BaseMessage): boolean {\n  if (!Array.isArray(message.content)) {\n    return false;\n  }\n  return message.content.some(\n    (block) =>\n      typeof block === 'object' &&\n      'type' in block &&\n      (block.type === 'image_url' || block.type === 'image')\n  );\n}\n\n/**\n * Applies head+tail soft-trim to tool result content.\n */\nfunction softTrimContent(\n  content: string,\n  settings: ContextPruningSettings['softTrim']\n): string {\n  const { headChars, tailChars } = settings;\n  const indicator = `\\n\\n… [soft-trimmed: ${content.length} chars → ${headChars + tailChars} chars, middle removed] …\\n\\n`;\n  return content.slice(0, headChars) + indicator + content.slice(-tailChars);\n}\n\nexport interface ContextPruningResult {\n  /** Number of messages that were soft-trimmed. */\n  softTrimmed: number;\n  /** Number of messages that were hard-cleared. */\n  hardCleared: number;\n}\n\n/**\n * Applies position-based context pruning to tool result messages.\n *\n * Modifies messages in-place and updates indexTokenCountMap with recounted\n * token values for modified messages.\n *\n * @param params.messages - The full message array (modified in-place).\n * @param params.indexTokenCountMap - Token count map (updated in-place).\n * @param params.tokenCounter - Function to recount tokens after modification.\n * @param params.config - Partial context pruning config (merged with defaults).\n * @returns Counts of soft-trimmed and hard-cleared messages.\n */\nexport function applyContextPruning(params: {\n  messages: BaseMessage[];\n  indexTokenCountMap: Record<string, number | undefined>;\n  tokenCounter: TokenCounter;\n  config?: ContextPruningConfig;\n  resolvedSettings?: ContextPruningSettings;\n}): ContextPruningResult {\n  const {\n    messages,\n    indexTokenCountMap,\n    tokenCounter,\n    config,\n    resolvedSettings,\n  } = params;\n  const settings = resolvedSettings ?? resolveContextPruningSettings(config);\n\n  if (!settings.enabled || messages.length === 0) {\n    return { softTrimmed: 0, hardCleared: 0 };\n  }\n\n  const totalMessages = messages.length;\n  let softTrimmed = 0;\n  let hardCleared = 0;\n\n  // Find the protected zone: last N assistant turns from the end.\n  // An \"assistant turn\" is a contiguous sequence of AI + Tool messages.\n  const protectedIndices = new Set<number>();\n\n  // Always protect the system message (index 0 if present)\n  if (messages[0]?.getType() === 'system') {\n    protectedIndices.add(0);\n  }\n\n  // Protect messages before the first human message\n  for (let i = 0; i < totalMessages; i++) {\n    if (messages[i].getType() === 'human') {\n      break;\n    }\n    protectedIndices.add(i);\n  }\n\n  // Protect the last N assistant turns (walking backwards)\n  let assistantTurnsFound = 0;\n  let inAssistantSequence = false;\n  for (let i = totalMessages - 1; i >= 0; i--) {\n    const type = messages[i].getType();\n    if (type === 'ai' || type === 'tool') {\n      protectedIndices.add(i);\n      if (!inAssistantSequence) {\n        inAssistantSequence = true;\n      }\n    } else {\n      if (inAssistantSequence) {\n        assistantTurnsFound++;\n        inAssistantSequence = false;\n        if (assistantTurnsFound >= settings.keepLastAssistants) {\n          break;\n        }\n      }\n      // Protect the human message between assistant turns in the protected zone\n      if (assistantTurnsFound < settings.keepLastAssistants) {\n        protectedIndices.add(i);\n      }\n    }\n  }\n\n  // Process each tool message outside the protected zone\n  for (let i = 0; i < totalMessages; i++) {\n    const message = messages[i];\n    if (message.getType() !== 'tool') {\n      continue;\n    }\n    if (protectedIndices.has(i)) {\n      continue;\n    }\n    if (hasImageContent(message)) {\n      continue;\n    }\n\n    const content = message.content;\n    if (typeof content !== 'string') {\n      continue;\n    }\n    if (content.length < settings.minPrunableToolChars) {\n      continue;\n    }\n\n    // Compute age ratio: how far back from the end (0 = latest, 1 = oldest)\n    const ageRatio = (totalMessages - i) / totalMessages;\n\n    if (ageRatio >= settings.hardClearRatio && settings.hardClear.enabled) {\n      // Hard-clear: replace with placeholder\n      const cloned = new ToolMessage({\n        content: settings.hardClear.placeholder,\n        tool_call_id: (message as ToolMessage).tool_call_id,\n        name: message.name,\n        id: message.id,\n        additional_kwargs: message.additional_kwargs,\n        response_metadata: message.response_metadata,\n      });\n      messages[i] = cloned;\n      indexTokenCountMap[i] = tokenCounter(cloned);\n      hardCleared++;\n    } else if (ageRatio >= settings.softTrimRatio) {\n      // Soft-trim: keep head + tail\n      if (content.length > settings.softTrim.maxChars) {\n        const cloned = new ToolMessage({\n          content: softTrimContent(content, settings.softTrim),\n          tool_call_id: (message as ToolMessage).tool_call_id,\n          name: message.name,\n          id: message.id,\n          additional_kwargs: message.additional_kwargs,\n          response_metadata: message.response_metadata,\n        });\n        messages[i] = cloned;\n        indexTokenCountMap[i] = tokenCounter(cloned);\n        softTrimmed++;\n      }\n    }\n  }\n\n  return { softTrimmed, hardCleared };\n}\n"],"names":["messages","resolveContextPruningSettings","ToolMessage"],"mappings":"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"}