@librechat/agents 3.2.38 → 3.2.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/dist/cjs/agents/AgentContext.cjs +25 -8
  2. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  3. package/dist/cjs/graphs/Graph.cjs +7 -4
  4. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  5. package/dist/cjs/hooks/createWorkspacePolicyHook.cjs +4 -3
  6. package/dist/cjs/hooks/createWorkspacePolicyHook.cjs.map +1 -1
  7. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +20 -4
  8. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
  9. package/dist/cjs/llm/bedrock/index.cjs +7 -1
  10. package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
  11. package/dist/cjs/llm/bedrock/toolCache.cjs +5 -4
  12. package/dist/cjs/llm/bedrock/toolCache.cjs.map +1 -1
  13. package/dist/cjs/llm/bedrock/utils/message_inputs.cjs +34 -17
  14. package/dist/cjs/llm/bedrock/utils/message_inputs.cjs.map +1 -1
  15. package/dist/cjs/llm/openrouter/index.cjs +1 -0
  16. package/dist/cjs/llm/openrouter/index.cjs.map +1 -1
  17. package/dist/cjs/llm/openrouter/toolCache.cjs +18 -5
  18. package/dist/cjs/llm/openrouter/toolCache.cjs.map +1 -1
  19. package/dist/cjs/main.cjs +4 -0
  20. package/dist/cjs/messages/anthropicToolCache.cjs +75 -13
  21. package/dist/cjs/messages/anthropicToolCache.cjs.map +1 -1
  22. package/dist/cjs/messages/cache.cjs +91 -35
  23. package/dist/cjs/messages/cache.cjs.map +1 -1
  24. package/dist/cjs/summarization/node.cjs +3 -2
  25. package/dist/cjs/summarization/node.cjs.map +1 -1
  26. package/dist/cjs/tools/ReadFile.cjs +2 -2
  27. package/dist/cjs/tools/ReadFile.cjs.map +1 -1
  28. package/dist/cjs/tools/cloudflare/CloudflareProgrammaticToolCalling.cjs +11 -11
  29. package/dist/cjs/tools/cloudflare/CloudflareProgrammaticToolCalling.cjs.map +1 -1
  30. package/dist/cjs/tools/local/LocalCodingTools.cjs +11 -11
  31. package/dist/cjs/tools/local/LocalCodingTools.cjs.map +1 -1
  32. package/dist/esm/agents/AgentContext.mjs +26 -9
  33. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  34. package/dist/esm/graphs/Graph.mjs +8 -5
  35. package/dist/esm/graphs/Graph.mjs.map +1 -1
  36. package/dist/esm/hooks/createWorkspacePolicyHook.mjs +4 -3
  37. package/dist/esm/hooks/createWorkspacePolicyHook.mjs.map +1 -1
  38. package/dist/esm/llm/anthropic/utils/message_inputs.mjs +20 -4
  39. package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
  40. package/dist/esm/llm/bedrock/index.mjs +7 -1
  41. package/dist/esm/llm/bedrock/index.mjs.map +1 -1
  42. package/dist/esm/llm/bedrock/toolCache.mjs +5 -4
  43. package/dist/esm/llm/bedrock/toolCache.mjs.map +1 -1
  44. package/dist/esm/llm/bedrock/utils/message_inputs.mjs +34 -17
  45. package/dist/esm/llm/bedrock/utils/message_inputs.mjs.map +1 -1
  46. package/dist/esm/llm/openrouter/index.mjs +1 -0
  47. package/dist/esm/llm/openrouter/index.mjs.map +1 -1
  48. package/dist/esm/llm/openrouter/toolCache.mjs +18 -5
  49. package/dist/esm/llm/openrouter/toolCache.mjs.map +1 -1
  50. package/dist/esm/main.mjs +2 -2
  51. package/dist/esm/messages/anthropicToolCache.mjs +75 -13
  52. package/dist/esm/messages/anthropicToolCache.mjs.map +1 -1
  53. package/dist/esm/messages/cache.mjs +88 -36
  54. package/dist/esm/messages/cache.mjs.map +1 -1
  55. package/dist/esm/summarization/node.mjs +4 -3
  56. package/dist/esm/summarization/node.mjs.map +1 -1
  57. package/dist/esm/tools/ReadFile.mjs +2 -2
  58. package/dist/esm/tools/ReadFile.mjs.map +1 -1
  59. package/dist/esm/tools/cloudflare/CloudflareProgrammaticToolCalling.mjs +11 -11
  60. package/dist/esm/tools/cloudflare/CloudflareProgrammaticToolCalling.mjs.map +1 -1
  61. package/dist/esm/tools/local/LocalCodingTools.mjs +11 -11
  62. package/dist/esm/tools/local/LocalCodingTools.mjs.map +1 -1
  63. package/dist/types/agents/AgentContext.d.ts +11 -0
  64. package/dist/types/agents/__tests__/promptCacheLiveHelpers.d.ts +2 -0
  65. package/dist/types/llm/bedrock/index.d.ts +13 -0
  66. package/dist/types/llm/bedrock/toolCache.d.ts +2 -1
  67. package/dist/types/llm/openrouter/index.d.ts +8 -0
  68. package/dist/types/llm/openrouter/toolCache.d.ts +2 -1
  69. package/dist/types/messages/anthropicToolCache.d.ts +2 -1
  70. package/dist/types/messages/cache.d.ts +49 -5
  71. package/dist/types/tools/ReadFile.d.ts +4 -4
  72. package/dist/types/types/llm.d.ts +14 -0
  73. package/package.json +1 -1
  74. package/src/agents/AgentContext.ts +64 -17
  75. package/src/agents/__tests__/AgentContext.anthropic.live.test.ts +6 -2
  76. package/src/agents/__tests__/AgentContext.bedrock.live.test.ts +7 -5
  77. package/src/agents/__tests__/AgentContext.openrouter.live.test.ts +1 -1
  78. package/src/agents/__tests__/AgentContext.test.ts +31 -19
  79. package/src/agents/__tests__/promptCacheLiveHelpers.ts +6 -2
  80. package/src/graphs/Graph.ts +40 -4
  81. package/src/hooks/__tests__/createWorkspacePolicyHook.test.ts +12 -12
  82. package/src/hooks/createWorkspacePolicyHook.ts +7 -6
  83. package/src/llm/anthropic/utils/message_inputs.ts +33 -6
  84. package/src/llm/bedrock/index.ts +21 -1
  85. package/src/llm/bedrock/llm.spec.ts +61 -0
  86. package/src/llm/bedrock/toolCache.test.ts +24 -0
  87. package/src/llm/bedrock/toolCache.ts +12 -7
  88. package/src/llm/bedrock/utils/message_inputs.ts +57 -40
  89. package/src/llm/openrouter/index.ts +9 -0
  90. package/src/llm/openrouter/toolCache.test.ts +52 -1
  91. package/src/llm/openrouter/toolCache.ts +40 -6
  92. package/src/messages/__tests__/anthropicToolCache.test.ts +168 -0
  93. package/src/messages/anthropicToolCache.ts +118 -15
  94. package/src/messages/cache.test.ts +175 -0
  95. package/src/messages/cache.ts +133 -48
  96. package/src/summarization/node.ts +21 -2
  97. package/src/tools/ReadFile.ts +2 -2
  98. package/src/tools/__tests__/LocalExecutionTools.test.ts +25 -25
  99. package/src/tools/__tests__/ProgrammaticToolCalling.test.ts +5 -5
  100. package/src/tools/__tests__/ReadFile.test.ts +3 -3
  101. package/src/tools/__tests__/ToolNode.session.test.ts +2 -2
  102. package/src/tools/__tests__/workspaceSeam.test.ts +2 -2
  103. package/src/tools/cloudflare/CloudflareProgrammaticToolCalling.ts +11 -11
  104. package/src/tools/local/LocalCodingTools.ts +14 -14
  105. package/src/types/llm.ts +14 -0
@@ -1 +1 @@
1
- {"version":3,"file":"cache.cjs","names":["BaseMessage","toLangChainContent","withMessageRole","AIMessage","HumanMessage","SystemMessage","ToolMessage"],"sources":["../../../src/messages/cache.ts"],"sourcesContent":["import {\n AIMessage,\n BaseMessage,\n ToolMessage,\n HumanMessage,\n SystemMessage,\n MessageContentComplex,\n} from '@langchain/core/messages';\nimport type Anthropic from '@anthropic-ai/sdk';\nimport type { AnthropicMessage } from '@/types/messages';\nimport { toLangChainContent } from './langchain';\nimport { ContentTypes } from '@/common/enum';\nimport { withMessageRole } from './format';\n\ntype MessageWithContent = {\n content?: string | MessageContentComplex[];\n};\n\ntype MessageContentWithCacheControl = MessageContentComplex & {\n cache_control?: unknown;\n};\n\n/**\n * Deep clones a message's content to prevent mutation of the original.\n */\nfunction deepCloneContent<T extends string | MessageContentComplex[]>(\n content: T\n): T {\n if (typeof content === 'string') {\n return content;\n }\n if (Array.isArray(content)) {\n return content.map((block) => ({ ...block })) as T;\n }\n return content;\n}\n\n/**\n * Clones a message with new content. For LangChain BaseMessage instances,\n * constructs a proper class instance so that `instanceof` checks are preserved\n * in downstream code (e.g., ensureThinkingBlockInMessages).\n * For plain objects (AnthropicMessage), uses object spread.\n */\nexport function cloneMessage<T extends MessageWithContent>(\n message: T,\n content: string | MessageContentComplex[]\n): T {\n if (message instanceof BaseMessage) {\n const baseParams = {\n content: toLangChainContent(content),\n additional_kwargs: { ...message.additional_kwargs },\n response_metadata: { ...message.response_metadata },\n id: message.id,\n name: message.name,\n };\n\n const msgType = message.getType();\n switch (msgType) {\n case 'ai':\n return withMessageRole(\n new AIMessage({\n ...baseParams,\n tool_calls: (message as unknown as AIMessage).tool_calls,\n }),\n 'assistant'\n ) as unknown as T;\n case 'human':\n return withMessageRole(\n new HumanMessage(baseParams),\n 'user'\n ) as unknown as T;\n case 'system':\n return withMessageRole(\n new SystemMessage(baseParams),\n 'system'\n ) as unknown as T;\n case 'tool':\n return withMessageRole(\n new ToolMessage({\n ...baseParams,\n tool_call_id: (message as unknown as ToolMessage).tool_call_id,\n }),\n 'tool'\n ) as unknown as T;\n default:\n break;\n }\n }\n\n const {\n lc_kwargs: _lc_kwargs,\n lc_serializable: _lc_serializable,\n lc_namespace: _lc_namespace,\n ...rest\n } = message as T & {\n lc_kwargs?: unknown;\n lc_serializable?: unknown;\n lc_namespace?: unknown;\n };\n\n const cloned = { ...rest, content } as T;\n\n // LangChain messages don't have a direct 'role' property - derive it from getType()\n if (\n 'getType' in message &&\n typeof message.getType === 'function' &&\n !('role' in cloned)\n ) {\n const msgType = (message as unknown as BaseMessage).getType();\n const roleMap: Record<string, string> = {\n human: 'user',\n ai: 'assistant',\n system: 'system',\n tool: 'tool',\n };\n (cloned as Record<string, unknown>).role = roleMap[msgType] || msgType;\n }\n\n return cloned;\n}\n\nfunction stripAnthropicCacheControlFromBlocks(\n content: MessageContentComplex[]\n): { content: MessageContentComplex[]; modified: boolean } {\n let modified = false;\n const strippedContent = content.map((block) => {\n if (!('cache_control' in block)) {\n return block;\n }\n\n const cloned: MessageContentWithCacheControl = { ...block };\n delete cloned.cache_control;\n modified = true;\n return cloned;\n });\n\n return { content: strippedContent, modified };\n}\n\nfunction sanitizeBedrockSystemMessage<T extends MessageWithContent>(\n message: T\n): T {\n const content = message.content;\n if (!Array.isArray(content)) {\n return message;\n }\n\n const stripped = stripAnthropicCacheControlFromBlocks(content);\n if (!stripped.modified) {\n return message;\n }\n\n return cloneMessage(message, stripped.content);\n}\n\n/**\n * Anthropic API: Adds cache control to the appropriate user messages in the payload.\n * Strips ALL existing cache control (both Anthropic and Bedrock formats) from all messages,\n * then adds fresh cache control to the last 2 user messages in a single backward pass.\n * This ensures we don't accumulate stale cache points across multiple turns.\n * Returns a new array - only clones messages that require modification.\n * @param messages - The array of message objects.\n * @returns - A new array of message objects with cache control added.\n */\nexport function addCacheControl<T extends AnthropicMessage | BaseMessage>(\n messages: T[]\n): T[] {\n if (!Array.isArray(messages) || messages.length < 2) {\n return messages;\n }\n\n const updatedMessages: T[] = [...messages];\n let userMessagesModified = 0;\n\n for (let i = updatedMessages.length - 1; i >= 0; i--) {\n const originalMessage = updatedMessages[i];\n const content = originalMessage.content;\n const isUserMessage =\n ('getType' in originalMessage && originalMessage.getType() === 'human') ||\n ('role' in originalMessage && originalMessage.role === 'user');\n const hasArrayContent = Array.isArray(content);\n const needsCacheAdd =\n userMessagesModified < 2 &&\n isUserMessage &&\n !isSyntheticMetaMessage(originalMessage) &&\n (typeof content === 'string' || hasArrayContent);\n\n // Skip messages that don't need any work\n if (!needsCacheAdd && !hasArrayContent) {\n continue;\n }\n\n let workingContent: MessageContentComplex[];\n let modified = false;\n\n if (hasArrayContent) {\n // Single pass: clone blocks, strip cache markers and cache points,\n // find last text block index for cache insertion — all at once.\n const src = content as MessageContentComplex[];\n workingContent = [];\n let lastTextIndex = -1;\n for (let j = 0; j < src.length; j++) {\n const block = src[j];\n if (isCachePoint(block)) {\n modified = true;\n continue; // skip cache point blocks\n }\n const cloned = { ...block };\n if ('cache_control' in cloned) {\n delete (cloned as Record<string, unknown>).cache_control;\n modified = true;\n }\n if ('type' in cloned && cloned.type === 'text') {\n lastTextIndex = workingContent.length;\n }\n workingContent.push(cloned as MessageContentComplex);\n }\n\n if (!modified && !needsCacheAdd) {\n continue; // nothing to strip and no cache to add\n }\n\n // Add cache control to the last text block for user messages\n if (needsCacheAdd && lastTextIndex >= 0) {\n (\n workingContent[lastTextIndex] as Anthropic.TextBlockParam\n ).cache_control = {\n type: 'ephemeral',\n };\n userMessagesModified++;\n }\n } else if (typeof content === 'string' && needsCacheAdd) {\n workingContent = [\n { type: 'text', text: content, cache_control: { type: 'ephemeral' } },\n ] as unknown as MessageContentComplex[];\n userMessagesModified++;\n } else {\n continue;\n }\n\n updatedMessages[i] = cloneMessage(\n originalMessage as MessageWithContent,\n workingContent\n ) as T;\n }\n\n return updatedMessages;\n}\n\n/**\n * Checks if a content block is a cache point\n */\nfunction isCachePoint(block: MessageContentComplex): boolean {\n return 'cachePoint' in block && !('type' in block);\n}\n\n/**\n * Block types that must never anchor the tail cache breakpoint, because the\n * marker would not survive to the model call:\n * - `thinking` / `redacted_thinking`: native Anthropic reasoning — the API\n * rejects `cache_control` on these blocks.\n * - `reasoning_content` / `reasoning` / `think`: foreign reasoning (Bedrock,\n * Google, LibreChat) that `_convertMessagesToAnthropicPayload` DROPS on\n * assistant turns during a cross-provider handoff.\n * - `input_json_delta`: persisted partial tool-input deltas, also DROPPED by\n * `_convertMessagesToAnthropicPayload` (the assembled input is restored onto\n * the tool_use block).\n * Anchoring the only breakpoint on a block that is about to disappear silently\n * loses tail caching, so all of these are excluded.\n */\nconst NON_ANCHORABLE_BLOCK_TYPES = new Set([\n 'thinking',\n 'redacted_thinking',\n 'reasoning_content',\n 'reasoning',\n 'think',\n 'input_json_delta',\n]);\n\n/**\n * A block can anchor the tail cache breakpoint when it is a real content block\n * that the Anthropic API accepts `cache_control` on and that survives provider\n * conversion. Reasoning / dropped-delta blocks are excluded (see\n * {@link NON_ANCHORABLE_BLOCK_TYPES}), and empty text blocks are not cacheable,\n * so both are skipped.\n */\nfunction isTailCacheableBlock(block: MessageContentComplex): boolean {\n if (isCachePoint(block)) {\n return false;\n }\n const type = (block as { type?: string }).type;\n if (type == null || NON_ANCHORABLE_BLOCK_TYPES.has(type)) {\n return false;\n }\n if (type === 'text') {\n const text = (block as { text?: string }).text;\n return text != null && text.trim() !== '';\n }\n return true;\n}\n\n/**\n * Anthropic API: single tail cache breakpoint (default strategy).\n *\n * Places exactly ONE `cache_control` marker on the last cacheable block of the\n * final non-synthetic message, mirroring the Claude Code strategy\n * (`markerIndex = messages.length - 1`). Because the marker always rides the\n * true tail, the entire conversation prefix is written once and read back on\n * the next turn as the history grows append-only — instead of the rolling\n * \"last two user messages\" markers, which leave freshly appended tool/assistant\n * turns outside the cached prefix and re-write large spans every step.\n *\n * Stale markers (Anthropic `cache_control` and Bedrock cache points) are\n * stripped from every message in a single backward pass so exactly one marker\n * survives. Synthetic skill/meta messages are skipped as anchors (their volatile\n * content must not pin the cache) but still have stale markers removed.\n *\n * Returns a new array; only messages that require modification are cloned.\n */\nexport function addTailCacheControl<T extends AnthropicMessage | BaseMessage>(\n messages: T[]\n): T[] {\n if (!Array.isArray(messages) || messages.length === 0) {\n return messages;\n }\n\n const updatedMessages: T[] = [...messages];\n let markerPlaced = false;\n\n for (let i = updatedMessages.length - 1; i >= 0; i--) {\n const originalMessage = updatedMessages[i];\n const content = originalMessage.content;\n const hasArrayContent = Array.isArray(content);\n const canPlaceMarker =\n !markerPlaced && !isSyntheticMetaMessage(originalMessage);\n\n // Earlier string-content messages carry no markers to strip.\n if (!canPlaceMarker && !hasArrayContent) {\n continue;\n }\n\n let workingContent: MessageContentComplex[];\n let modified = false;\n\n if (hasArrayContent) {\n const src = content as MessageContentComplex[];\n workingContent = [];\n let tailIndex = -1;\n for (let j = 0; j < src.length; j++) {\n const block = src[j];\n if (isCachePoint(block)) {\n modified = true;\n continue;\n }\n const cloned = { ...block };\n if ('cache_control' in cloned) {\n delete (cloned as Record<string, unknown>).cache_control;\n modified = true;\n }\n if (\n canPlaceMarker &&\n isTailCacheableBlock(cloned as MessageContentComplex)\n ) {\n tailIndex = workingContent.length;\n }\n workingContent.push(cloned as MessageContentComplex);\n }\n\n if (canPlaceMarker && tailIndex >= 0) {\n (workingContent[tailIndex] as Anthropic.TextBlockParam).cache_control =\n {\n type: 'ephemeral',\n };\n markerPlaced = true;\n modified = true;\n }\n\n if (!modified) {\n continue;\n }\n } else if (\n typeof content === 'string' &&\n canPlaceMarker &&\n content.trim() !== ''\n ) {\n workingContent = [\n { type: 'text', text: content, cache_control: { type: 'ephemeral' } },\n ] as unknown as MessageContentComplex[];\n markerPlaced = true;\n } else {\n continue;\n }\n\n updatedMessages[i] = cloneMessage(\n originalMessage as MessageWithContent,\n workingContent\n ) as T;\n }\n\n return updatedMessages;\n}\n\nfunction getMessageRole(message: MessageWithContent): string | undefined {\n if (message instanceof BaseMessage) {\n return message.getType();\n }\n if ('role' in message && typeof message.role === 'string') {\n return message.role;\n }\n return undefined;\n}\n\nconst SKILL_MESSAGE_SOURCE = 'skill';\n\n/**\n * Synthetic skill/meta messages (reconstructed skill bodies, primed SKILL.md\n * instructions) are re-injected every turn and are not stable conversation\n * turns. They must not anchor a fresh prompt-cache marker — doing so pins the\n * cache to a volatile/duplicated prefix. Stale markers are still stripped from\n * them; only the *adding* of new markers is suppressed. Detected via\n * `additional_kwargs.isMeta === true` or `additional_kwargs.source === 'skill'`.\n */\nfunction isSyntheticMetaMessage(message: MessageWithContent): boolean {\n const { additional_kwargs: kwargs } = message as {\n additional_kwargs?: { isMeta?: unknown; source?: unknown };\n };\n if (kwargs == null) {\n return false;\n }\n return kwargs.isMeta === true || kwargs.source === SKILL_MESSAGE_SOURCE;\n}\n\nfunction isCacheableConversationMessage(message: MessageWithContent): boolean {\n const role = getMessageRole(message);\n return (\n role === 'human' || role === 'user' || role === 'ai' || role === 'assistant'\n );\n}\n\nfunction isAssistantConversationMessage(message: MessageWithContent): boolean {\n const role = getMessageRole(message);\n return role === 'ai' || role === 'assistant';\n}\n\nfunction hasCacheMarker(message: MessageWithContent): boolean {\n return (\n Array.isArray(message.content) &&\n message.content.some((block) => 'cache_control' in block)\n );\n}\n\nfunction addCacheControlToRecentMessages<\n T extends AnthropicMessage | BaseMessage,\n>(\n messages: T[],\n maxCachePoints: number,\n canUseMessage: (message: MessageWithContent) => boolean\n): T[] {\n if (\n !Array.isArray(messages) ||\n messages.length === 0 ||\n maxCachePoints <= 0\n ) {\n return messages;\n }\n\n const updatedMessages: T[] = [...messages];\n let cachePointsAdded = 0;\n\n for (let i = updatedMessages.length - 1; i >= 0; i--) {\n const originalMessage = updatedMessages[i];\n const content = originalMessage.content;\n const hasArrayContent = Array.isArray(content);\n const canAddCache =\n cachePointsAdded < maxCachePoints &&\n canUseMessage(originalMessage) &&\n !isSyntheticMetaMessage(originalMessage);\n\n if (!canAddCache && !hasArrayContent) {\n continue;\n }\n\n let workingContent: MessageContentComplex[];\n let modified = false;\n\n if (hasArrayContent) {\n const src = content as MessageContentComplex[];\n workingContent = [];\n let lastNonEmptyTextIndex = -1;\n\n for (let j = 0; j < src.length; j++) {\n const block = src[j];\n if (isCachePoint(block)) {\n modified = true;\n continue;\n }\n\n const cloned = { ...block };\n if ('cache_control' in cloned) {\n delete (cloned as Record<string, unknown>).cache_control;\n modified = true;\n }\n\n if ('type' in cloned && cloned.type === 'text') {\n const text = (cloned as { text?: string }).text;\n if (text != null && text.trim() !== '') {\n lastNonEmptyTextIndex = workingContent.length;\n }\n }\n workingContent.push(cloned as MessageContentComplex);\n }\n\n if (canAddCache && lastNonEmptyTextIndex >= 0) {\n (\n workingContent[lastNonEmptyTextIndex] as Anthropic.TextBlockParam\n ).cache_control = {\n type: 'ephemeral',\n };\n cachePointsAdded++;\n modified = true;\n }\n\n if (!modified) {\n continue;\n }\n } else if (\n typeof content === 'string' &&\n content.trim() !== '' &&\n canAddCache\n ) {\n workingContent = [\n { type: 'text', text: content, cache_control: { type: 'ephemeral' } },\n ] as unknown as MessageContentComplex[];\n cachePointsAdded++;\n } else {\n continue;\n }\n\n updatedMessages[i] = cloneMessage(\n originalMessage as MessageWithContent,\n workingContent\n ) as T;\n }\n\n return updatedMessages;\n}\n\nexport function addCacheControlToStablePrefixMessages<\n T extends AnthropicMessage | BaseMessage,\n>(messages: T[], maxCachePoints: number): T[] {\n const assistantMarked = addCacheControlToRecentMessages(\n messages,\n maxCachePoints,\n isAssistantConversationMessage\n );\n\n if (assistantMarked.some(hasCacheMarker)) {\n return assistantMarked;\n }\n\n return addCacheControlToRecentMessages(\n messages,\n maxCachePoints,\n isCacheableConversationMessage\n );\n}\n\n/**\n * Checks if a message's content has Anthropic cache_control fields.\n */\nfunction hasAnthropicCacheControl(content: MessageContentComplex[]): boolean {\n for (let i = 0; i < content.length; i++) {\n if ('cache_control' in content[i]) return true;\n }\n return false;\n}\n\n/**\n * Removes all Anthropic cache_control fields from messages\n * Used when switching from Anthropic to Bedrock provider\n * Returns a new array - only clones messages that require modification.\n */\nexport function stripAnthropicCacheControl<T extends MessageWithContent>(\n messages: T[]\n): T[] {\n if (!Array.isArray(messages)) {\n return messages;\n }\n\n const updatedMessages: T[] = [...messages];\n\n for (let i = 0; i < updatedMessages.length; i++) {\n const originalMessage = updatedMessages[i];\n const content = originalMessage.content;\n\n if (!Array.isArray(content) || !hasAnthropicCacheControl(content)) {\n continue;\n }\n\n const clonedContent = deepCloneContent(content);\n for (let j = 0; j < clonedContent.length; j++) {\n const block = clonedContent[j] as Record<string, unknown>;\n if ('cache_control' in block) {\n delete block.cache_control;\n }\n }\n updatedMessages[i] = cloneMessage(originalMessage, clonedContent);\n }\n\n return updatedMessages;\n}\n\n/**\n * Checks if a message's content has Bedrock cachePoint blocks.\n */\nfunction hasBedrockCachePoint(content: MessageContentComplex[]): boolean {\n for (let i = 0; i < content.length; i++) {\n if (isCachePoint(content[i])) return true;\n }\n return false;\n}\n\n/**\n * Removes all Bedrock cachePoint blocks from messages\n * Used when switching from Bedrock to Anthropic provider\n * Returns a new array - only clones messages that require modification.\n */\nexport function stripBedrockCacheControl<T extends MessageWithContent>(\n messages: T[]\n): T[] {\n if (!Array.isArray(messages)) {\n return messages;\n }\n\n const updatedMessages: T[] = [...messages];\n\n for (let i = 0; i < updatedMessages.length; i++) {\n const originalMessage = updatedMessages[i];\n const content = originalMessage.content;\n\n if (!Array.isArray(content) || !hasBedrockCachePoint(content)) {\n continue;\n }\n\n const clonedContent = deepCloneContent(content).filter(\n (block) => !isCachePoint(block as MessageContentComplex)\n );\n updatedMessages[i] = cloneMessage(originalMessage, clonedContent);\n }\n\n return updatedMessages;\n}\n\n/**\n * Adds Bedrock Converse API cache points to the latest two user messages.\n * Inserts `{ cachePoint: { type: 'default' } }` as a separate content block\n * immediately after the last text block in each targeted message.\n * Strips ALL existing cache control (both Bedrock and Anthropic formats) from all messages,\n * then adds fresh cache points to the latest two non-tool user messages in a single backward pass.\n * This ensures we don't accumulate stale cache points across multiple turns.\n * Returns a new array - only clones messages that require modification.\n * @param messages - The array of message objects.\n * @returns - A new array of message objects with cache points added.\n */\nexport function addBedrockCacheControl<\n T extends MessageWithContent & { getType?: () => string; role?: string },\n>(messages: T[]): T[] {\n if (!Array.isArray(messages) || messages.length === 0) {\n return messages;\n }\n\n const updatedMessages: T[] = [...messages];\n let cachePointsAdded = 0;\n\n for (let i = updatedMessages.length - 1; i >= 0; i--) {\n const originalMessage = updatedMessages[i];\n const messageType =\n 'getType' in originalMessage &&\n typeof originalMessage.getType === 'function'\n ? originalMessage.getType()\n : undefined;\n const messageRole =\n 'role' in originalMessage && typeof originalMessage.role === 'string'\n ? originalMessage.role\n : undefined;\n\n const isSystemMessage =\n messageType === 'system' || messageRole === 'system';\n if (isSystemMessage) {\n updatedMessages[i] = sanitizeBedrockSystemMessage(originalMessage);\n continue;\n }\n\n const isToolMessage = messageType === 'tool' || messageRole === 'tool';\n const isUserMessage = messageType === 'human' || messageRole === 'user';\n const content = originalMessage.content;\n const hasSerializationProps =\n 'lc_kwargs' in originalMessage ||\n 'lc_serializable' in originalMessage ||\n 'lc_namespace' in originalMessage;\n const hasArrayContent = Array.isArray(content);\n const isEmptyString = typeof content === 'string' && content === '';\n const needsCacheAdd =\n cachePointsAdded < 2 &&\n isUserMessage &&\n !isToolMessage &&\n !isEmptyString &&\n !isSyntheticMetaMessage(originalMessage) &&\n (typeof content === 'string' || hasArrayContent);\n\n if (!needsCacheAdd && !hasArrayContent && !hasSerializationProps) {\n continue;\n }\n\n let workingContent: string | MessageContentComplex[];\n let modified = hasSerializationProps;\n\n if (hasArrayContent) {\n // Single pass: clone blocks, strip cache markers, find last\n // non-empty text block for cache point insertion — all at once.\n const src = content as MessageContentComplex[];\n workingContent = [];\n let lastNonEmptyTextIndex = -1;\n for (let j = 0; j < src.length; j++) {\n const block = src[j];\n if (isCachePoint(block)) {\n modified = true;\n continue;\n }\n const cloned = { ...block };\n if ('cache_control' in cloned) {\n delete (cloned as Record<string, unknown>).cache_control;\n modified = true;\n }\n const type = (cloned as { type?: string }).type;\n if (type === ContentTypes.TEXT || type === 'text') {\n const text = (cloned as { text?: string }).text;\n if (text != null && text.trim() !== '') {\n lastNonEmptyTextIndex = workingContent.length;\n }\n }\n workingContent.push(cloned as MessageContentComplex);\n }\n\n if (!modified && !needsCacheAdd) {\n continue;\n }\n\n // Insert cache point after the last non-empty text block.\n // Skip if no cacheable text content exists (whitespace-only messages).\n if (needsCacheAdd && lastNonEmptyTextIndex >= 0) {\n workingContent.splice(lastNonEmptyTextIndex + 1, 0, {\n cachePoint: { type: 'default' },\n } as MessageContentComplex);\n cachePointsAdded++;\n }\n } else if (typeof content === 'string' && needsCacheAdd) {\n workingContent = [\n { type: ContentTypes.TEXT, text: content },\n { cachePoint: { type: 'default' } } as MessageContentComplex,\n ];\n cachePointsAdded++;\n } else if (typeof content === 'string' && hasSerializationProps) {\n workingContent = content;\n } else {\n continue;\n }\n\n updatedMessages[i] = cloneMessage(originalMessage, workingContent);\n }\n\n return updatedMessages;\n}\n\n/**\n * Bedrock Converse API: single tail cache breakpoint (default strategy).\n *\n * The Bedrock counterpart of {@link addTailCacheControl}. Strips ALL existing\n * cache control (Bedrock cache points and Anthropic `cache_control`) from every\n * message, then inserts exactly ONE `{ cachePoint: { type: 'default' } }` block\n * immediately after the last non-empty text block of the most recent\n * non-synthetic, non-system message. Anchoring on the rolling tail keeps the\n * cached prefix append-only as the conversation grows, instead of re-writing\n * large spans every turn with the legacy \"last two user messages\" cache points.\n *\n * System messages are sanitized (Anthropic `cache_control` stripped) but never\n * anchored. Synthetic skill/meta messages are skipped as anchors so their\n * volatile content cannot pin the cache.\n *\n * Returns a new array - only clones messages that require modification.\n */\nexport function addBedrockTailCacheControl<\n T extends MessageWithContent & { getType?: () => string; role?: string },\n>(messages: T[]): T[] {\n if (!Array.isArray(messages) || messages.length === 0) {\n return messages;\n }\n\n const updatedMessages: T[] = [...messages];\n let cachePointPlaced = false;\n\n for (let i = updatedMessages.length - 1; i >= 0; i--) {\n const originalMessage = updatedMessages[i];\n const messageType =\n 'getType' in originalMessage &&\n typeof originalMessage.getType === 'function'\n ? originalMessage.getType()\n : undefined;\n const messageRole =\n 'role' in originalMessage && typeof originalMessage.role === 'string'\n ? originalMessage.role\n : undefined;\n\n const isSystemMessage =\n messageType === 'system' || messageRole === 'system';\n if (isSystemMessage) {\n updatedMessages[i] = sanitizeBedrockSystemMessage(originalMessage);\n continue;\n }\n\n const content = originalMessage.content;\n const hasSerializationProps =\n 'lc_kwargs' in originalMessage ||\n 'lc_serializable' in originalMessage ||\n 'lc_namespace' in originalMessage;\n const hasArrayContent = Array.isArray(content);\n const isEmptyString = typeof content === 'string' && content === '';\n const canPlaceCachePoint =\n !cachePointPlaced &&\n !isEmptyString &&\n !isSyntheticMetaMessage(originalMessage) &&\n (typeof content === 'string' || hasArrayContent);\n\n if (!canPlaceCachePoint && !hasArrayContent && !hasSerializationProps) {\n continue;\n }\n\n let workingContent: string | MessageContentComplex[];\n let modified = hasSerializationProps;\n\n if (hasArrayContent) {\n const src = content as MessageContentComplex[];\n workingContent = [];\n let lastNonEmptyTextIndex = -1;\n for (let j = 0; j < src.length; j++) {\n const block = src[j];\n if (isCachePoint(block)) {\n modified = true;\n continue;\n }\n const cloned = { ...block };\n if ('cache_control' in cloned) {\n delete (cloned as Record<string, unknown>).cache_control;\n modified = true;\n }\n const type = (cloned as { type?: string }).type;\n if (type === ContentTypes.TEXT || type === 'text') {\n const text = (cloned as { text?: string }).text;\n if (text != null && text.trim() !== '') {\n lastNonEmptyTextIndex = workingContent.length;\n }\n }\n workingContent.push(cloned as MessageContentComplex);\n }\n\n if (!modified && !canPlaceCachePoint) {\n continue;\n }\n\n if (canPlaceCachePoint && lastNonEmptyTextIndex >= 0) {\n workingContent.splice(lastNonEmptyTextIndex + 1, 0, {\n cachePoint: { type: 'default' },\n } as MessageContentComplex);\n cachePointPlaced = true;\n modified = true;\n }\n } else if (typeof content === 'string' && canPlaceCachePoint) {\n workingContent = [\n { type: ContentTypes.TEXT, text: content },\n { cachePoint: { type: 'default' } } as MessageContentComplex,\n ];\n cachePointPlaced = true;\n } else if (typeof content === 'string' && hasSerializationProps) {\n workingContent = content;\n } else {\n continue;\n }\n\n updatedMessages[i] = cloneMessage(originalMessage, workingContent);\n }\n\n return updatedMessages;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"cache.cjs","names":["BaseMessage","toLangChainContent","withMessageRole","AIMessage","HumanMessage","SystemMessage","ToolMessage"],"sources":["../../../src/messages/cache.ts"],"sourcesContent":["import {\n AIMessage,\n BaseMessage,\n ToolMessage,\n HumanMessage,\n SystemMessage,\n MessageContentComplex,\n} from '@langchain/core/messages';\nimport type Anthropic from '@anthropic-ai/sdk';\nimport type { AnthropicMessage } from '@/types/messages';\nimport { toLangChainContent } from './langchain';\nimport { ContentTypes } from '@/common/enum';\nimport { withMessageRole } from './format';\n\ntype MessageWithContent = {\n content?: string | MessageContentComplex[];\n};\n\ntype MessageContentWithCacheControl = MessageContentComplex & {\n cache_control?: unknown;\n};\n\n/**\n * Prompt-cache breakpoint TTL.\n *\n * Both Anthropic (`cache_control.ttl`) and Bedrock (`cachePoint.ttl`) accept\n * `'5m'` (the legacy provider default) and `'1h'` (the extended cache). When\n * prompt caching is enabled the SDK now defaults to the 1-hour extended cache\n * (see {@link DEFAULT_PROMPT_CACHE_TTL}); pass `'5m'` to opt back into the\n * legacy 5-minute behavior.\n */\nexport type PromptCacheTtl = '5m' | '1h';\n\n/**\n * Default TTL applied wherever a prompt-cache breakpoint is added. The 1-hour\n * extended cache keeps prefixes warm across longer gaps between turns, at the\n * cost of a higher one-time cache-write multiplier (2x vs 1.25x for 5m).\n */\nexport const DEFAULT_PROMPT_CACHE_TTL: PromptCacheTtl = '1h';\n\n/**\n * Resolve an optionally-configured TTL to a concrete value, defaulting to the\n * 1-hour extended cache. Used at the Anthropic/Bedrock prompt-cache call sites.\n */\nexport function resolvePromptCacheTtl(\n ttl: PromptCacheTtl | undefined\n): PromptCacheTtl {\n return ttl ?? DEFAULT_PROMPT_CACHE_TTL;\n}\n\n/** Anthropic `cache_control` shape (the SDK accepts an optional `ttl`). */\ntype AnthropicCacheControl = { type: 'ephemeral'; ttl?: '1h' };\n\n/**\n * Build an Anthropic `cache_control` breakpoint for the given TTL. `'5m'` (or\n * `undefined`) omits the `ttl` field — that is the provider default, so the\n * payload stays byte-identical to the legacy 5-minute marker. `'1h'` adds the\n * explicit extended-cache `ttl`.\n */\nexport function buildAnthropicCacheControl(\n ttl?: PromptCacheTtl\n): AnthropicCacheControl {\n return ttl === '1h'\n ? { type: 'ephemeral', ttl: '1h' }\n : { type: 'ephemeral' };\n}\n\n/** Bedrock `cachePoint` shape (the SDK accepts an optional `ttl`). */\ntype BedrockCachePoint = { type: 'default'; ttl?: '1h' };\n\n/**\n * Build a Bedrock `cachePoint` for the given TTL. Mirrors\n * {@link buildAnthropicCacheControl}: `'5m'`/`undefined` omits `ttl` (the\n * legacy default), `'1h'` adds the extended-cache `ttl`.\n */\nexport function buildBedrockCachePoint(\n ttl?: PromptCacheTtl\n): BedrockCachePoint {\n return ttl === '1h' ? { type: 'default', ttl: '1h' } : { type: 'default' };\n}\n\n/**\n * Deep clones a message's content to prevent mutation of the original.\n */\nfunction deepCloneContent<T extends string | MessageContentComplex[]>(\n content: T\n): T {\n if (typeof content === 'string') {\n return content;\n }\n if (Array.isArray(content)) {\n return content.map((block) => ({ ...block })) as T;\n }\n return content;\n}\n\n/**\n * Clones a message with new content. For LangChain BaseMessage instances,\n * constructs a proper class instance so that `instanceof` checks are preserved\n * in downstream code (e.g., ensureThinkingBlockInMessages).\n * For plain objects (AnthropicMessage), uses object spread.\n */\nexport function cloneMessage<T extends MessageWithContent>(\n message: T,\n content: string | MessageContentComplex[]\n): T {\n if (message instanceof BaseMessage) {\n const baseParams = {\n content: toLangChainContent(content),\n additional_kwargs: { ...message.additional_kwargs },\n response_metadata: { ...message.response_metadata },\n id: message.id,\n name: message.name,\n };\n\n const msgType = message.getType();\n switch (msgType) {\n case 'ai':\n return withMessageRole(\n new AIMessage({\n ...baseParams,\n tool_calls: (message as unknown as AIMessage).tool_calls,\n }),\n 'assistant'\n ) as unknown as T;\n case 'human':\n return withMessageRole(\n new HumanMessage(baseParams),\n 'user'\n ) as unknown as T;\n case 'system':\n return withMessageRole(\n new SystemMessage(baseParams),\n 'system'\n ) as unknown as T;\n case 'tool':\n return withMessageRole(\n new ToolMessage({\n ...baseParams,\n tool_call_id: (message as unknown as ToolMessage).tool_call_id,\n }),\n 'tool'\n ) as unknown as T;\n default:\n break;\n }\n }\n\n const {\n lc_kwargs: _lc_kwargs,\n lc_serializable: _lc_serializable,\n lc_namespace: _lc_namespace,\n ...rest\n } = message as T & {\n lc_kwargs?: unknown;\n lc_serializable?: unknown;\n lc_namespace?: unknown;\n };\n\n const cloned = { ...rest, content } as T;\n\n // LangChain messages don't have a direct 'role' property - derive it from getType()\n if (\n 'getType' in message &&\n typeof message.getType === 'function' &&\n !('role' in cloned)\n ) {\n const msgType = (message as unknown as BaseMessage).getType();\n const roleMap: Record<string, string> = {\n human: 'user',\n ai: 'assistant',\n system: 'system',\n tool: 'tool',\n };\n (cloned as Record<string, unknown>).role = roleMap[msgType] || msgType;\n }\n\n return cloned;\n}\n\n/**\n * Sanitize a Bedrock system message: strip Anthropic `cache_control` (Bedrock\n * conversion can't use it) and normalize any existing `cachePoint` to the\n * resolved TTL. The normalization matters because Bedrock requires longer-TTL\n * checkpoints to appear before shorter ones — a stale 5-minute system cachePoint\n * (host-supplied or carried over from a 5m config) left ahead of a 1-hour\n * message tail would make the request invalid. System messages are never\n * anchored as the tail breakpoint; this only fixes markers already present.\n */\nfunction sanitizeBedrockSystemMessage<T extends MessageWithContent>(\n message: T,\n ttl?: PromptCacheTtl\n): T {\n const content = message.content;\n if (!Array.isArray(content)) {\n return message;\n }\n\n const sanitized: MessageContentComplex[] = [];\n let modified = false;\n for (const block of content) {\n if (isCachePoint(block)) {\n const existing = (block as { cachePoint?: { ttl?: unknown } }).cachePoint;\n const desired = buildBedrockCachePoint(ttl);\n if (existing?.ttl !== desired.ttl) {\n modified = true;\n sanitized.push({ cachePoint: desired } as MessageContentComplex);\n } else {\n sanitized.push(block);\n }\n continue;\n }\n if ('cache_control' in block) {\n const cloned: MessageContentWithCacheControl = { ...block };\n delete cloned.cache_control;\n modified = true;\n sanitized.push(cloned);\n continue;\n }\n sanitized.push(block);\n }\n\n if (!modified) {\n return message;\n }\n\n return cloneMessage(message, sanitized);\n}\n\n/**\n * Anthropic API: Adds cache control to the appropriate user messages in the payload.\n * Strips ALL existing cache control (both Anthropic and Bedrock formats) from all messages,\n * then adds fresh cache control to the last 2 user messages in a single backward pass.\n * This ensures we don't accumulate stale cache points across multiple turns.\n * Returns a new array - only clones messages that require modification.\n * @param messages - The array of message objects.\n * @returns - A new array of message objects with cache control added.\n */\nexport function addCacheControl<T extends AnthropicMessage | BaseMessage>(\n messages: T[],\n ttl?: PromptCacheTtl\n): T[] {\n if (!Array.isArray(messages) || messages.length < 2) {\n return messages;\n }\n\n const updatedMessages: T[] = [...messages];\n let userMessagesModified = 0;\n\n for (let i = updatedMessages.length - 1; i >= 0; i--) {\n const originalMessage = updatedMessages[i];\n const content = originalMessage.content;\n const isUserMessage =\n ('getType' in originalMessage && originalMessage.getType() === 'human') ||\n ('role' in originalMessage && originalMessage.role === 'user');\n const hasArrayContent = Array.isArray(content);\n const needsCacheAdd =\n userMessagesModified < 2 &&\n isUserMessage &&\n !isSyntheticMetaMessage(originalMessage) &&\n (typeof content === 'string' || hasArrayContent);\n\n // Skip messages that don't need any work\n if (!needsCacheAdd && !hasArrayContent) {\n continue;\n }\n\n let workingContent: MessageContentComplex[];\n let modified = false;\n\n if (hasArrayContent) {\n // Single pass: clone blocks, strip cache markers and cache points,\n // find last text block index for cache insertion — all at once.\n const src = content as MessageContentComplex[];\n workingContent = [];\n let lastTextIndex = -1;\n for (let j = 0; j < src.length; j++) {\n const block = src[j];\n if (isCachePoint(block)) {\n modified = true;\n continue; // skip cache point blocks\n }\n const cloned = { ...block };\n if ('cache_control' in cloned) {\n delete (cloned as Record<string, unknown>).cache_control;\n modified = true;\n }\n if ('type' in cloned && cloned.type === 'text') {\n lastTextIndex = workingContent.length;\n }\n workingContent.push(cloned as MessageContentComplex);\n }\n\n if (!modified && !needsCacheAdd) {\n continue; // nothing to strip and no cache to add\n }\n\n // Add cache control to the last text block for user messages\n if (needsCacheAdd && lastTextIndex >= 0) {\n (\n workingContent[lastTextIndex] as Anthropic.TextBlockParam\n ).cache_control = buildAnthropicCacheControl(ttl);\n userMessagesModified++;\n }\n } else if (typeof content === 'string' && needsCacheAdd) {\n workingContent = [\n {\n type: 'text',\n text: content,\n cache_control: buildAnthropicCacheControl(ttl),\n },\n ] as unknown as MessageContentComplex[];\n userMessagesModified++;\n } else {\n continue;\n }\n\n updatedMessages[i] = cloneMessage(\n originalMessage as MessageWithContent,\n workingContent\n ) as T;\n }\n\n return updatedMessages;\n}\n\n/**\n * Checks if a content block is a cache point\n */\nfunction isCachePoint(block: MessageContentComplex): boolean {\n return 'cachePoint' in block && !('type' in block);\n}\n\n/**\n * Block types that must never anchor the tail cache breakpoint, because the\n * marker would not survive to the model call:\n * - `thinking` / `redacted_thinking`: native Anthropic reasoning — the API\n * rejects `cache_control` on these blocks.\n * - `reasoning_content` / `reasoning` / `think`: foreign reasoning (Bedrock,\n * Google, LibreChat) that `_convertMessagesToAnthropicPayload` DROPS on\n * assistant turns during a cross-provider handoff.\n * - `input_json_delta`: persisted partial tool-input deltas, also DROPPED by\n * `_convertMessagesToAnthropicPayload` (the assembled input is restored onto\n * the tool_use block).\n * Anchoring the only breakpoint on a block that is about to disappear silently\n * loses tail caching, so all of these are excluded.\n */\nconst NON_ANCHORABLE_BLOCK_TYPES = new Set([\n 'thinking',\n 'redacted_thinking',\n 'reasoning_content',\n 'reasoning',\n 'think',\n 'input_json_delta',\n]);\n\n/**\n * A block can anchor the tail cache breakpoint when it is a real content block\n * that the Anthropic API accepts `cache_control` on and that survives provider\n * conversion. Reasoning / dropped-delta blocks are excluded (see\n * {@link NON_ANCHORABLE_BLOCK_TYPES}), and empty text blocks are not cacheable,\n * so both are skipped.\n */\nfunction isTailCacheableBlock(block: MessageContentComplex): boolean {\n if (isCachePoint(block)) {\n return false;\n }\n const type = (block as { type?: string }).type;\n if (type == null || NON_ANCHORABLE_BLOCK_TYPES.has(type)) {\n return false;\n }\n if (type === 'text') {\n const text = (block as { text?: string }).text;\n return text != null && text.trim() !== '';\n }\n return true;\n}\n\n/**\n * Anthropic API: single tail cache breakpoint (default strategy).\n *\n * Places exactly ONE `cache_control` marker on the last cacheable block of the\n * final non-synthetic message, mirroring the Claude Code strategy\n * (`markerIndex = messages.length - 1`). Because the marker always rides the\n * true tail, the entire conversation prefix is written once and read back on\n * the next turn as the history grows append-only — instead of the rolling\n * \"last two user messages\" markers, which leave freshly appended tool/assistant\n * turns outside the cached prefix and re-write large spans every step.\n *\n * Stale markers (Anthropic `cache_control` and Bedrock cache points) are\n * stripped from every message in a single backward pass so exactly one marker\n * survives. Synthetic skill/meta messages are skipped as anchors (their volatile\n * content must not pin the cache) but still have stale markers removed.\n *\n * Returns a new array; only messages that require modification are cloned.\n */\nexport function addTailCacheControl<T extends AnthropicMessage | BaseMessage>(\n messages: T[],\n ttl?: PromptCacheTtl\n): T[] {\n if (!Array.isArray(messages) || messages.length === 0) {\n return messages;\n }\n\n const updatedMessages: T[] = [...messages];\n let markerPlaced = false;\n\n for (let i = updatedMessages.length - 1; i >= 0; i--) {\n const originalMessage = updatedMessages[i];\n const content = originalMessage.content;\n const hasArrayContent = Array.isArray(content);\n const canPlaceMarker =\n !markerPlaced && !isSyntheticMetaMessage(originalMessage);\n\n // Earlier string-content messages carry no markers to strip.\n if (!canPlaceMarker && !hasArrayContent) {\n continue;\n }\n\n let workingContent: MessageContentComplex[];\n let modified = false;\n\n if (hasArrayContent) {\n const src = content as MessageContentComplex[];\n workingContent = [];\n let tailIndex = -1;\n for (let j = 0; j < src.length; j++) {\n const block = src[j];\n if (isCachePoint(block)) {\n modified = true;\n continue;\n }\n const cloned = { ...block };\n if ('cache_control' in cloned) {\n delete (cloned as Record<string, unknown>).cache_control;\n modified = true;\n }\n if (\n canPlaceMarker &&\n isTailCacheableBlock(cloned as MessageContentComplex)\n ) {\n tailIndex = workingContent.length;\n }\n workingContent.push(cloned as MessageContentComplex);\n }\n\n if (canPlaceMarker && tailIndex >= 0) {\n (workingContent[tailIndex] as Anthropic.TextBlockParam).cache_control =\n buildAnthropicCacheControl(ttl);\n markerPlaced = true;\n modified = true;\n }\n\n if (!modified) {\n continue;\n }\n } else if (\n typeof content === 'string' &&\n canPlaceMarker &&\n content.trim() !== ''\n ) {\n workingContent = [\n {\n type: 'text',\n text: content,\n cache_control: buildAnthropicCacheControl(ttl),\n },\n ] as unknown as MessageContentComplex[];\n markerPlaced = true;\n } else {\n continue;\n }\n\n updatedMessages[i] = cloneMessage(\n originalMessage as MessageWithContent,\n workingContent\n ) as T;\n }\n\n return updatedMessages;\n}\n\nfunction getMessageRole(message: MessageWithContent): string | undefined {\n if (message instanceof BaseMessage) {\n return message.getType();\n }\n if ('role' in message && typeof message.role === 'string') {\n return message.role;\n }\n return undefined;\n}\n\nconst SKILL_MESSAGE_SOURCE = 'skill';\n\n/**\n * Synthetic skill/meta messages (reconstructed skill bodies, primed SKILL.md\n * instructions) are re-injected every turn and are not stable conversation\n * turns. They must not anchor a fresh prompt-cache marker — doing so pins the\n * cache to a volatile/duplicated prefix. Stale markers are still stripped from\n * them; only the *adding* of new markers is suppressed. Detected via\n * `additional_kwargs.isMeta === true` or `additional_kwargs.source === 'skill'`.\n */\nfunction isSyntheticMetaMessage(message: MessageWithContent): boolean {\n const { additional_kwargs: kwargs } = message as {\n additional_kwargs?: { isMeta?: unknown; source?: unknown };\n };\n if (kwargs == null) {\n return false;\n }\n return kwargs.isMeta === true || kwargs.source === SKILL_MESSAGE_SOURCE;\n}\n\nfunction isCacheableConversationMessage(message: MessageWithContent): boolean {\n const role = getMessageRole(message);\n return (\n role === 'human' || role === 'user' || role === 'ai' || role === 'assistant'\n );\n}\n\nfunction isAssistantConversationMessage(message: MessageWithContent): boolean {\n const role = getMessageRole(message);\n return role === 'ai' || role === 'assistant';\n}\n\nfunction hasCacheMarker(message: MessageWithContent): boolean {\n return (\n Array.isArray(message.content) &&\n message.content.some((block) => 'cache_control' in block)\n );\n}\n\nfunction addCacheControlToRecentMessages<\n T extends AnthropicMessage | BaseMessage,\n>(\n messages: T[],\n maxCachePoints: number,\n canUseMessage: (message: MessageWithContent) => boolean,\n ttl?: PromptCacheTtl\n): T[] {\n if (\n !Array.isArray(messages) ||\n messages.length === 0 ||\n maxCachePoints <= 0\n ) {\n return messages;\n }\n\n const updatedMessages: T[] = [...messages];\n let cachePointsAdded = 0;\n\n for (let i = updatedMessages.length - 1; i >= 0; i--) {\n const originalMessage = updatedMessages[i];\n const content = originalMessage.content;\n const hasArrayContent = Array.isArray(content);\n const canAddCache =\n cachePointsAdded < maxCachePoints &&\n canUseMessage(originalMessage) &&\n !isSyntheticMetaMessage(originalMessage);\n\n if (!canAddCache && !hasArrayContent) {\n continue;\n }\n\n let workingContent: MessageContentComplex[];\n let modified = false;\n\n if (hasArrayContent) {\n const src = content as MessageContentComplex[];\n workingContent = [];\n let lastNonEmptyTextIndex = -1;\n\n for (let j = 0; j < src.length; j++) {\n const block = src[j];\n if (isCachePoint(block)) {\n modified = true;\n continue;\n }\n\n const cloned = { ...block };\n if ('cache_control' in cloned) {\n delete (cloned as Record<string, unknown>).cache_control;\n modified = true;\n }\n\n if ('type' in cloned && cloned.type === 'text') {\n const text = (cloned as { text?: string }).text;\n if (text != null && text.trim() !== '') {\n lastNonEmptyTextIndex = workingContent.length;\n }\n }\n workingContent.push(cloned as MessageContentComplex);\n }\n\n if (canAddCache && lastNonEmptyTextIndex >= 0) {\n (\n workingContent[lastNonEmptyTextIndex] as Anthropic.TextBlockParam\n ).cache_control = buildAnthropicCacheControl(ttl);\n cachePointsAdded++;\n modified = true;\n }\n\n if (!modified) {\n continue;\n }\n } else if (\n typeof content === 'string' &&\n content.trim() !== '' &&\n canAddCache\n ) {\n workingContent = [\n {\n type: 'text',\n text: content,\n cache_control: buildAnthropicCacheControl(ttl),\n },\n ] as unknown as MessageContentComplex[];\n cachePointsAdded++;\n } else {\n continue;\n }\n\n updatedMessages[i] = cloneMessage(\n originalMessage as MessageWithContent,\n workingContent\n ) as T;\n }\n\n return updatedMessages;\n}\n\nexport function addCacheControlToStablePrefixMessages<\n T extends AnthropicMessage | BaseMessage,\n>(messages: T[], maxCachePoints: number, ttl?: PromptCacheTtl): T[] {\n const assistantMarked = addCacheControlToRecentMessages(\n messages,\n maxCachePoints,\n isAssistantConversationMessage,\n ttl\n );\n\n if (assistantMarked.some(hasCacheMarker)) {\n return assistantMarked;\n }\n\n return addCacheControlToRecentMessages(\n messages,\n maxCachePoints,\n isCacheableConversationMessage,\n ttl\n );\n}\n\n/**\n * Checks if a message's content has Anthropic cache_control fields.\n */\nfunction hasAnthropicCacheControl(content: MessageContentComplex[]): boolean {\n for (let i = 0; i < content.length; i++) {\n if ('cache_control' in content[i]) return true;\n }\n return false;\n}\n\n/**\n * Removes all Anthropic cache_control fields from messages\n * Used when switching from Anthropic to Bedrock provider\n * Returns a new array - only clones messages that require modification.\n */\nexport function stripAnthropicCacheControl<T extends MessageWithContent>(\n messages: T[]\n): T[] {\n if (!Array.isArray(messages)) {\n return messages;\n }\n\n const updatedMessages: T[] = [...messages];\n\n for (let i = 0; i < updatedMessages.length; i++) {\n const originalMessage = updatedMessages[i];\n const content = originalMessage.content;\n\n if (!Array.isArray(content) || !hasAnthropicCacheControl(content)) {\n continue;\n }\n\n const clonedContent = deepCloneContent(content);\n for (let j = 0; j < clonedContent.length; j++) {\n const block = clonedContent[j] as Record<string, unknown>;\n if ('cache_control' in block) {\n delete block.cache_control;\n }\n }\n updatedMessages[i] = cloneMessage(originalMessage, clonedContent);\n }\n\n return updatedMessages;\n}\n\n/**\n * Checks if a message's content has Bedrock cachePoint blocks.\n */\nfunction hasBedrockCachePoint(content: MessageContentComplex[]): boolean {\n for (let i = 0; i < content.length; i++) {\n if (isCachePoint(content[i])) return true;\n }\n return false;\n}\n\n/**\n * Removes all Bedrock cachePoint blocks from messages\n * Used when switching from Bedrock to Anthropic provider\n * Returns a new array - only clones messages that require modification.\n */\nexport function stripBedrockCacheControl<T extends MessageWithContent>(\n messages: T[]\n): T[] {\n if (!Array.isArray(messages)) {\n return messages;\n }\n\n const updatedMessages: T[] = [...messages];\n\n for (let i = 0; i < updatedMessages.length; i++) {\n const originalMessage = updatedMessages[i];\n const content = originalMessage.content;\n\n if (!Array.isArray(content) || !hasBedrockCachePoint(content)) {\n continue;\n }\n\n const clonedContent = deepCloneContent(content).filter(\n (block) => !isCachePoint(block as MessageContentComplex)\n );\n updatedMessages[i] = cloneMessage(originalMessage, clonedContent);\n }\n\n return updatedMessages;\n}\n\n/**\n * Adds Bedrock Converse API cache points to the latest two user messages.\n * Inserts `{ cachePoint: { type: 'default' } }` as a separate content block\n * immediately after the last text block in each targeted message.\n * Strips ALL existing cache control (both Bedrock and Anthropic formats) from all messages,\n * then adds fresh cache points to the latest two non-tool user messages in a single backward pass.\n * This ensures we don't accumulate stale cache points across multiple turns.\n * Returns a new array - only clones messages that require modification.\n * @param messages - The array of message objects.\n * @returns - A new array of message objects with cache points added.\n */\nexport function addBedrockCacheControl<\n T extends MessageWithContent & { getType?: () => string; role?: string },\n>(messages: T[], ttl?: PromptCacheTtl): T[] {\n if (!Array.isArray(messages) || messages.length === 0) {\n return messages;\n }\n\n const updatedMessages: T[] = [...messages];\n let cachePointsAdded = 0;\n\n for (let i = updatedMessages.length - 1; i >= 0; i--) {\n const originalMessage = updatedMessages[i];\n const messageType =\n 'getType' in originalMessage &&\n typeof originalMessage.getType === 'function'\n ? originalMessage.getType()\n : undefined;\n const messageRole =\n 'role' in originalMessage && typeof originalMessage.role === 'string'\n ? originalMessage.role\n : undefined;\n\n const isSystemMessage =\n messageType === 'system' || messageRole === 'system';\n if (isSystemMessage) {\n updatedMessages[i] = sanitizeBedrockSystemMessage(originalMessage, ttl);\n continue;\n }\n\n const isToolMessage = messageType === 'tool' || messageRole === 'tool';\n const isUserMessage = messageType === 'human' || messageRole === 'user';\n const content = originalMessage.content;\n const hasSerializationProps =\n 'lc_kwargs' in originalMessage ||\n 'lc_serializable' in originalMessage ||\n 'lc_namespace' in originalMessage;\n const hasArrayContent = Array.isArray(content);\n const isEmptyString = typeof content === 'string' && content === '';\n const needsCacheAdd =\n cachePointsAdded < 2 &&\n isUserMessage &&\n !isToolMessage &&\n !isEmptyString &&\n !isSyntheticMetaMessage(originalMessage) &&\n (typeof content === 'string' || hasArrayContent);\n\n if (!needsCacheAdd && !hasArrayContent && !hasSerializationProps) {\n continue;\n }\n\n let workingContent: string | MessageContentComplex[];\n let modified = hasSerializationProps;\n\n if (hasArrayContent) {\n // Single pass: clone blocks, strip cache markers, find last\n // non-empty text block for cache point insertion — all at once.\n const src = content as MessageContentComplex[];\n workingContent = [];\n let lastNonEmptyTextIndex = -1;\n for (let j = 0; j < src.length; j++) {\n const block = src[j];\n if (isCachePoint(block)) {\n modified = true;\n continue;\n }\n const cloned = { ...block };\n if ('cache_control' in cloned) {\n delete (cloned as Record<string, unknown>).cache_control;\n modified = true;\n }\n const type = (cloned as { type?: string }).type;\n if (type === ContentTypes.TEXT || type === 'text') {\n const text = (cloned as { text?: string }).text;\n if (text != null && text.trim() !== '') {\n lastNonEmptyTextIndex = workingContent.length;\n }\n }\n workingContent.push(cloned as MessageContentComplex);\n }\n\n if (!modified && !needsCacheAdd) {\n continue;\n }\n\n // Insert cache point after the last non-empty text block.\n // Skip if no cacheable text content exists (whitespace-only messages).\n if (needsCacheAdd && lastNonEmptyTextIndex >= 0) {\n workingContent.splice(lastNonEmptyTextIndex + 1, 0, {\n cachePoint: buildBedrockCachePoint(ttl),\n } as MessageContentComplex);\n cachePointsAdded++;\n }\n } else if (typeof content === 'string' && needsCacheAdd) {\n workingContent = [\n { type: ContentTypes.TEXT, text: content },\n { cachePoint: buildBedrockCachePoint(ttl) } as MessageContentComplex,\n ];\n cachePointsAdded++;\n } else if (typeof content === 'string' && hasSerializationProps) {\n workingContent = content;\n } else {\n continue;\n }\n\n updatedMessages[i] = cloneMessage(originalMessage, workingContent);\n }\n\n return updatedMessages;\n}\n\n/**\n * Bedrock Converse API: single tail cache breakpoint (default strategy).\n *\n * The Bedrock counterpart of {@link addTailCacheControl}. Strips ALL existing\n * cache control (Bedrock cache points and Anthropic `cache_control`) from every\n * message, then inserts exactly ONE `{ cachePoint: { type: 'default' } }` block\n * immediately after the last non-empty text block of the most recent\n * non-synthetic, non-system message. Anchoring on the rolling tail keeps the\n * cached prefix append-only as the conversation grows, instead of re-writing\n * large spans every turn with the legacy \"last two user messages\" cache points.\n *\n * System messages are sanitized (Anthropic `cache_control` stripped) but never\n * anchored. Synthetic skill/meta messages are skipped as anchors so their\n * volatile content cannot pin the cache.\n *\n * Returns a new array - only clones messages that require modification.\n */\nexport function addBedrockTailCacheControl<\n T extends MessageWithContent & { getType?: () => string; role?: string },\n>(messages: T[], ttl?: PromptCacheTtl): T[] {\n if (!Array.isArray(messages) || messages.length === 0) {\n return messages;\n }\n\n const updatedMessages: T[] = [...messages];\n let cachePointPlaced = false;\n\n for (let i = updatedMessages.length - 1; i >= 0; i--) {\n const originalMessage = updatedMessages[i];\n const messageType =\n 'getType' in originalMessage &&\n typeof originalMessage.getType === 'function'\n ? originalMessage.getType()\n : undefined;\n const messageRole =\n 'role' in originalMessage && typeof originalMessage.role === 'string'\n ? originalMessage.role\n : undefined;\n\n const isSystemMessage =\n messageType === 'system' || messageRole === 'system';\n if (isSystemMessage) {\n updatedMessages[i] = sanitizeBedrockSystemMessage(originalMessage, ttl);\n continue;\n }\n\n const content = originalMessage.content;\n const hasSerializationProps =\n 'lc_kwargs' in originalMessage ||\n 'lc_serializable' in originalMessage ||\n 'lc_namespace' in originalMessage;\n const hasArrayContent = Array.isArray(content);\n const isEmptyString = typeof content === 'string' && content === '';\n const canPlaceCachePoint =\n !cachePointPlaced &&\n !isEmptyString &&\n !isSyntheticMetaMessage(originalMessage) &&\n (typeof content === 'string' || hasArrayContent);\n\n if (!canPlaceCachePoint && !hasArrayContent && !hasSerializationProps) {\n continue;\n }\n\n let workingContent: string | MessageContentComplex[];\n let modified = hasSerializationProps;\n\n if (hasArrayContent) {\n const src = content as MessageContentComplex[];\n workingContent = [];\n let lastNonEmptyTextIndex = -1;\n for (let j = 0; j < src.length; j++) {\n const block = src[j];\n if (isCachePoint(block)) {\n modified = true;\n continue;\n }\n const cloned = { ...block };\n if ('cache_control' in cloned) {\n delete (cloned as Record<string, unknown>).cache_control;\n modified = true;\n }\n const type = (cloned as { type?: string }).type;\n if (type === ContentTypes.TEXT || type === 'text') {\n const text = (cloned as { text?: string }).text;\n if (text != null && text.trim() !== '') {\n lastNonEmptyTextIndex = workingContent.length;\n }\n }\n workingContent.push(cloned as MessageContentComplex);\n }\n\n if (!modified && !canPlaceCachePoint) {\n continue;\n }\n\n if (canPlaceCachePoint && lastNonEmptyTextIndex >= 0) {\n workingContent.splice(lastNonEmptyTextIndex + 1, 0, {\n cachePoint: buildBedrockCachePoint(ttl),\n } as MessageContentComplex);\n cachePointPlaced = true;\n modified = true;\n }\n } else if (typeof content === 'string' && canPlaceCachePoint) {\n workingContent = [\n { type: ContentTypes.TEXT, text: content },\n { cachePoint: buildBedrockCachePoint(ttl) } as MessageContentComplex,\n ];\n cachePointPlaced = true;\n } else if (typeof content === 'string' && hasSerializationProps) {\n workingContent = content;\n } else {\n continue;\n }\n\n updatedMessages[i] = cloneMessage(originalMessage, workingContent);\n }\n\n return updatedMessages;\n}\n"],"mappings":"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"}
@@ -330,6 +330,7 @@ async function executeSummarizationWithFallback(params) {
330
330
  provider: clientConfig.provider,
331
331
  reasoningKey: agentContext.reasoningKey,
332
332
  usePromptCache,
333
+ promptCacheTtl: clientConfig.provider === "anthropic" || clientConfig.provider === "openrouter" ? require_cache.resolvePromptCacheTtl(clientConfig.clientOptions.promptCacheTtl) : void 0,
333
334
  log
334
335
  });
335
336
  summaryText = result.text;
@@ -710,12 +711,12 @@ function traceConfig(config, stage) {
710
711
  * Providers with prompt caching get a cache hit on the system prompt +
711
712
  * tool definitions prefix.
712
713
  */
713
- async function summarizeWithCacheHit({ model, messages, promptText, updatePromptText, priorSummaryText, config, stepId, provider, reasoningKey, usePromptCache, log }) {
714
+ async function summarizeWithCacheHit({ model, messages, promptText, updatePromptText, priorSummaryText, config, stepId, provider, reasoningKey, usePromptCache, promptCacheTtl, log }) {
714
715
  const instruction = buildSummarizationInstruction(promptText, updatePromptText, priorSummaryText);
715
716
  const fullMessages = [...messages, new _langchain_core_messages.HumanMessage(instruction)];
716
717
  const responseMsg = (await require_invoke.attemptInvoke({
717
718
  model,
718
- messages: usePromptCache === true ? require_cache.addTailCacheControl(fullMessages) : fullMessages,
719
+ messages: usePromptCache === true ? require_cache.addTailCacheControl(fullMessages, promptCacheTtl) : fullMessages,
719
720
  provider,
720
721
  onChunk: createSummarizationChunkHandler({
721
722
  stepId,
@@ -1 +1 @@
1
- {"version":3,"file":"node.cjs","names":["AIMessage","ToolMessage","getMaxOutputTokensKey","SystemMessage","initializeModel","tryFallbackProviders","HumanMessage","safeDispatchCustomEvent","executeHooks","splitAtRecencyBoundary","createRemoveAllMessage","getChunkContent","chunkAny","attemptInvoke","addTailCacheControl"],"sources":["../../../src/summarization/node.ts"],"sourcesContent":["import {\n AIMessage,\n ToolMessage,\n HumanMessage,\n SystemMessage,\n} from '@langchain/core/messages';\nimport type { UsageMetadata, BaseMessage } from '@langchain/core/messages';\nimport type { RunnableConfig } from '@langchain/core/runnables';\nimport type { AgentContext } from '@/agents/AgentContext';\nimport type { HookRegistry } from '@/hooks';\nimport type { OnChunk } from '@/llm/invoke';\nimport type * as t from '@/types';\nimport {\n Constants,\n ContentTypes,\n GraphEvents,\n StepTypes,\n Providers,\n} from '@/common';\nimport { safeDispatchCustomEvent, emitAgentLog } from '@/utils/events';\nimport { attemptInvoke, tryFallbackProviders } from '@/llm/invoke';\nimport { createRemoveAllMessage } from '@/messages/reducer';\nimport { splitAtRecencyBoundary } from '@/messages/recency';\nimport { getMaxOutputTokensKey } from '@/llm/request';\nimport { addTailCacheControl } from '@/messages/cache';\nimport { initializeModel } from '@/llm/init';\nimport { getChunkContent } from '@/stream';\nimport { executeHooks } from '@/hooks';\n\nconst SUMMARIZATION_PARAM_KEYS = new Set(['maxSummaryTokens']);\n\n/**\n * Default number of recent user-led turns preserved verbatim during\n * compaction. A turn begins at a HumanMessage and includes every\n * following AIMessage and ToolMessage up to the next HumanMessage.\n * The most recent turn is always retained regardless of this value;\n * the default of `2` additionally keeps the prior exchange so the\n * model has fresh context on what just happened. Setting\n * `retainRecent.turns` to `0` reverts to the legacy \"summarize every\n * message\" behavior.\n */\nconst DEFAULT_RETAIN_RECENT_TURNS = 2;\n\n/**\n * Token overhead of the XML wrapper + instruction text added around the\n * summary at injection time in AgentContext.buildSystemRunnable:\n * `<summary>\\n${text}\\n</summary>\\n\\nYour context window was compacted...`\n * ~33 tokens on Anthropic, ~24-27 on OpenAI. Using 33 as a safe ceiling.\n */\nconst SUMMARY_WRAPPER_OVERHEAD_TOKENS = 33;\n\n/** Structured checkpoint prompt for fresh summarization (no prior summary). */\nexport const DEFAULT_SUMMARIZATION_PROMPT = `Hold on, before you continue I need you to write me a checkpoint of everything so far. Your context window is filling up and this checkpoint replaces the messages above, so capture everything you need to pick right back up.\n\nDon't second-guess or fact-check anything you did, your tool results reflect exactly what happened. If a tool result appears truncated, that's just a display artifact from context management: the tool executed fully. Just record what you did and what you observed. Only the checkpoint, don't respond to me or continue the conversation.\n\n## Checkpoint\n\n## Goal\nWhat I asked you to do and any sub-goals you identified.\n\n## Constraints & Preferences\nAny rules, preferences, or configuration I established.\n\n## Progress\n### Done\n- What you completed and the outcomes\n\n### In Progress\n- What you're currently working on\n\n## Key Decisions\nDecisions you made and why.\n\n## Next Steps\nConcrete task actions remaining, in priority order.\n\n## Critical Context\nExact identifiers, names, error messages, URLs, and details you need to preserve verbatim.\n\nRules:\n- Record what you did and observed, don't judge or re-evaluate it\n- For each tool call: the tool name, key inputs, and the outcome\n- Preserve exact identifiers, names, errors, and references verbatim\n- Short declarative sentences\n- Skip empty sections`;\n\n/** Prompt for re-compaction when a prior summary exists. */\nexport const DEFAULT_UPDATE_SUMMARIZATION_PROMPT = `Hold on again, update your checkpoint. Merge the new messages into your existing checkpoint and give me a single consolidated replacement.\n\nKeep it roughly the same length as your last checkpoint. Compress older details to make room for what's new, don't just append. Give recent actions more detail, compress older items to one-liners.\n\nDon't fact-check or second-guess anything, your tool results are ground truth. If a tool result appears truncated, that's just a display artifact: the tool executed fully. Only the checkpoint, don't respond to me or continue the conversation.\n\nRules:\n- Merge new progress into existing sections, don't duplicate headers\n- Compress older completed items into one-line entries\n- Move items from \"In Progress\" to \"Done\" when you completed them\n- Update \"Next Steps\" to reflect current task priorities.\n- For each new tool call: the tool name, key inputs, and the outcome\n- Preserve exact identifiers, names, errors, and references verbatim\n- Skip empty sections`;\n\nfunction separateParameters(parameters: Record<string, unknown>): {\n llmParams: Record<string, unknown>;\n maxSummaryTokens?: number;\n} {\n const llmParams: Record<string, unknown> = {};\n let maxSummaryTokens: number | undefined;\n\n for (const [key, value] of Object.entries(parameters)) {\n if (SUMMARIZATION_PARAM_KEYS.has(key)) {\n if (\n key === 'maxSummaryTokens' &&\n typeof value === 'number' &&\n value > 0\n ) {\n maxSummaryTokens = value;\n }\n } else {\n llmParams[key] = value;\n }\n }\n\n return { llmParams, maxSummaryTokens };\n}\n\n/**\n * Generates a structural metadata summary without making an LLM call.\n * Used as a last-resort fallback when all summarization attempts fail.\n * Preserves tool names and message counts so the agent retains basic context.\n */\nfunction generateMetadataStub(messages: BaseMessage[]): string {\n const counts: Record<string, number> = {};\n const toolNames = new Set<string>();\n\n for (const msg of messages) {\n const role = msg.getType();\n counts[role] = (counts[role] ?? 0) + 1;\n\n if (role === 'tool' && msg.name != null && msg.name !== '') {\n toolNames.add(msg.name);\n }\n\n if (\n role === 'ai' &&\n msg instanceof AIMessage &&\n msg.tool_calls &&\n msg.tool_calls.length > 0\n ) {\n for (const tc of msg.tool_calls) {\n toolNames.add(tc.name);\n }\n }\n }\n\n const countParts = Object.entries(counts)\n .map(([role, count]) => `${count} ${role}`)\n .join(', ');\n\n const lines = [\n `[Metadata summary: ${messages.length} messages (${countParts})]`,\n ];\n\n if (toolNames.size > 0) {\n lines.push(`[Tools used: ${Array.from(toolNames).join(', ')}]`);\n }\n\n return lines.join('\\n');\n}\n\n/** Maximum number of tool failures to include in the enrichment section. */\nconst MAX_TOOL_FAILURES = 8;\n/** Maximum chars per failure summary line. */\nconst MAX_TOOL_FAILURE_CHARS = 240;\n\n/**\n * Extracts failed tool results from messages and formats them as a structured\n * section. LLMs often omit specific failure details (exit codes, error messages)\n * from their summaries, this mechanical enrichment guarantees they survive.\n */\nfunction extractToolFailuresSection(messages: BaseMessage[]): string {\n const failures: Array<{ toolName: string; summary: string }> = [];\n const seen = new Set<string>();\n\n for (const msg of messages) {\n if (msg.getType() !== 'tool') {\n continue;\n }\n const toolMsg = msg as ToolMessage;\n if (toolMsg.status !== 'error') {\n continue;\n }\n // Deduplicate by tool_call_id\n const callId = toolMsg.tool_call_id;\n if (callId && seen.has(callId)) {\n continue;\n }\n if (callId) {\n seen.add(callId);\n }\n\n const toolName = toolMsg.name ?? 'tool';\n const content =\n typeof toolMsg.content === 'string'\n ? toolMsg.content\n : JSON.stringify(toolMsg.content);\n const normalized = content.replace(/\\s+/g, ' ').trim();\n const summary =\n normalized.length > MAX_TOOL_FAILURE_CHARS\n ? `${normalized.slice(0, MAX_TOOL_FAILURE_CHARS - 3)}...`\n : normalized;\n\n failures.push({ toolName, summary });\n }\n\n if (failures.length === 0) {\n return '';\n }\n\n const lines = failures\n .slice(0, MAX_TOOL_FAILURES)\n .map((f) => `- ${f.toolName}: ${f.summary}`);\n if (failures.length > MAX_TOOL_FAILURES) {\n lines.push(`- ...and ${failures.length - MAX_TOOL_FAILURES} more`);\n }\n\n return `\\n\\n## Tool Failures\\n${lines.join('\\n')}`;\n}\n\n/**\n * Appends mechanical enrichment sections to an LLM-generated summary.\n * Tool failures are appended verbatim because LLMs often omit specific\n * error details from their summaries.\n */\nfunction enrichSummary(summaryText: string, messages: BaseMessage[]): string {\n return summaryText + extractToolFailuresSection(messages);\n}\n\n/**\n * Restores pre-masking tool content onto the messages array using\n * `pendingOriginalToolContent` stored on AgentContext. Only allocates\n * a new array when there are entries to restore; otherwise returns the\n * input reference unchanged.\n */\nfunction restoreOriginalToolContent(\n messages: BaseMessage[],\n originalToolContent: Map<number, string> | undefined\n): BaseMessage[] {\n if (originalToolContent == null || originalToolContent.size === 0) {\n return messages;\n }\n const restored = [...messages];\n for (const [idx, content] of originalToolContent) {\n const msg = restored[idx];\n if (msg instanceof ToolMessage) {\n restored[idx] = new ToolMessage({\n content,\n tool_call_id: msg.tool_call_id,\n name: msg.name,\n id: msg.id,\n additional_kwargs: msg.additional_kwargs,\n response_metadata: msg.response_metadata,\n });\n }\n }\n return restored;\n}\n\n// ---------------------------------------------------------------------------\n// Extracted helpers for createSummarizeNode\n// ---------------------------------------------------------------------------\n\ninterface SummarizationClientConfig {\n provider: string;\n modelName?: string;\n clientOptions: Record<string, unknown>;\n effectiveMaxSummaryTokens?: number;\n promptText: string;\n updatePromptText: string;\n}\n\n/** Assembles the summarization model's client options from agent and config. */\nfunction buildSummarizationClientConfig(\n agentContext: AgentContext,\n summarizationConfig?: t.SummarizationConfig\n): SummarizationClientConfig {\n const provider = (summarizationConfig?.provider ??\n agentContext.provider) as string;\n const modelName = summarizationConfig?.model;\n const parameters = summarizationConfig?.parameters ?? {};\n const promptText =\n summarizationConfig?.prompt ?? DEFAULT_SUMMARIZATION_PROMPT;\n const updatePromptText =\n summarizationConfig?.updatePrompt ?? DEFAULT_UPDATE_SUMMARIZATION_PROMPT;\n\n const { llmParams, maxSummaryTokens: paramMaxSummaryTokens } =\n separateParameters(parameters);\n\n const isSelfSummarize = provider === (agentContext.provider as string);\n const baseOptions =\n isSelfSummarize && agentContext.clientOptions\n ? { ...agentContext.clientOptions }\n : {};\n\n const clientOptions: Record<string, unknown> = {\n ...baseOptions,\n ...llmParams,\n };\n\n if (modelName != null && modelName !== '') {\n clientOptions.model = modelName;\n clientOptions.modelName = modelName;\n }\n\n const effectiveMaxSummaryTokens =\n paramMaxSummaryTokens ?? summarizationConfig?.maxSummaryTokens;\n\n if (effectiveMaxSummaryTokens != null) {\n clientOptions[getMaxOutputTokensKey(provider)] = effectiveMaxSummaryTokens;\n }\n\n return {\n provider,\n modelName,\n clientOptions,\n effectiveMaxSummaryTokens,\n promptText,\n updatePromptText,\n };\n}\n\n/** Computes the token count for a summary, preferring provider output tokens when available. */\nfunction computeSummaryTokenCount(\n summaryText: string,\n summaryUsage: Partial<UsageMetadata> | undefined,\n tokenCounter?: (message: BaseMessage) => number\n): number {\n const providerOutputTokens = Number(summaryUsage?.output_tokens) || 0;\n if (providerOutputTokens > 0) {\n return providerOutputTokens + SUMMARY_WRAPPER_OVERHEAD_TOKENS;\n }\n if (tokenCounter) {\n return (\n tokenCounter(new SystemMessage(summaryText)) +\n SUMMARY_WRAPPER_OVERHEAD_TOKENS\n );\n }\n return 0;\n}\n\n/** Constructs the SummaryContentBlock persisted in the run step and dispatched to events. */\nfunction buildSummaryBlock(params: {\n summaryText: string;\n tokenCount: number;\n stepId: string;\n stepIndex: number;\n modelName?: string;\n provider: string;\n summaryVersion: number;\n}): t.SummaryContentBlock {\n return {\n type: ContentTypes.SUMMARY,\n content: [\n {\n type: ContentTypes.TEXT,\n text: params.summaryText,\n } as t.MessageContentComplex,\n ],\n tokenCount: params.tokenCount,\n summaryVersion: params.summaryVersion,\n boundary: {\n messageId: params.stepId,\n contentIndex: params.stepIndex,\n },\n model: params.modelName,\n provider: params.provider,\n createdAt: new Date().toISOString(),\n };\n}\n\ntype LogFn = (\n level: 'debug' | 'info' | 'warn' | 'error',\n message: string,\n data?: Record<string, unknown>\n) => void;\n\n/**\n * Extracts an HTTP status code from a thrown LLM-provider error. Returns\n * `undefined` for non-object values (including `null` or `undefined`, both\n * valid `throw` targets in JS) so callers never dereference a nullish\n * value.\n */\nfunction extractHttpStatus(err: unknown): number | undefined {\n if (err == null || typeof err !== 'object') {\n return undefined;\n }\n const errRecord = err as Record<string, unknown>;\n const direct = errRecord.status;\n if (typeof direct === 'number') {\n return direct;\n }\n const statusCode = errRecord.statusCode;\n if (typeof statusCode === 'number') {\n return statusCode;\n }\n const response = errRecord.response;\n if (response != null && typeof response === 'object') {\n const nested = (response as Record<string, unknown>).status;\n if (typeof nested === 'number') {\n return nested;\n }\n }\n return undefined;\n}\n\n/**\n * Formats a provider-level error for logging. Returns both a human-readable\n * suffix (safe to include in the message string so it survives any host-side\n * formatter) and a structured metadata bag for rich log backends.\n */\nfunction describeProviderError(\n err: unknown,\n provider: string,\n modelName?: string\n): { suffix: string; data: Record<string, unknown> } {\n const providerLabel = `${provider}/${modelName ?? '(no-model)'}`;\n const errMsg = err instanceof Error ? err.message : String(err);\n\n const data: Record<string, unknown> = {\n provider,\n model: modelName,\n };\n if (err instanceof Error) {\n data.errorName = err.name;\n data.errorStack = err.stack;\n }\n\n const status = extractHttpStatus(err);\n const statusSuffix = status != null ? ` (HTTP ${status})` : '';\n if (status != null) {\n data.status = status;\n }\n\n return {\n suffix: `[${providerLabel}]${statusSuffix}: ${errMsg}`,\n data,\n };\n}\n\n/**\n * Formats an exhausted-fallback error. `tryFallbackProviders` throws the\n * last fallback provider's error, which may be from any of the configured\n * fallbacks — not the primary — so we label the log with the list of\n * fallback providers attempted rather than mis-attributing to the primary.\n *\n * Entries in `fallbacks` are normally strongly typed, but we defend against\n * malformed runtime config (null/undefined entries, missing `provider`\n * field) so a recoverable summarization failure is never promoted to an\n * uncaught exception from inside the logging path.\n */\nfunction describeFallbackError(\n err: unknown,\n fallbacks: unknown\n): { suffix: string; data: Record<string, unknown> } {\n const errMsg = err instanceof Error ? err.message : String(err);\n const list: ReadonlyArray<unknown> = Array.isArray(fallbacks)\n ? fallbacks\n : [];\n const providerNames = list\n .map((f) => {\n if (f == null || typeof f !== 'object') {\n return undefined;\n }\n const raw = (f as { provider?: unknown }).provider;\n return raw != null ? String(raw) : undefined;\n })\n .filter((p): p is string => typeof p === 'string');\n const label =\n providerNames.length > 0\n ? `fallbacks=[${providerNames.join(',')}]`\n : 'no-fallbacks';\n\n const data: Record<string, unknown> = {\n fallbackProviders: providerNames,\n fallbackCount: list.length,\n };\n if (err instanceof Error) {\n data.errorName = err.name;\n data.errorStack = err.stack;\n }\n const status = extractHttpStatus(err);\n const statusSuffix = status != null ? ` (HTTP ${status})` : '';\n if (status != null) {\n data.status = status;\n }\n\n return {\n suffix: `[${label}]${statusSuffix}: ${errMsg}`,\n data,\n };\n}\n\n/**\n * Runs the summarization LLM call with primary + fallback providers,\n * falling back to a metadata stub when all calls fail.\n */\nasync function executeSummarizationWithFallback(params: {\n agentContext: AgentContext;\n messages: BaseMessage[];\n clientConfig: SummarizationClientConfig;\n summarizeConfig?: RunnableConfig;\n stepId: string;\n usePromptCache: boolean;\n log: LogFn;\n}): Promise<{ text: string; usage?: Partial<UsageMetadata> }> {\n const {\n agentContext,\n messages,\n clientConfig,\n summarizeConfig,\n stepId,\n usePromptCache,\n log,\n } = params;\n\n const priorSummaryText = agentContext.getSummaryText()?.trim() ?? '';\n\n let summaryText = '';\n let summaryUsage: Partial<UsageMetadata> | undefined;\n\n try {\n /**\n * Initialize inside the try so that a misconfigured provider\n * (e.g. an unrecognized summarization.provider) surfaces through the\n * `log('error', ...)` path below rather than bubbling up silently.\n */\n const summarizationModel = initializeModel({\n provider: clientConfig.provider as Providers,\n clientOptions: clientConfig.clientOptions as t.ClientOptions,\n tools: agentContext.getToolsForBinding(),\n }) as t.ChatModel;\n\n const result = await summarizeWithCacheHit({\n model: summarizationModel,\n messages,\n promptText: clientConfig.promptText,\n updatePromptText: clientConfig.updatePromptText,\n priorSummaryText,\n config: summarizeConfig,\n stepId,\n provider: clientConfig.provider as Providers,\n reasoningKey: agentContext.reasoningKey,\n usePromptCache,\n log,\n });\n summaryText = result.text;\n summaryUsage = result.usage;\n } catch (primaryError) {\n const primaryDescribed = describeProviderError(\n primaryError,\n clientConfig.provider,\n clientConfig.modelName\n );\n log('error', `Summarization LLM call failed ${primaryDescribed.suffix}`, {\n ...primaryDescribed.data,\n messagesToRefineCount: messages.length,\n });\n\n const rawFallbacks = (\n clientConfig.clientOptions as unknown as t.LLMConfig | undefined\n )?.fallbacks;\n const fallbacks = Array.isArray(rawFallbacks) ? rawFallbacks : [];\n if (fallbacks.length > 0) {\n try {\n const onChunk = createSummarizationChunkHandler({\n stepId,\n config: traceConfig(summarizeConfig, 'cache_hit_compaction'),\n provider: clientConfig.provider as Providers,\n reasoningKey: agentContext.reasoningKey,\n });\n const fbResult = await tryFallbackProviders({\n fallbacks,\n tools: agentContext.getToolsForBinding(),\n messages: [\n ...messages,\n new HumanMessage(\n buildSummarizationInstruction(\n clientConfig.promptText,\n clientConfig.updatePromptText,\n priorSummaryText\n )\n ),\n ],\n config: traceConfig(summarizeConfig, 'cache_hit_compaction'),\n primaryError,\n onChunk,\n });\n const fbMsg = fbResult?.messages?.[0];\n if (fbMsg) {\n summaryText = extractResponseText(\n fbMsg as { content: string | object }\n );\n }\n } catch (fbErr) {\n const fbDescribed = describeFallbackError(fbErr, fallbacks);\n log('warn', `Fallback providers also failed ${fbDescribed.suffix}`, {\n ...fbDescribed.data,\n });\n }\n }\n if (!summaryText) {\n log(\n 'warn',\n `Summarization failed, falling back to metadata stub ${primaryDescribed.suffix}`,\n {\n ...primaryDescribed.data,\n messagesToRefineCount: messages.length,\n }\n );\n summaryText = generateMetadataStub(messages);\n }\n }\n\n return { text: summaryText, usage: summaryUsage };\n}\n\n/** Dispatches run step completion, ON_SUMMARIZE_COMPLETE, and rebuilds token map. */\nasync function dispatchCompletionEvents(params: {\n graph: CreateSummarizeNodeParams['graph'];\n runnableConfig?: RunnableConfig;\n stepId: string;\n summaryBlock: t.SummaryContentBlock;\n agentContext: AgentContext;\n runStep: t.RunStep;\n summaryUsage?: Partial<UsageMetadata>;\n agentId: string;\n /**\n * Number of messages preserved verbatim by the recency window after\n * compaction. Reported via the PostCompact hook payload so observers\n * (metrics, cleanup) see the true post-compaction message count\n * instead of always-zero.\n */\n messagesAfterCount: number;\n}): Promise<void> {\n const {\n graph,\n runnableConfig,\n stepId,\n summaryBlock,\n agentContext,\n runStep,\n summaryUsage,\n agentId,\n messagesAfterCount,\n } = params;\n\n runStep.summary = summaryBlock;\n if (summaryUsage) {\n runStep.usage = {\n prompt_tokens: Number(summaryUsage.input_tokens) || 0,\n completion_tokens: Number(summaryUsage.output_tokens) || 0,\n total_tokens:\n (Number(summaryUsage.input_tokens) || 0) +\n (Number(summaryUsage.output_tokens) || 0),\n };\n }\n\n await graph.dispatchRunStepCompleted(\n stepId,\n { type: 'summary', summary: summaryBlock } satisfies t.SummaryCompleted,\n runnableConfig\n );\n\n if (runnableConfig) {\n await safeDispatchCustomEvent(\n GraphEvents.ON_SUMMARIZE_COMPLETE,\n {\n id: stepId,\n agentId,\n summary: summaryBlock,\n } satisfies t.SummarizeCompleteEvent,\n runnableConfig\n );\n }\n\n const sessionId = graph.runId ?? '';\n if (graph.hookRegistry?.hasHookFor('PostCompact', sessionId) === true) {\n const threadId = (\n runnableConfig?.configurable as Record<string, unknown> | undefined\n )?.thread_id as string | undefined;\n const firstBlock = summaryBlock.content?.[0];\n const summaryText =\n firstBlock != null &&\n typeof firstBlock === 'object' &&\n 'text' in firstBlock &&\n typeof firstBlock.text === 'string'\n ? firstBlock.text\n : '';\n await executeHooks({\n registry: graph.hookRegistry,\n input: {\n hook_event_name: 'PostCompact',\n runId: sessionId,\n threadId,\n agentId,\n summary: summaryText,\n messagesAfterCount,\n },\n sessionId,\n }).catch(() => {\n /* PostCompact is observational — swallow errors */\n });\n }\n\n agentContext.rebuildTokenMapAfterSummarization({});\n}\n\n// ---------------------------------------------------------------------------\n// createSummarizeNode\n// ---------------------------------------------------------------------------\n\ninterface CreateSummarizeNodeParams {\n agentContext: AgentContext;\n graph: {\n contentData: t.RunStep[];\n contentIndexMap: Map<string, number>;\n config?: RunnableConfig;\n runId?: string;\n isMultiAgent: boolean;\n hookRegistry?: HookRegistry;\n dispatchRunStep: (\n runStep: t.RunStep,\n config?: RunnableConfig\n ) => Promise<void>;\n dispatchRunStepCompleted: (\n stepId: string,\n result: t.StepCompleted,\n config?: RunnableConfig\n ) => Promise<void>;\n };\n generateStepId: (stepKey: string) => [string, number];\n}\n\nexport function createSummarizeNode({\n agentContext,\n graph,\n generateStepId,\n}: CreateSummarizeNodeParams) {\n return async (\n state: {\n messages: BaseMessage[];\n summarizationRequest?: t.SummarizationNodeInput;\n },\n config?: RunnableConfig\n ): Promise<{ summarizationRequest: undefined; messages?: BaseMessage[] }> => {\n const request = state.summarizationRequest;\n if (request == null) {\n return { summarizationRequest: undefined };\n }\n\n const maxCtx = agentContext.maxContextTokens ?? 0;\n if (maxCtx > 0 && agentContext.instructionTokens >= maxCtx) {\n emitAgentLog(\n config,\n 'warn',\n 'summarize',\n 'Summarization skipped, instructions exceed context budget. Reduce the number of tools or increase maxContextTokens.',\n {\n instructionTokens: agentContext.instructionTokens,\n maxContextTokens: maxCtx,\n breakdown: agentContext.formatTokenBudgetBreakdown(),\n },\n { runId: graph.runId, agentId: request.agentId }\n );\n return { summarizationRequest: undefined };\n }\n\n /**\n * Capture the original-tool-content map locally before doing the\n * split. We need it in three places: to restore the head for\n * summarizer quality, to leave intact on the skip path (state is\n * unchanged), and — critically — to carry forward the tail-relevant\n * entries on the summarize-fired path. Clearing it eagerly here\n * would lose the originals for masked tool messages that the\n * recency window keeps in the tail; a future summarization could\n * then only summarize the masked stub instead of the full payload.\n */\n const originalPending = agentContext.pendingOriginalToolContent;\n\n const restoredMessages = restoreOriginalToolContent(\n state.messages,\n originalPending\n );\n\n const runnableConfig = config ?? graph.config;\n\n const retainRecent = agentContext.summarizationConfig?.retainRecent;\n const { head: messagesToRefine, tailStartIndex } = splitAtRecencyBoundary(\n restoredMessages,\n {\n turns: retainRecent?.turns ?? DEFAULT_RETAIN_RECENT_TURNS,\n tokens: retainRecent?.tokens,\n tokenCounter: agentContext.tokenCounter,\n }\n );\n /**\n * Use the *masked* messages for the retained tail so that any\n * truncation prune applied to oversized ToolMessage content stays\n * truncated in live state. The summarizer above reads the restored\n * (full-content) head for summary quality, but reinjecting restored\n * tool payloads into state would defeat masking and bloat the\n * checkpoint, forcing more expensive re-pruning on later turns.\n * `restoreOriginalToolContent` returns an array with identical\n * length and structure to `state.messages` (replacements only at\n * specific indices), so the same tailStartIndex slices both arrays\n * at the same turn boundary.\n */\n const messagesToRetain = state.messages.slice(tailStartIndex);\n\n if (messagesToRefine.length === 0) {\n /**\n * Recency window covers the entire conversation — there is no\n * older content to summarize. Skipping prevents the model from\n * destroying the user's most recent message (e.g. a large pasted\n * payload on the first turn) by replacing it with a generic\n * checkpoint summary. Mark the trigger so the same unchanged\n * state is not re-evaluated on the next prune cycle.\n */\n emitAgentLog(\n config,\n 'debug',\n 'summarize',\n 'Summarization skipped — recency window retains all messages',\n {\n messagesRetained: messagesToRetain.length,\n retainTurns: retainRecent?.turns ?? DEFAULT_RETAIN_RECENT_TURNS,\n },\n { runId: graph.runId, agentId: request.agentId }\n );\n agentContext.markSummarizationTriggered(state.messages.length);\n return { summarizationRequest: undefined };\n }\n\n const clientConfig = buildSummarizationClientConfig(\n agentContext,\n agentContext.summarizationConfig\n );\n\n const stepKey = `summarize-${request.agentId}`;\n const [stepId, stepIndex] = generateStepId(stepKey);\n\n const placeholderSummary: t.SummaryContentBlock = {\n type: ContentTypes.SUMMARY,\n model: clientConfig.modelName,\n provider: clientConfig.provider,\n };\n\n const runStep: t.RunStep = {\n stepIndex,\n id: stepId,\n type: StepTypes.MESSAGE_CREATION,\n index: graph.contentData.length,\n stepDetails: {\n type: StepTypes.MESSAGE_CREATION,\n message_creation: { message_id: stepId },\n },\n summary: placeholderSummary,\n usage: null,\n };\n\n if (graph.runId != null && graph.runId !== '') {\n runStep.runId = graph.runId;\n }\n if (graph.isMultiAgent && agentContext.agentId) {\n runStep.agentId = agentContext.agentId;\n }\n\n await graph.dispatchRunStep(runStep, runnableConfig);\n\n if (runnableConfig) {\n await safeDispatchCustomEvent(\n GraphEvents.ON_SUMMARIZE_START,\n {\n agentId: request.agentId,\n provider: clientConfig.provider,\n model: clientConfig.modelName,\n messagesToRefineCount: messagesToRefine.length,\n summaryVersion: agentContext.summaryVersion + 1,\n } satisfies t.SummarizeStartEvent,\n runnableConfig\n );\n }\n\n const sessionId = graph.runId ?? '';\n if (graph.hookRegistry?.hasHookFor('PreCompact', sessionId) === true) {\n const threadId = (\n runnableConfig?.configurable as Record<string, unknown> | undefined\n )?.thread_id as string | undefined;\n await executeHooks({\n registry: graph.hookRegistry,\n input: {\n hook_event_name: 'PreCompact',\n runId: sessionId,\n threadId,\n agentId: request.agentId,\n messagesBeforeCount: messagesToRefine.length,\n trigger: agentContext.summarizationConfig?.trigger?.type ?? 'default',\n },\n sessionId,\n }).catch(() => {\n /* PreCompact is observational — swallow errors */\n });\n }\n\n const isSelfSummarizeModel =\n clientConfig.provider === (agentContext.provider as string);\n const hasPromptCache =\n isSelfSummarizeModel &&\n (agentContext.clientOptions as Record<string, unknown> | undefined)\n ?.promptCache === true;\n\n const log: LogFn = (level, message, data) => {\n emitAgentLog(runnableConfig, level, 'summarize', message, data, {\n runId: graph.runId,\n agentId: request.agentId,\n });\n };\n\n log('debug', 'Summarization starting', {\n messagesToRefineCount: messagesToRefine.length,\n hasPriorSummary: (agentContext.getSummaryText()?.trim() ?? '') !== '',\n summaryVersion: agentContext.summaryVersion + 1,\n isSelfSummarize: isSelfSummarizeModel,\n hasPromptCache,\n provider: clientConfig.provider,\n });\n\n const summarizeConfig: RunnableConfig | undefined = config\n ? {\n ...config,\n metadata: {\n ...config.metadata,\n agent_id: request.agentId,\n summarization_provider: clientConfig.provider,\n summarization_model: clientConfig.modelName,\n /**\n * Per-call model attribution for usage consumers (the subagent\n * usage-capture handler): the summarizer's model can differ from\n * the agent's primary, and providers that emit no `ls_model_name`\n * would otherwise be billed against the primary config's model.\n * Omitted for self-summarize (no explicit model — the primary\n * config fallback is then correct). `tryFallbackProviders`\n * overrides this per fallback attempt; `INVOKED_PROVIDER` is\n * stamped by `attemptInvoke` itself.\n */\n ...(clientConfig.modelName != null && clientConfig.modelName !== ''\n ? { [Constants.INVOKED_MODEL]: clientConfig.modelName }\n : {}),\n },\n }\n : undefined;\n\n const { text: rawText, usage: summaryUsage } =\n await executeSummarizationWithFallback({\n agentContext,\n messages: messagesToRefine,\n clientConfig,\n summarizeConfig,\n stepId,\n usePromptCache: isSelfSummarizeModel && hasPromptCache,\n log,\n });\n\n if (!rawText) {\n agentContext.markSummarizationTriggered(0);\n if (runnableConfig) {\n await safeDispatchCustomEvent(\n GraphEvents.ON_SUMMARIZE_COMPLETE,\n {\n id: stepId,\n agentId: request.agentId,\n error: 'Summarization produced empty output',\n } satisfies t.SummarizeCompleteEvent,\n runnableConfig\n );\n }\n return { summarizationRequest: undefined };\n }\n\n const summaryText = enrichSummary(rawText, messagesToRefine);\n\n const tokenCount = computeSummaryTokenCount(\n summaryText,\n summaryUsage,\n agentContext.tokenCounter\n );\n\n agentContext.setSummary(summaryText, tokenCount);\n\n log('info', 'Summary persisted');\n log('debug', 'Summary details', {\n summaryTokens: tokenCount,\n textLength: summaryText.length,\n messagesCompacted: messagesToRefine.length,\n summaryVersion: agentContext.summaryVersion,\n ...(summaryUsage != null\n ? {\n input_tokens: summaryUsage.input_tokens,\n output_tokens: summaryUsage.output_tokens,\n cache_read: summaryUsage.input_token_details?.cache_read,\n cache_creation: summaryUsage.input_token_details?.cache_creation,\n }\n : {}),\n });\n\n const summaryBlock = buildSummaryBlock({\n summaryText,\n tokenCount,\n stepId,\n stepIndex: runStep.index,\n modelName: clientConfig.modelName,\n provider: clientConfig.provider,\n summaryVersion: agentContext.summaryVersion,\n });\n\n await dispatchCompletionEvents({\n graph,\n runnableConfig,\n stepId,\n summaryBlock,\n agentContext,\n runStep,\n summaryUsage,\n agentId: request.agentId,\n messagesAfterCount: messagesToRetain.length,\n });\n\n /**\n * `dispatchCompletionEvents` calls `rebuildTokenMapAfterSummarization({})`\n * which resets the dedupe baseline to 0 — correct under the legacy\n * \"remove-all only\" shape where no messages survived, but stale once\n * the recency window keeps a tail. Realign the baseline to the\n * surviving tail length so a subsequent prune cycle on the unchanged\n * tail short-circuits via `shouldSkipSummarization` instead of\n * looping back into another summarize call.\n */\n agentContext.markSummarizationTriggered(messagesToRetain.length);\n\n /**\n * Carry forward the original-content entries that correspond to the\n * retained tail, reindexed for the post-removeAll state where tail\n * messages start at index 0. Without this, a future summarization\n * that pulls these tail messages into its head would only see the\n * masked stubs (since `setSummary` clears `pruneMessages`, and the\n * fresh pruner at the next turn has no record of prior masks).\n * Entries for indices < `tailStartIndex` belong to messages we just\n * summarized — they are no longer reachable so they are dropped.\n */\n if (originalPending != null && originalPending.size > 0) {\n const tailPending = new Map<number, string>();\n for (const [idx, content] of originalPending) {\n if (idx >= tailStartIndex) {\n tailPending.set(idx - tailStartIndex, content);\n }\n }\n agentContext.pendingOriginalToolContent =\n tailPending.size > 0 ? tailPending : undefined;\n } else {\n agentContext.pendingOriginalToolContent = undefined;\n }\n\n return {\n summarizationRequest: undefined,\n messages:\n messagesToRetain.length > 0\n ? [createRemoveAllMessage(), ...messagesToRetain]\n : [createRemoveAllMessage()],\n };\n };\n}\n\n/** Extracts text from an LLM response, skipping reasoning/thinking blocks. */\nfunction extractResponseText(response: { content: string | object }): string {\n const { content } = response;\n if (typeof content === 'string') {\n return content.trim();\n }\n if (!Array.isArray(content)) {\n return '';\n }\n const parts: string[] = [];\n for (const block of content) {\n if (typeof block === 'string') {\n parts.push(block);\n continue;\n }\n if (block == null || typeof block !== 'object') {\n continue;\n }\n const rec = block as Record<string, unknown>;\n if (\n rec.type === ContentTypes.THINKING ||\n rec.type === ContentTypes.REASONING_CONTENT ||\n rec.type === 'redacted_thinking'\n ) {\n continue;\n }\n if (rec.type === 'text' && typeof rec.text === 'string') {\n parts.push(rec.text);\n }\n }\n return parts.join('').trim();\n}\n\nfunction buildSummarizationInstruction(\n promptText: string,\n updatePromptText: string | undefined,\n priorSummaryText: string\n): string {\n const effectivePrompt = priorSummaryText\n ? (updatePromptText ?? promptText)\n : promptText;\n const parts = [effectivePrompt];\n if (priorSummaryText) {\n parts.push(\n `\\n\\n<previous-summary>\\n${priorSummaryText}\\n</previous-summary>`\n );\n }\n return parts.join('');\n}\n\n/** Creates an `onChunk` callback that dispatches `ON_SUMMARIZE_DELTA` events for streaming. */\nfunction createSummarizationChunkHandler({\n stepId,\n config,\n provider,\n reasoningKey = 'reasoning_content',\n}: {\n stepId?: string;\n config?: RunnableConfig;\n provider?: Providers;\n reasoningKey?: 'reasoning_content' | 'reasoning';\n}): OnChunk | undefined {\n if (stepId == null || stepId === '' || !config) {\n return undefined;\n }\n return (chunk) => {\n const chunkAny = chunk as Parameters<typeof getChunkContent>[0]['chunk'];\n const raw = getChunkContent({ chunk: chunkAny, provider, reasoningKey });\n if (raw == null || (typeof raw === 'string' && !raw)) {\n return;\n }\n const contentBlocks: t.MessageContentComplex[] =\n typeof raw === 'string'\n ? [{ type: ContentTypes.TEXT, text: raw } as t.MessageContentComplex]\n : raw;\n\n void safeDispatchCustomEvent(\n GraphEvents.ON_SUMMARIZE_DELTA,\n {\n id: stepId,\n delta: {\n summary: {\n type: ContentTypes.SUMMARY,\n content: contentBlocks,\n provider: String(config.metadata?.summarization_provider ?? ''),\n model: String(config.metadata?.summarization_model ?? ''),\n },\n },\n } satisfies t.SummarizeDeltaEvent,\n config\n );\n };\n}\n\nfunction traceConfig(\n config: RunnableConfig | undefined,\n stage: string\n): RunnableConfig | undefined {\n if (!config) {\n return undefined;\n }\n return {\n ...config,\n runName: `summarization:${stage}`,\n metadata: { ...config.metadata, summarization: true, stage },\n };\n}\n\n/**\n * Cache-friendly compaction: sends raw conversation messages with the\n * summarization instruction appended as the final HumanMessage.\n * Providers with prompt caching get a cache hit on the system prompt +\n * tool definitions prefix.\n */\nasync function summarizeWithCacheHit({\n model,\n messages,\n promptText,\n updatePromptText,\n priorSummaryText,\n config,\n stepId,\n provider,\n reasoningKey,\n usePromptCache,\n log,\n}: {\n model: t.ChatModel;\n messages: BaseMessage[];\n promptText: string;\n updatePromptText?: string;\n priorSummaryText: string;\n config?: RunnableConfig;\n stepId?: string;\n provider: Providers;\n reasoningKey?: 'reasoning_content' | 'reasoning';\n usePromptCache?: boolean;\n log?: LogFn;\n}): Promise<{ text: string; usage?: Partial<UsageMetadata> }> {\n const instruction = buildSummarizationInstruction(\n promptText,\n updatePromptText,\n priorSummaryText\n );\n\n const fullMessages = [...messages, new HumanMessage(instruction)];\n const invokeMessages =\n usePromptCache === true ? addTailCacheControl(fullMessages) : fullMessages;\n\n const result = await attemptInvoke(\n {\n model,\n messages: invokeMessages,\n provider,\n onChunk: createSummarizationChunkHandler({\n stepId,\n config: traceConfig(config, 'cache_hit_compaction'),\n provider,\n reasoningKey,\n }),\n },\n traceConfig(config, 'cache_hit_compaction')\n );\n\n const responseMsg = result.messages?.[0];\n const text = responseMsg\n ? extractResponseText(responseMsg as { content: string | object })\n : '';\n let usage: Partial<UsageMetadata> | undefined;\n let usageSource = 'none';\n if (\n responseMsg != null &&\n 'usage_metadata' in responseMsg &&\n responseMsg.usage_metadata != null\n ) {\n usage = responseMsg.usage_metadata as Partial<UsageMetadata>;\n usageSource = 'usage_metadata';\n } else if (responseMsg != null) {\n const respMeta = responseMsg.response_metadata as\n | Record<string, unknown>\n | undefined;\n const raw = (respMeta?.metadata as Record<string, unknown> | undefined)\n ?.usage as Record<string, unknown> | undefined;\n if (raw != null) {\n usage = {\n input_tokens: Number(raw.inputTokens) || undefined,\n output_tokens: Number(raw.outputTokens) || undefined,\n } as Partial<UsageMetadata>;\n usageSource = 'response_metadata';\n }\n }\n const cacheDetails = (\n usage as\n | {\n input_token_details?: {\n cache_read?: number;\n cache_creation?: number;\n };\n }\n | undefined\n )?.input_token_details;\n log?.('debug', 'Summarization LLM usage', {\n source: usageSource,\n input_tokens: usage?.input_tokens,\n output_tokens: usage?.output_tokens,\n ...(cacheDetails?.cache_read != null || cacheDetails?.cache_creation != null\n ? {\n 'input_token_details.cache_read': cacheDetails.cache_read,\n 'input_token_details.cache_creation': cacheDetails.cache_creation,\n }\n : {}),\n });\n return { text, usage };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"node.cjs","names":["AIMessage","ToolMessage","getMaxOutputTokensKey","SystemMessage","initializeModel","resolvePromptCacheTtl","tryFallbackProviders","HumanMessage","safeDispatchCustomEvent","executeHooks","splitAtRecencyBoundary","createRemoveAllMessage","getChunkContent","chunkAny","attemptInvoke","addTailCacheControl"],"sources":["../../../src/summarization/node.ts"],"sourcesContent":["import {\n AIMessage,\n ToolMessage,\n HumanMessage,\n SystemMessage,\n} from '@langchain/core/messages';\nimport type { UsageMetadata, BaseMessage } from '@langchain/core/messages';\nimport type { RunnableConfig } from '@langchain/core/runnables';\nimport type { AgentContext } from '@/agents/AgentContext';\nimport type { HookRegistry } from '@/hooks';\nimport type { OnChunk } from '@/llm/invoke';\nimport type * as t from '@/types';\nimport {\n addTailCacheControl,\n resolvePromptCacheTtl,\n type PromptCacheTtl,\n} from '@/messages/cache';\nimport {\n Constants,\n ContentTypes,\n GraphEvents,\n StepTypes,\n Providers,\n} from '@/common';\nimport { safeDispatchCustomEvent, emitAgentLog } from '@/utils/events';\nimport { attemptInvoke, tryFallbackProviders } from '@/llm/invoke';\nimport { createRemoveAllMessage } from '@/messages/reducer';\nimport { splitAtRecencyBoundary } from '@/messages/recency';\nimport { getMaxOutputTokensKey } from '@/llm/request';\nimport { initializeModel } from '@/llm/init';\nimport { getChunkContent } from '@/stream';\nimport { executeHooks } from '@/hooks';\n\nconst SUMMARIZATION_PARAM_KEYS = new Set(['maxSummaryTokens']);\n\n/**\n * Default number of recent user-led turns preserved verbatim during\n * compaction. A turn begins at a HumanMessage and includes every\n * following AIMessage and ToolMessage up to the next HumanMessage.\n * The most recent turn is always retained regardless of this value;\n * the default of `2` additionally keeps the prior exchange so the\n * model has fresh context on what just happened. Setting\n * `retainRecent.turns` to `0` reverts to the legacy \"summarize every\n * message\" behavior.\n */\nconst DEFAULT_RETAIN_RECENT_TURNS = 2;\n\n/**\n * Token overhead of the XML wrapper + instruction text added around the\n * summary at injection time in AgentContext.buildSystemRunnable:\n * `<summary>\\n${text}\\n</summary>\\n\\nYour context window was compacted...`\n * ~33 tokens on Anthropic, ~24-27 on OpenAI. Using 33 as a safe ceiling.\n */\nconst SUMMARY_WRAPPER_OVERHEAD_TOKENS = 33;\n\n/** Structured checkpoint prompt for fresh summarization (no prior summary). */\nexport const DEFAULT_SUMMARIZATION_PROMPT = `Hold on, before you continue I need you to write me a checkpoint of everything so far. Your context window is filling up and this checkpoint replaces the messages above, so capture everything you need to pick right back up.\n\nDon't second-guess or fact-check anything you did, your tool results reflect exactly what happened. If a tool result appears truncated, that's just a display artifact from context management: the tool executed fully. Just record what you did and what you observed. Only the checkpoint, don't respond to me or continue the conversation.\n\n## Checkpoint\n\n## Goal\nWhat I asked you to do and any sub-goals you identified.\n\n## Constraints & Preferences\nAny rules, preferences, or configuration I established.\n\n## Progress\n### Done\n- What you completed and the outcomes\n\n### In Progress\n- What you're currently working on\n\n## Key Decisions\nDecisions you made and why.\n\n## Next Steps\nConcrete task actions remaining, in priority order.\n\n## Critical Context\nExact identifiers, names, error messages, URLs, and details you need to preserve verbatim.\n\nRules:\n- Record what you did and observed, don't judge or re-evaluate it\n- For each tool call: the tool name, key inputs, and the outcome\n- Preserve exact identifiers, names, errors, and references verbatim\n- Short declarative sentences\n- Skip empty sections`;\n\n/** Prompt for re-compaction when a prior summary exists. */\nexport const DEFAULT_UPDATE_SUMMARIZATION_PROMPT = `Hold on again, update your checkpoint. Merge the new messages into your existing checkpoint and give me a single consolidated replacement.\n\nKeep it roughly the same length as your last checkpoint. Compress older details to make room for what's new, don't just append. Give recent actions more detail, compress older items to one-liners.\n\nDon't fact-check or second-guess anything, your tool results are ground truth. If a tool result appears truncated, that's just a display artifact: the tool executed fully. Only the checkpoint, don't respond to me or continue the conversation.\n\nRules:\n- Merge new progress into existing sections, don't duplicate headers\n- Compress older completed items into one-line entries\n- Move items from \"In Progress\" to \"Done\" when you completed them\n- Update \"Next Steps\" to reflect current task priorities.\n- For each new tool call: the tool name, key inputs, and the outcome\n- Preserve exact identifiers, names, errors, and references verbatim\n- Skip empty sections`;\n\nfunction separateParameters(parameters: Record<string, unknown>): {\n llmParams: Record<string, unknown>;\n maxSummaryTokens?: number;\n} {\n const llmParams: Record<string, unknown> = {};\n let maxSummaryTokens: number | undefined;\n\n for (const [key, value] of Object.entries(parameters)) {\n if (SUMMARIZATION_PARAM_KEYS.has(key)) {\n if (\n key === 'maxSummaryTokens' &&\n typeof value === 'number' &&\n value > 0\n ) {\n maxSummaryTokens = value;\n }\n } else {\n llmParams[key] = value;\n }\n }\n\n return { llmParams, maxSummaryTokens };\n}\n\n/**\n * Generates a structural metadata summary without making an LLM call.\n * Used as a last-resort fallback when all summarization attempts fail.\n * Preserves tool names and message counts so the agent retains basic context.\n */\nfunction generateMetadataStub(messages: BaseMessage[]): string {\n const counts: Record<string, number> = {};\n const toolNames = new Set<string>();\n\n for (const msg of messages) {\n const role = msg.getType();\n counts[role] = (counts[role] ?? 0) + 1;\n\n if (role === 'tool' && msg.name != null && msg.name !== '') {\n toolNames.add(msg.name);\n }\n\n if (\n role === 'ai' &&\n msg instanceof AIMessage &&\n msg.tool_calls &&\n msg.tool_calls.length > 0\n ) {\n for (const tc of msg.tool_calls) {\n toolNames.add(tc.name);\n }\n }\n }\n\n const countParts = Object.entries(counts)\n .map(([role, count]) => `${count} ${role}`)\n .join(', ');\n\n const lines = [\n `[Metadata summary: ${messages.length} messages (${countParts})]`,\n ];\n\n if (toolNames.size > 0) {\n lines.push(`[Tools used: ${Array.from(toolNames).join(', ')}]`);\n }\n\n return lines.join('\\n');\n}\n\n/** Maximum number of tool failures to include in the enrichment section. */\nconst MAX_TOOL_FAILURES = 8;\n/** Maximum chars per failure summary line. */\nconst MAX_TOOL_FAILURE_CHARS = 240;\n\n/**\n * Extracts failed tool results from messages and formats them as a structured\n * section. LLMs often omit specific failure details (exit codes, error messages)\n * from their summaries, this mechanical enrichment guarantees they survive.\n */\nfunction extractToolFailuresSection(messages: BaseMessage[]): string {\n const failures: Array<{ toolName: string; summary: string }> = [];\n const seen = new Set<string>();\n\n for (const msg of messages) {\n if (msg.getType() !== 'tool') {\n continue;\n }\n const toolMsg = msg as ToolMessage;\n if (toolMsg.status !== 'error') {\n continue;\n }\n // Deduplicate by tool_call_id\n const callId = toolMsg.tool_call_id;\n if (callId && seen.has(callId)) {\n continue;\n }\n if (callId) {\n seen.add(callId);\n }\n\n const toolName = toolMsg.name ?? 'tool';\n const content =\n typeof toolMsg.content === 'string'\n ? toolMsg.content\n : JSON.stringify(toolMsg.content);\n const normalized = content.replace(/\\s+/g, ' ').trim();\n const summary =\n normalized.length > MAX_TOOL_FAILURE_CHARS\n ? `${normalized.slice(0, MAX_TOOL_FAILURE_CHARS - 3)}...`\n : normalized;\n\n failures.push({ toolName, summary });\n }\n\n if (failures.length === 0) {\n return '';\n }\n\n const lines = failures\n .slice(0, MAX_TOOL_FAILURES)\n .map((f) => `- ${f.toolName}: ${f.summary}`);\n if (failures.length > MAX_TOOL_FAILURES) {\n lines.push(`- ...and ${failures.length - MAX_TOOL_FAILURES} more`);\n }\n\n return `\\n\\n## Tool Failures\\n${lines.join('\\n')}`;\n}\n\n/**\n * Appends mechanical enrichment sections to an LLM-generated summary.\n * Tool failures are appended verbatim because LLMs often omit specific\n * error details from their summaries.\n */\nfunction enrichSummary(summaryText: string, messages: BaseMessage[]): string {\n return summaryText + extractToolFailuresSection(messages);\n}\n\n/**\n * Restores pre-masking tool content onto the messages array using\n * `pendingOriginalToolContent` stored on AgentContext. Only allocates\n * a new array when there are entries to restore; otherwise returns the\n * input reference unchanged.\n */\nfunction restoreOriginalToolContent(\n messages: BaseMessage[],\n originalToolContent: Map<number, string> | undefined\n): BaseMessage[] {\n if (originalToolContent == null || originalToolContent.size === 0) {\n return messages;\n }\n const restored = [...messages];\n for (const [idx, content] of originalToolContent) {\n const msg = restored[idx];\n if (msg instanceof ToolMessage) {\n restored[idx] = new ToolMessage({\n content,\n tool_call_id: msg.tool_call_id,\n name: msg.name,\n id: msg.id,\n additional_kwargs: msg.additional_kwargs,\n response_metadata: msg.response_metadata,\n });\n }\n }\n return restored;\n}\n\n// ---------------------------------------------------------------------------\n// Extracted helpers for createSummarizeNode\n// ---------------------------------------------------------------------------\n\ninterface SummarizationClientConfig {\n provider: string;\n modelName?: string;\n clientOptions: Record<string, unknown>;\n effectiveMaxSummaryTokens?: number;\n promptText: string;\n updatePromptText: string;\n}\n\n/** Assembles the summarization model's client options from agent and config. */\nfunction buildSummarizationClientConfig(\n agentContext: AgentContext,\n summarizationConfig?: t.SummarizationConfig\n): SummarizationClientConfig {\n const provider = (summarizationConfig?.provider ??\n agentContext.provider) as string;\n const modelName = summarizationConfig?.model;\n const parameters = summarizationConfig?.parameters ?? {};\n const promptText =\n summarizationConfig?.prompt ?? DEFAULT_SUMMARIZATION_PROMPT;\n const updatePromptText =\n summarizationConfig?.updatePrompt ?? DEFAULT_UPDATE_SUMMARIZATION_PROMPT;\n\n const { llmParams, maxSummaryTokens: paramMaxSummaryTokens } =\n separateParameters(parameters);\n\n const isSelfSummarize = provider === (agentContext.provider as string);\n const baseOptions =\n isSelfSummarize && agentContext.clientOptions\n ? { ...agentContext.clientOptions }\n : {};\n\n const clientOptions: Record<string, unknown> = {\n ...baseOptions,\n ...llmParams,\n };\n\n if (modelName != null && modelName !== '') {\n clientOptions.model = modelName;\n clientOptions.modelName = modelName;\n }\n\n const effectiveMaxSummaryTokens =\n paramMaxSummaryTokens ?? summarizationConfig?.maxSummaryTokens;\n\n if (effectiveMaxSummaryTokens != null) {\n clientOptions[getMaxOutputTokensKey(provider)] = effectiveMaxSummaryTokens;\n }\n\n return {\n provider,\n modelName,\n clientOptions,\n effectiveMaxSummaryTokens,\n promptText,\n updatePromptText,\n };\n}\n\n/** Computes the token count for a summary, preferring provider output tokens when available. */\nfunction computeSummaryTokenCount(\n summaryText: string,\n summaryUsage: Partial<UsageMetadata> | undefined,\n tokenCounter?: (message: BaseMessage) => number\n): number {\n const providerOutputTokens = Number(summaryUsage?.output_tokens) || 0;\n if (providerOutputTokens > 0) {\n return providerOutputTokens + SUMMARY_WRAPPER_OVERHEAD_TOKENS;\n }\n if (tokenCounter) {\n return (\n tokenCounter(new SystemMessage(summaryText)) +\n SUMMARY_WRAPPER_OVERHEAD_TOKENS\n );\n }\n return 0;\n}\n\n/** Constructs the SummaryContentBlock persisted in the run step and dispatched to events. */\nfunction buildSummaryBlock(params: {\n summaryText: string;\n tokenCount: number;\n stepId: string;\n stepIndex: number;\n modelName?: string;\n provider: string;\n summaryVersion: number;\n}): t.SummaryContentBlock {\n return {\n type: ContentTypes.SUMMARY,\n content: [\n {\n type: ContentTypes.TEXT,\n text: params.summaryText,\n } as t.MessageContentComplex,\n ],\n tokenCount: params.tokenCount,\n summaryVersion: params.summaryVersion,\n boundary: {\n messageId: params.stepId,\n contentIndex: params.stepIndex,\n },\n model: params.modelName,\n provider: params.provider,\n createdAt: new Date().toISOString(),\n };\n}\n\ntype LogFn = (\n level: 'debug' | 'info' | 'warn' | 'error',\n message: string,\n data?: Record<string, unknown>\n) => void;\n\n/**\n * Extracts an HTTP status code from a thrown LLM-provider error. Returns\n * `undefined` for non-object values (including `null` or `undefined`, both\n * valid `throw` targets in JS) so callers never dereference a nullish\n * value.\n */\nfunction extractHttpStatus(err: unknown): number | undefined {\n if (err == null || typeof err !== 'object') {\n return undefined;\n }\n const errRecord = err as Record<string, unknown>;\n const direct = errRecord.status;\n if (typeof direct === 'number') {\n return direct;\n }\n const statusCode = errRecord.statusCode;\n if (typeof statusCode === 'number') {\n return statusCode;\n }\n const response = errRecord.response;\n if (response != null && typeof response === 'object') {\n const nested = (response as Record<string, unknown>).status;\n if (typeof nested === 'number') {\n return nested;\n }\n }\n return undefined;\n}\n\n/**\n * Formats a provider-level error for logging. Returns both a human-readable\n * suffix (safe to include in the message string so it survives any host-side\n * formatter) and a structured metadata bag for rich log backends.\n */\nfunction describeProviderError(\n err: unknown,\n provider: string,\n modelName?: string\n): { suffix: string; data: Record<string, unknown> } {\n const providerLabel = `${provider}/${modelName ?? '(no-model)'}`;\n const errMsg = err instanceof Error ? err.message : String(err);\n\n const data: Record<string, unknown> = {\n provider,\n model: modelName,\n };\n if (err instanceof Error) {\n data.errorName = err.name;\n data.errorStack = err.stack;\n }\n\n const status = extractHttpStatus(err);\n const statusSuffix = status != null ? ` (HTTP ${status})` : '';\n if (status != null) {\n data.status = status;\n }\n\n return {\n suffix: `[${providerLabel}]${statusSuffix}: ${errMsg}`,\n data,\n };\n}\n\n/**\n * Formats an exhausted-fallback error. `tryFallbackProviders` throws the\n * last fallback provider's error, which may be from any of the configured\n * fallbacks — not the primary — so we label the log with the list of\n * fallback providers attempted rather than mis-attributing to the primary.\n *\n * Entries in `fallbacks` are normally strongly typed, but we defend against\n * malformed runtime config (null/undefined entries, missing `provider`\n * field) so a recoverable summarization failure is never promoted to an\n * uncaught exception from inside the logging path.\n */\nfunction describeFallbackError(\n err: unknown,\n fallbacks: unknown\n): { suffix: string; data: Record<string, unknown> } {\n const errMsg = err instanceof Error ? err.message : String(err);\n const list: ReadonlyArray<unknown> = Array.isArray(fallbacks)\n ? fallbacks\n : [];\n const providerNames = list\n .map((f) => {\n if (f == null || typeof f !== 'object') {\n return undefined;\n }\n const raw = (f as { provider?: unknown }).provider;\n return raw != null ? String(raw) : undefined;\n })\n .filter((p): p is string => typeof p === 'string');\n const label =\n providerNames.length > 0\n ? `fallbacks=[${providerNames.join(',')}]`\n : 'no-fallbacks';\n\n const data: Record<string, unknown> = {\n fallbackProviders: providerNames,\n fallbackCount: list.length,\n };\n if (err instanceof Error) {\n data.errorName = err.name;\n data.errorStack = err.stack;\n }\n const status = extractHttpStatus(err);\n const statusSuffix = status != null ? ` (HTTP ${status})` : '';\n if (status != null) {\n data.status = status;\n }\n\n return {\n suffix: `[${label}]${statusSuffix}: ${errMsg}`,\n data,\n };\n}\n\n/**\n * Runs the summarization LLM call with primary + fallback providers,\n * falling back to a metadata stub when all calls fail.\n */\nasync function executeSummarizationWithFallback(params: {\n agentContext: AgentContext;\n messages: BaseMessage[];\n clientConfig: SummarizationClientConfig;\n summarizeConfig?: RunnableConfig;\n stepId: string;\n usePromptCache: boolean;\n log: LogFn;\n}): Promise<{ text: string; usage?: Partial<UsageMetadata> }> {\n const {\n agentContext,\n messages,\n clientConfig,\n summarizeConfig,\n stepId,\n usePromptCache,\n log,\n } = params;\n\n const priorSummaryText = agentContext.getSummaryText()?.trim() ?? '';\n\n let summaryText = '';\n let summaryUsage: Partial<UsageMetadata> | undefined;\n\n try {\n /**\n * Initialize inside the try so that a misconfigured provider\n * (e.g. an unrecognized summarization.provider) surfaces through the\n * `log('error', ...)` path below rather than bubbling up silently.\n */\n const summarizationModel = initializeModel({\n provider: clientConfig.provider as Providers,\n clientOptions: clientConfig.clientOptions as t.ClientOptions,\n tools: agentContext.getToolsForBinding(),\n }) as t.ChatModel;\n\n const result = await summarizeWithCacheHit({\n model: summarizationModel,\n messages,\n promptText: clientConfig.promptText,\n updatePromptText: clientConfig.updatePromptText,\n priorSummaryText,\n config: summarizeConfig,\n stepId,\n provider: clientConfig.provider as Providers,\n reasoningKey: agentContext.reasoningKey,\n usePromptCache,\n promptCacheTtl:\n (clientConfig.provider as Providers) === Providers.ANTHROPIC ||\n (clientConfig.provider as Providers) === Providers.OPENROUTER\n ? resolvePromptCacheTtl(\n (\n clientConfig.clientOptions as {\n promptCacheTtl?: PromptCacheTtl;\n }\n ).promptCacheTtl\n )\n : undefined,\n log,\n });\n summaryText = result.text;\n summaryUsage = result.usage;\n } catch (primaryError) {\n const primaryDescribed = describeProviderError(\n primaryError,\n clientConfig.provider,\n clientConfig.modelName\n );\n log('error', `Summarization LLM call failed ${primaryDescribed.suffix}`, {\n ...primaryDescribed.data,\n messagesToRefineCount: messages.length,\n });\n\n const rawFallbacks = (\n clientConfig.clientOptions as unknown as t.LLMConfig | undefined\n )?.fallbacks;\n const fallbacks = Array.isArray(rawFallbacks) ? rawFallbacks : [];\n if (fallbacks.length > 0) {\n try {\n const onChunk = createSummarizationChunkHandler({\n stepId,\n config: traceConfig(summarizeConfig, 'cache_hit_compaction'),\n provider: clientConfig.provider as Providers,\n reasoningKey: agentContext.reasoningKey,\n });\n const fbResult = await tryFallbackProviders({\n fallbacks,\n tools: agentContext.getToolsForBinding(),\n messages: [\n ...messages,\n new HumanMessage(\n buildSummarizationInstruction(\n clientConfig.promptText,\n clientConfig.updatePromptText,\n priorSummaryText\n )\n ),\n ],\n config: traceConfig(summarizeConfig, 'cache_hit_compaction'),\n primaryError,\n onChunk,\n });\n const fbMsg = fbResult?.messages?.[0];\n if (fbMsg) {\n summaryText = extractResponseText(\n fbMsg as { content: string | object }\n );\n }\n } catch (fbErr) {\n const fbDescribed = describeFallbackError(fbErr, fallbacks);\n log('warn', `Fallback providers also failed ${fbDescribed.suffix}`, {\n ...fbDescribed.data,\n });\n }\n }\n if (!summaryText) {\n log(\n 'warn',\n `Summarization failed, falling back to metadata stub ${primaryDescribed.suffix}`,\n {\n ...primaryDescribed.data,\n messagesToRefineCount: messages.length,\n }\n );\n summaryText = generateMetadataStub(messages);\n }\n }\n\n return { text: summaryText, usage: summaryUsage };\n}\n\n/** Dispatches run step completion, ON_SUMMARIZE_COMPLETE, and rebuilds token map. */\nasync function dispatchCompletionEvents(params: {\n graph: CreateSummarizeNodeParams['graph'];\n runnableConfig?: RunnableConfig;\n stepId: string;\n summaryBlock: t.SummaryContentBlock;\n agentContext: AgentContext;\n runStep: t.RunStep;\n summaryUsage?: Partial<UsageMetadata>;\n agentId: string;\n /**\n * Number of messages preserved verbatim by the recency window after\n * compaction. Reported via the PostCompact hook payload so observers\n * (metrics, cleanup) see the true post-compaction message count\n * instead of always-zero.\n */\n messagesAfterCount: number;\n}): Promise<void> {\n const {\n graph,\n runnableConfig,\n stepId,\n summaryBlock,\n agentContext,\n runStep,\n summaryUsage,\n agentId,\n messagesAfterCount,\n } = params;\n\n runStep.summary = summaryBlock;\n if (summaryUsage) {\n runStep.usage = {\n prompt_tokens: Number(summaryUsage.input_tokens) || 0,\n completion_tokens: Number(summaryUsage.output_tokens) || 0,\n total_tokens:\n (Number(summaryUsage.input_tokens) || 0) +\n (Number(summaryUsage.output_tokens) || 0),\n };\n }\n\n await graph.dispatchRunStepCompleted(\n stepId,\n { type: 'summary', summary: summaryBlock } satisfies t.SummaryCompleted,\n runnableConfig\n );\n\n if (runnableConfig) {\n await safeDispatchCustomEvent(\n GraphEvents.ON_SUMMARIZE_COMPLETE,\n {\n id: stepId,\n agentId,\n summary: summaryBlock,\n } satisfies t.SummarizeCompleteEvent,\n runnableConfig\n );\n }\n\n const sessionId = graph.runId ?? '';\n if (graph.hookRegistry?.hasHookFor('PostCompact', sessionId) === true) {\n const threadId = (\n runnableConfig?.configurable as Record<string, unknown> | undefined\n )?.thread_id as string | undefined;\n const firstBlock = summaryBlock.content?.[0];\n const summaryText =\n firstBlock != null &&\n typeof firstBlock === 'object' &&\n 'text' in firstBlock &&\n typeof firstBlock.text === 'string'\n ? firstBlock.text\n : '';\n await executeHooks({\n registry: graph.hookRegistry,\n input: {\n hook_event_name: 'PostCompact',\n runId: sessionId,\n threadId,\n agentId,\n summary: summaryText,\n messagesAfterCount,\n },\n sessionId,\n }).catch(() => {\n /* PostCompact is observational — swallow errors */\n });\n }\n\n agentContext.rebuildTokenMapAfterSummarization({});\n}\n\n// ---------------------------------------------------------------------------\n// createSummarizeNode\n// ---------------------------------------------------------------------------\n\ninterface CreateSummarizeNodeParams {\n agentContext: AgentContext;\n graph: {\n contentData: t.RunStep[];\n contentIndexMap: Map<string, number>;\n config?: RunnableConfig;\n runId?: string;\n isMultiAgent: boolean;\n hookRegistry?: HookRegistry;\n dispatchRunStep: (\n runStep: t.RunStep,\n config?: RunnableConfig\n ) => Promise<void>;\n dispatchRunStepCompleted: (\n stepId: string,\n result: t.StepCompleted,\n config?: RunnableConfig\n ) => Promise<void>;\n };\n generateStepId: (stepKey: string) => [string, number];\n}\n\nexport function createSummarizeNode({\n agentContext,\n graph,\n generateStepId,\n}: CreateSummarizeNodeParams) {\n return async (\n state: {\n messages: BaseMessage[];\n summarizationRequest?: t.SummarizationNodeInput;\n },\n config?: RunnableConfig\n ): Promise<{ summarizationRequest: undefined; messages?: BaseMessage[] }> => {\n const request = state.summarizationRequest;\n if (request == null) {\n return { summarizationRequest: undefined };\n }\n\n const maxCtx = agentContext.maxContextTokens ?? 0;\n if (maxCtx > 0 && agentContext.instructionTokens >= maxCtx) {\n emitAgentLog(\n config,\n 'warn',\n 'summarize',\n 'Summarization skipped, instructions exceed context budget. Reduce the number of tools or increase maxContextTokens.',\n {\n instructionTokens: agentContext.instructionTokens,\n maxContextTokens: maxCtx,\n breakdown: agentContext.formatTokenBudgetBreakdown(),\n },\n { runId: graph.runId, agentId: request.agentId }\n );\n return { summarizationRequest: undefined };\n }\n\n /**\n * Capture the original-tool-content map locally before doing the\n * split. We need it in three places: to restore the head for\n * summarizer quality, to leave intact on the skip path (state is\n * unchanged), and — critically — to carry forward the tail-relevant\n * entries on the summarize-fired path. Clearing it eagerly here\n * would lose the originals for masked tool messages that the\n * recency window keeps in the tail; a future summarization could\n * then only summarize the masked stub instead of the full payload.\n */\n const originalPending = agentContext.pendingOriginalToolContent;\n\n const restoredMessages = restoreOriginalToolContent(\n state.messages,\n originalPending\n );\n\n const runnableConfig = config ?? graph.config;\n\n const retainRecent = agentContext.summarizationConfig?.retainRecent;\n const { head: messagesToRefine, tailStartIndex } = splitAtRecencyBoundary(\n restoredMessages,\n {\n turns: retainRecent?.turns ?? DEFAULT_RETAIN_RECENT_TURNS,\n tokens: retainRecent?.tokens,\n tokenCounter: agentContext.tokenCounter,\n }\n );\n /**\n * Use the *masked* messages for the retained tail so that any\n * truncation prune applied to oversized ToolMessage content stays\n * truncated in live state. The summarizer above reads the restored\n * (full-content) head for summary quality, but reinjecting restored\n * tool payloads into state would defeat masking and bloat the\n * checkpoint, forcing more expensive re-pruning on later turns.\n * `restoreOriginalToolContent` returns an array with identical\n * length and structure to `state.messages` (replacements only at\n * specific indices), so the same tailStartIndex slices both arrays\n * at the same turn boundary.\n */\n const messagesToRetain = state.messages.slice(tailStartIndex);\n\n if (messagesToRefine.length === 0) {\n /**\n * Recency window covers the entire conversation — there is no\n * older content to summarize. Skipping prevents the model from\n * destroying the user's most recent message (e.g. a large pasted\n * payload on the first turn) by replacing it with a generic\n * checkpoint summary. Mark the trigger so the same unchanged\n * state is not re-evaluated on the next prune cycle.\n */\n emitAgentLog(\n config,\n 'debug',\n 'summarize',\n 'Summarization skipped — recency window retains all messages',\n {\n messagesRetained: messagesToRetain.length,\n retainTurns: retainRecent?.turns ?? DEFAULT_RETAIN_RECENT_TURNS,\n },\n { runId: graph.runId, agentId: request.agentId }\n );\n agentContext.markSummarizationTriggered(state.messages.length);\n return { summarizationRequest: undefined };\n }\n\n const clientConfig = buildSummarizationClientConfig(\n agentContext,\n agentContext.summarizationConfig\n );\n\n const stepKey = `summarize-${request.agentId}`;\n const [stepId, stepIndex] = generateStepId(stepKey);\n\n const placeholderSummary: t.SummaryContentBlock = {\n type: ContentTypes.SUMMARY,\n model: clientConfig.modelName,\n provider: clientConfig.provider,\n };\n\n const runStep: t.RunStep = {\n stepIndex,\n id: stepId,\n type: StepTypes.MESSAGE_CREATION,\n index: graph.contentData.length,\n stepDetails: {\n type: StepTypes.MESSAGE_CREATION,\n message_creation: { message_id: stepId },\n },\n summary: placeholderSummary,\n usage: null,\n };\n\n if (graph.runId != null && graph.runId !== '') {\n runStep.runId = graph.runId;\n }\n if (graph.isMultiAgent && agentContext.agentId) {\n runStep.agentId = agentContext.agentId;\n }\n\n await graph.dispatchRunStep(runStep, runnableConfig);\n\n if (runnableConfig) {\n await safeDispatchCustomEvent(\n GraphEvents.ON_SUMMARIZE_START,\n {\n agentId: request.agentId,\n provider: clientConfig.provider,\n model: clientConfig.modelName,\n messagesToRefineCount: messagesToRefine.length,\n summaryVersion: agentContext.summaryVersion + 1,\n } satisfies t.SummarizeStartEvent,\n runnableConfig\n );\n }\n\n const sessionId = graph.runId ?? '';\n if (graph.hookRegistry?.hasHookFor('PreCompact', sessionId) === true) {\n const threadId = (\n runnableConfig?.configurable as Record<string, unknown> | undefined\n )?.thread_id as string | undefined;\n await executeHooks({\n registry: graph.hookRegistry,\n input: {\n hook_event_name: 'PreCompact',\n runId: sessionId,\n threadId,\n agentId: request.agentId,\n messagesBeforeCount: messagesToRefine.length,\n trigger: agentContext.summarizationConfig?.trigger?.type ?? 'default',\n },\n sessionId,\n }).catch(() => {\n /* PreCompact is observational — swallow errors */\n });\n }\n\n const isSelfSummarizeModel =\n clientConfig.provider === (agentContext.provider as string);\n const hasPromptCache =\n isSelfSummarizeModel &&\n (agentContext.clientOptions as Record<string, unknown> | undefined)\n ?.promptCache === true;\n\n const log: LogFn = (level, message, data) => {\n emitAgentLog(runnableConfig, level, 'summarize', message, data, {\n runId: graph.runId,\n agentId: request.agentId,\n });\n };\n\n log('debug', 'Summarization starting', {\n messagesToRefineCount: messagesToRefine.length,\n hasPriorSummary: (agentContext.getSummaryText()?.trim() ?? '') !== '',\n summaryVersion: agentContext.summaryVersion + 1,\n isSelfSummarize: isSelfSummarizeModel,\n hasPromptCache,\n provider: clientConfig.provider,\n });\n\n const summarizeConfig: RunnableConfig | undefined = config\n ? {\n ...config,\n metadata: {\n ...config.metadata,\n agent_id: request.agentId,\n summarization_provider: clientConfig.provider,\n summarization_model: clientConfig.modelName,\n /**\n * Per-call model attribution for usage consumers (the subagent\n * usage-capture handler): the summarizer's model can differ from\n * the agent's primary, and providers that emit no `ls_model_name`\n * would otherwise be billed against the primary config's model.\n * Omitted for self-summarize (no explicit model — the primary\n * config fallback is then correct). `tryFallbackProviders`\n * overrides this per fallback attempt; `INVOKED_PROVIDER` is\n * stamped by `attemptInvoke` itself.\n */\n ...(clientConfig.modelName != null && clientConfig.modelName !== ''\n ? { [Constants.INVOKED_MODEL]: clientConfig.modelName }\n : {}),\n },\n }\n : undefined;\n\n const { text: rawText, usage: summaryUsage } =\n await executeSummarizationWithFallback({\n agentContext,\n messages: messagesToRefine,\n clientConfig,\n summarizeConfig,\n stepId,\n usePromptCache: isSelfSummarizeModel && hasPromptCache,\n log,\n });\n\n if (!rawText) {\n agentContext.markSummarizationTriggered(0);\n if (runnableConfig) {\n await safeDispatchCustomEvent(\n GraphEvents.ON_SUMMARIZE_COMPLETE,\n {\n id: stepId,\n agentId: request.agentId,\n error: 'Summarization produced empty output',\n } satisfies t.SummarizeCompleteEvent,\n runnableConfig\n );\n }\n return { summarizationRequest: undefined };\n }\n\n const summaryText = enrichSummary(rawText, messagesToRefine);\n\n const tokenCount = computeSummaryTokenCount(\n summaryText,\n summaryUsage,\n agentContext.tokenCounter\n );\n\n agentContext.setSummary(summaryText, tokenCount);\n\n log('info', 'Summary persisted');\n log('debug', 'Summary details', {\n summaryTokens: tokenCount,\n textLength: summaryText.length,\n messagesCompacted: messagesToRefine.length,\n summaryVersion: agentContext.summaryVersion,\n ...(summaryUsage != null\n ? {\n input_tokens: summaryUsage.input_tokens,\n output_tokens: summaryUsage.output_tokens,\n cache_read: summaryUsage.input_token_details?.cache_read,\n cache_creation: summaryUsage.input_token_details?.cache_creation,\n }\n : {}),\n });\n\n const summaryBlock = buildSummaryBlock({\n summaryText,\n tokenCount,\n stepId,\n stepIndex: runStep.index,\n modelName: clientConfig.modelName,\n provider: clientConfig.provider,\n summaryVersion: agentContext.summaryVersion,\n });\n\n await dispatchCompletionEvents({\n graph,\n runnableConfig,\n stepId,\n summaryBlock,\n agentContext,\n runStep,\n summaryUsage,\n agentId: request.agentId,\n messagesAfterCount: messagesToRetain.length,\n });\n\n /**\n * `dispatchCompletionEvents` calls `rebuildTokenMapAfterSummarization({})`\n * which resets the dedupe baseline to 0 — correct under the legacy\n * \"remove-all only\" shape where no messages survived, but stale once\n * the recency window keeps a tail. Realign the baseline to the\n * surviving tail length so a subsequent prune cycle on the unchanged\n * tail short-circuits via `shouldSkipSummarization` instead of\n * looping back into another summarize call.\n */\n agentContext.markSummarizationTriggered(messagesToRetain.length);\n\n /**\n * Carry forward the original-content entries that correspond to the\n * retained tail, reindexed for the post-removeAll state where tail\n * messages start at index 0. Without this, a future summarization\n * that pulls these tail messages into its head would only see the\n * masked stubs (since `setSummary` clears `pruneMessages`, and the\n * fresh pruner at the next turn has no record of prior masks).\n * Entries for indices < `tailStartIndex` belong to messages we just\n * summarized — they are no longer reachable so they are dropped.\n */\n if (originalPending != null && originalPending.size > 0) {\n const tailPending = new Map<number, string>();\n for (const [idx, content] of originalPending) {\n if (idx >= tailStartIndex) {\n tailPending.set(idx - tailStartIndex, content);\n }\n }\n agentContext.pendingOriginalToolContent =\n tailPending.size > 0 ? tailPending : undefined;\n } else {\n agentContext.pendingOriginalToolContent = undefined;\n }\n\n return {\n summarizationRequest: undefined,\n messages:\n messagesToRetain.length > 0\n ? [createRemoveAllMessage(), ...messagesToRetain]\n : [createRemoveAllMessage()],\n };\n };\n}\n\n/** Extracts text from an LLM response, skipping reasoning/thinking blocks. */\nfunction extractResponseText(response: { content: string | object }): string {\n const { content } = response;\n if (typeof content === 'string') {\n return content.trim();\n }\n if (!Array.isArray(content)) {\n return '';\n }\n const parts: string[] = [];\n for (const block of content) {\n if (typeof block === 'string') {\n parts.push(block);\n continue;\n }\n if (block == null || typeof block !== 'object') {\n continue;\n }\n const rec = block as Record<string, unknown>;\n if (\n rec.type === ContentTypes.THINKING ||\n rec.type === ContentTypes.REASONING_CONTENT ||\n rec.type === 'redacted_thinking'\n ) {\n continue;\n }\n if (rec.type === 'text' && typeof rec.text === 'string') {\n parts.push(rec.text);\n }\n }\n return parts.join('').trim();\n}\n\nfunction buildSummarizationInstruction(\n promptText: string,\n updatePromptText: string | undefined,\n priorSummaryText: string\n): string {\n const effectivePrompt = priorSummaryText\n ? (updatePromptText ?? promptText)\n : promptText;\n const parts = [effectivePrompt];\n if (priorSummaryText) {\n parts.push(\n `\\n\\n<previous-summary>\\n${priorSummaryText}\\n</previous-summary>`\n );\n }\n return parts.join('');\n}\n\n/** Creates an `onChunk` callback that dispatches `ON_SUMMARIZE_DELTA` events for streaming. */\nfunction createSummarizationChunkHandler({\n stepId,\n config,\n provider,\n reasoningKey = 'reasoning_content',\n}: {\n stepId?: string;\n config?: RunnableConfig;\n provider?: Providers;\n reasoningKey?: 'reasoning_content' | 'reasoning';\n}): OnChunk | undefined {\n if (stepId == null || stepId === '' || !config) {\n return undefined;\n }\n return (chunk) => {\n const chunkAny = chunk as Parameters<typeof getChunkContent>[0]['chunk'];\n const raw = getChunkContent({ chunk: chunkAny, provider, reasoningKey });\n if (raw == null || (typeof raw === 'string' && !raw)) {\n return;\n }\n const contentBlocks: t.MessageContentComplex[] =\n typeof raw === 'string'\n ? [{ type: ContentTypes.TEXT, text: raw } as t.MessageContentComplex]\n : raw;\n\n void safeDispatchCustomEvent(\n GraphEvents.ON_SUMMARIZE_DELTA,\n {\n id: stepId,\n delta: {\n summary: {\n type: ContentTypes.SUMMARY,\n content: contentBlocks,\n provider: String(config.metadata?.summarization_provider ?? ''),\n model: String(config.metadata?.summarization_model ?? ''),\n },\n },\n } satisfies t.SummarizeDeltaEvent,\n config\n );\n };\n}\n\nfunction traceConfig(\n config: RunnableConfig | undefined,\n stage: string\n): RunnableConfig | undefined {\n if (!config) {\n return undefined;\n }\n return {\n ...config,\n runName: `summarization:${stage}`,\n metadata: { ...config.metadata, summarization: true, stage },\n };\n}\n\n/**\n * Cache-friendly compaction: sends raw conversation messages with the\n * summarization instruction appended as the final HumanMessage.\n * Providers with prompt caching get a cache hit on the system prompt +\n * tool definitions prefix.\n */\nasync function summarizeWithCacheHit({\n model,\n messages,\n promptText,\n updatePromptText,\n priorSummaryText,\n config,\n stepId,\n provider,\n reasoningKey,\n usePromptCache,\n promptCacheTtl,\n log,\n}: {\n model: t.ChatModel;\n messages: BaseMessage[];\n promptText: string;\n updatePromptText?: string;\n priorSummaryText: string;\n config?: RunnableConfig;\n stepId?: string;\n provider: Providers;\n reasoningKey?: 'reasoning_content' | 'reasoning';\n usePromptCache?: boolean;\n promptCacheTtl?: PromptCacheTtl;\n log?: LogFn;\n}): Promise<{ text: string; usage?: Partial<UsageMetadata> }> {\n const instruction = buildSummarizationInstruction(\n promptText,\n updatePromptText,\n priorSummaryText\n );\n\n const fullMessages = [...messages, new HumanMessage(instruction)];\n const invokeMessages =\n usePromptCache === true\n ? addTailCacheControl(fullMessages, promptCacheTtl)\n : fullMessages;\n\n const result = await attemptInvoke(\n {\n model,\n messages: invokeMessages,\n provider,\n onChunk: createSummarizationChunkHandler({\n stepId,\n config: traceConfig(config, 'cache_hit_compaction'),\n provider,\n reasoningKey,\n }),\n },\n traceConfig(config, 'cache_hit_compaction')\n );\n\n const responseMsg = result.messages?.[0];\n const text = responseMsg\n ? extractResponseText(responseMsg as { content: string | object })\n : '';\n let usage: Partial<UsageMetadata> | undefined;\n let usageSource = 'none';\n if (\n responseMsg != null &&\n 'usage_metadata' in responseMsg &&\n responseMsg.usage_metadata != null\n ) {\n usage = responseMsg.usage_metadata as Partial<UsageMetadata>;\n usageSource = 'usage_metadata';\n } else if (responseMsg != null) {\n const respMeta = responseMsg.response_metadata as\n | Record<string, unknown>\n | undefined;\n const raw = (respMeta?.metadata as Record<string, unknown> | undefined)\n ?.usage as Record<string, unknown> | undefined;\n if (raw != null) {\n usage = {\n input_tokens: Number(raw.inputTokens) || undefined,\n output_tokens: Number(raw.outputTokens) || undefined,\n } as Partial<UsageMetadata>;\n usageSource = 'response_metadata';\n }\n }\n const cacheDetails = (\n usage as\n | {\n input_token_details?: {\n cache_read?: number;\n cache_creation?: number;\n };\n }\n | undefined\n )?.input_token_details;\n log?.('debug', 'Summarization LLM usage', {\n source: usageSource,\n input_tokens: usage?.input_tokens,\n output_tokens: usage?.output_tokens,\n ...(cacheDetails?.cache_read != null || cacheDetails?.cache_creation != null\n ? {\n 'input_token_details.cache_read': cacheDetails.cache_read,\n 'input_token_details.cache_creation': cacheDetails.cache_creation,\n }\n : {}),\n });\n return { text, usage };\n}\n"],"mappings":"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"}
@@ -19,11 +19,11 @@ CONSTRAINTS:
19
19
  - Do not guess file paths. Use paths from the skill documentation or tool output.`;
20
20
  const ReadFileToolSchema = {
21
21
  type: "object",
22
- properties: { file_path: {
22
+ properties: { path: {
23
23
  type: "string",
24
24
  description: "Path to the file. For skill files: \"{skillName}/{path}\" (e.g. \"pdf-analyzer/src/utils.py\"). For code execution output: the path as returned by the execution tool."
25
25
  } },
26
- required: ["file_path"]
26
+ required: ["path"]
27
27
  };
28
28
  const ReadFileToolDefinition = {
29
29
  name: ReadFileToolName,
@@ -1 +1 @@
1
- {"version":3,"file":"ReadFile.cjs","names":[],"sources":["../../../src/tools/ReadFile.ts"],"sourcesContent":["// src/tools/ReadFile.ts\nimport { Constants } from '@/common';\n\nexport const ReadFileToolName = Constants.READ_FILE;\n\nexport const ReadFileToolDescription = `Read the contents of a file. Returns text content with line numbers for easy reference.\n\nFor skill files, use the path format: {skillName}/{filePath} (e.g. \"pdf-analyzer/src/utils.py\", \"code-review/SKILL.md\").\n\nBEHAVIOR:\n- Text files: returned with numbered lines.\n- Images (png, jpeg, gif, webp): returned as visual content the model can see.\n- PDFs: returned as document content.\n- Other binary files: metadata returned with a note to use bash for processing.\n- Large files (>256KB text, >10MB binary): metadata only.\n- SKILL.md: returns the skill's instructions directly.\n\nCONSTRAINTS:\n- Only files from invoked skills or code execution output are accessible.\n- Do not guess file paths. Use paths from the skill documentation or tool output.`;\n\nexport const ReadFileToolSchema = {\n type: 'object',\n properties: {\n file_path: {\n type: 'string',\n description:\n 'Path to the file. For skill files: \"{skillName}/{path}\" (e.g. \"pdf-analyzer/src/utils.py\"). For code execution output: the path as returned by the execution tool.',\n },\n },\n required: ['file_path'],\n} as const;\n\nexport const ReadFileToolDefinition = {\n name: ReadFileToolName,\n description: ReadFileToolDescription,\n parameters: ReadFileToolSchema,\n responseFormat: 'content_and_artifact' as const,\n} as const;\n"],"mappings":";;;AAGA,MAAa,mBAAA;AAEb,MAAa,0BAA0B;;;;;;;;;;;;;;;AAgBvC,MAAa,qBAAqB;CAChC,MAAM;CACN,YAAY,EACV,WAAW;EACT,MAAM;EACN,aACE;CACJ,EACF;CACA,UAAU,CAAC,WAAW;AACxB;AAEA,MAAa,yBAAyB;CACpC,MAAM;CACN,aAAa;CACb,YAAY;CACZ,gBAAgB;AAClB"}
1
+ {"version":3,"file":"ReadFile.cjs","names":[],"sources":["../../../src/tools/ReadFile.ts"],"sourcesContent":["// src/tools/ReadFile.ts\nimport { Constants } from '@/common';\n\nexport const ReadFileToolName = Constants.READ_FILE;\n\nexport const ReadFileToolDescription = `Read the contents of a file. Returns text content with line numbers for easy reference.\n\nFor skill files, use the path format: {skillName}/{filePath} (e.g. \"pdf-analyzer/src/utils.py\", \"code-review/SKILL.md\").\n\nBEHAVIOR:\n- Text files: returned with numbered lines.\n- Images (png, jpeg, gif, webp): returned as visual content the model can see.\n- PDFs: returned as document content.\n- Other binary files: metadata returned with a note to use bash for processing.\n- Large files (>256KB text, >10MB binary): metadata only.\n- SKILL.md: returns the skill's instructions directly.\n\nCONSTRAINTS:\n- Only files from invoked skills or code execution output are accessible.\n- Do not guess file paths. Use paths from the skill documentation or tool output.`;\n\nexport const ReadFileToolSchema = {\n type: 'object',\n properties: {\n path: {\n type: 'string',\n description:\n 'Path to the file. For skill files: \"{skillName}/{path}\" (e.g. \"pdf-analyzer/src/utils.py\"). For code execution output: the path as returned by the execution tool.',\n },\n },\n required: ['path'],\n} as const;\n\nexport const ReadFileToolDefinition = {\n name: ReadFileToolName,\n description: ReadFileToolDescription,\n parameters: ReadFileToolSchema,\n responseFormat: 'content_and_artifact' as const,\n} as const;\n"],"mappings":";;;AAGA,MAAa,mBAAA;AAEb,MAAa,0BAA0B;;;;;;;;;;;;;;;AAgBvC,MAAa,qBAAqB;CAChC,MAAM;CACN,YAAY,EACV,MAAM;EACJ,MAAM;EACN,aACE;CACJ,EACF;CACA,UAAU,CAAC,MAAM;AACnB;AAEA,MAAa,yBAAyB;CACpC,MAAM;CACN,aAAa;CACb,YAAY;CACZ,gBAAgB;AAClB"}
@@ -360,30 +360,30 @@ async def execute_code(lang, code, args=None):
360
360
  finally:
361
361
  shutil.rmtree(temp_dir, ignore_errors=True)
362
362
 
363
- async def read_file(file_path, offset=None, limit=None):
364
- resolved = _resolve(file_path)
363
+ async def read_file(path, offset=None, limit=None):
364
+ resolved = _resolve(path)
365
365
  with open(resolved, encoding="utf-8") as handle:
366
366
  return _line_window(handle.read(), offset, limit)
367
367
 
368
- async def write_file(file_path, content):
368
+ async def write_file(path, content):
369
369
  _assert_writable("write_file")
370
- resolved = _resolve(file_path)
370
+ resolved = _resolve(path)
371
371
  os.makedirs(os.path.dirname(resolved), exist_ok=True)
372
372
  existed = os.path.exists(resolved)
373
373
  with open(resolved, "w", encoding="utf-8") as handle:
374
374
  handle.write(content)
375
375
  return f"{'Overwrote' if existed else 'Created'} {resolved} ({len(content)} chars)."
376
376
 
377
- async def edit_file(file_path, old_text=None, new_text=None, edits=None):
377
+ async def edit_file(path, old_text=None, new_text=None, edits=None):
378
378
  _assert_writable("edit_file")
379
- resolved = _resolve(file_path)
379
+ resolved = _resolve(path)
380
380
  edits = edits or [{"old_text": old_text, "new_text": new_text}]
381
381
  content = open(resolved, encoding="utf-8").read()
382
382
  for edit in edits:
383
383
  old = edit.get("old_text") or ""
384
384
  new = edit.get("new_text") or ""
385
385
  if content.count(old) != 1:
386
- raise ValueError(f"Could not locate old_text exactly once in {file_path}")
386
+ raise ValueError(f"Could not locate old_text exactly once in {path}")
387
387
  content = content.replace(old, new, 1)
388
388
  open(resolved, "w", encoding="utf-8").write(content)
389
389
  return f"Applied {len(edits)} edit(s) to {resolved}."
@@ -759,13 +759,13 @@ async function execute_code(payload) {
759
759
  }
760
760
 
761
761
  async function read_file(payload) {
762
- const resolved = resolvePath(payload.file_path);
762
+ const resolved = resolvePath(payload.path);
763
763
  return lineWindow(await fsp.readFile(resolved, "utf8"), payload.offset, payload.limit);
764
764
  }
765
765
 
766
766
  async function write_file(payload) {
767
767
  assertWritable("write_file");
768
- const resolved = resolvePath(payload.file_path);
768
+ const resolved = resolvePath(payload.path);
769
769
  await fsp.mkdir(path.dirname(resolved), { recursive: true });
770
770
  const existed = fs.existsSync(resolved);
771
771
  await fsp.writeFile(resolved, payload.content, "utf8");
@@ -774,14 +774,14 @@ async function write_file(payload) {
774
774
 
775
775
  async function edit_file(payload) {
776
776
  assertWritable("edit_file");
777
- const resolved = resolvePath(payload.file_path);
777
+ const resolved = resolvePath(payload.path);
778
778
  const edits = payload.edits || [{ old_text: payload.old_text, new_text: payload.new_text }];
779
779
  let content = await fsp.readFile(resolved, "utf8");
780
780
  for (const edit of edits) {
781
781
  const oldText = edit.old_text || "";
782
782
  const newText = edit.new_text || "";
783
783
  if (oldText === "" || content.split(oldText).length - 1 !== 1) {
784
- throw new Error("Could not locate old_text exactly once in " + payload.file_path);
784
+ throw new Error("Could not locate old_text exactly once in " + payload.path);
785
785
  }
786
786
  content = content.replace(oldText, newText);
787
787
  }