@librechat/agents 3.3.2 → 3.3.4
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.
- package/dist/cjs/agents/AgentContext.cjs +2 -1
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +342 -109
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/graphs/MultiAgentGraph.cjs +3 -1
- package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
- package/dist/cjs/langfuse.cjs +6 -0
- package/dist/cjs/langfuse.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +79 -6
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/utils/message_inputs.cjs +37 -10
- package/dist/cjs/llm/bedrock/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/llm/google/utils/common.cjs +19 -7
- package/dist/cjs/llm/google/utils/common.cjs.map +1 -1
- package/dist/cjs/llm/invoke.cjs +64 -3
- package/dist/cjs/llm/invoke.cjs.map +1 -1
- package/dist/cjs/llm/openai/index.cjs +41 -5
- package/dist/cjs/llm/openai/index.cjs.map +1 -1
- package/dist/cjs/llm/openai/utils/index.cjs +25 -4
- package/dist/cjs/llm/openai/utils/index.cjs.map +1 -1
- package/dist/cjs/llm/openrouter/index.cjs +4 -1
- package/dist/cjs/llm/openrouter/index.cjs.map +1 -1
- package/dist/cjs/main.cjs +19 -2
- package/dist/cjs/messages/cache.cjs +54 -33
- package/dist/cjs/messages/cache.cjs.map +1 -1
- package/dist/cjs/messages/contextPruning.cjs +17 -43
- package/dist/cjs/messages/contextPruning.cjs.map +1 -1
- package/dist/cjs/messages/core.cjs +315 -23
- package/dist/cjs/messages/core.cjs.map +1 -1
- package/dist/cjs/messages/format.cjs +218 -60
- package/dist/cjs/messages/format.cjs.map +1 -1
- package/dist/cjs/messages/index.cjs +1 -1
- package/dist/cjs/messages/prune.cjs +503 -127
- package/dist/cjs/messages/prune.cjs.map +1 -1
- package/dist/cjs/stream.cjs +7 -3
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/summarization/node.cjs +28 -14
- package/dist/cjs/summarization/node.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +54 -41
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/toolOutputReferences.cjs +6 -0
- package/dist/cjs/tools/toolOutputReferences.cjs.map +1 -1
- package/dist/cjs/utils/index.cjs +1 -1
- package/dist/cjs/utils/llm.cjs +1 -1
- package/dist/cjs/utils/llm.cjs.map +1 -1
- package/dist/cjs/utils/tokens.cjs +307 -20
- package/dist/cjs/utils/tokens.cjs.map +1 -1
- package/dist/cjs/utils/toolContent.cjs +1514 -0
- package/dist/cjs/utils/toolContent.cjs.map +1 -0
- package/dist/esm/agents/AgentContext.mjs +2 -1
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +349 -116
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/graphs/MultiAgentGraph.mjs +3 -1
- package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
- package/dist/esm/langfuse.mjs +6 -0
- package/dist/esm/langfuse.mjs.map +1 -1
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs +79 -6
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/llm/bedrock/utils/message_inputs.mjs +37 -10
- package/dist/esm/llm/bedrock/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/llm/google/utils/common.mjs +19 -7
- package/dist/esm/llm/google/utils/common.mjs.map +1 -1
- package/dist/esm/llm/invoke.mjs +65 -5
- package/dist/esm/llm/invoke.mjs.map +1 -1
- package/dist/esm/llm/openai/index.mjs +41 -5
- package/dist/esm/llm/openai/index.mjs.map +1 -1
- package/dist/esm/llm/openai/utils/index.mjs +25 -4
- package/dist/esm/llm/openai/utils/index.mjs.map +1 -1
- package/dist/esm/llm/openrouter/index.mjs +4 -1
- package/dist/esm/llm/openrouter/index.mjs.map +1 -1
- package/dist/esm/main.mjs +7 -7
- package/dist/esm/messages/cache.mjs +55 -34
- package/dist/esm/messages/cache.mjs.map +1 -1
- package/dist/esm/messages/contextPruning.mjs +17 -43
- package/dist/esm/messages/contextPruning.mjs.map +1 -1
- package/dist/esm/messages/core.mjs +305 -24
- package/dist/esm/messages/core.mjs.map +1 -1
- package/dist/esm/messages/format.mjs +218 -61
- package/dist/esm/messages/format.mjs.map +1 -1
- package/dist/esm/messages/index.mjs +1 -1
- package/dist/esm/messages/prune.mjs +500 -128
- package/dist/esm/messages/prune.mjs.map +1 -1
- package/dist/esm/stream.mjs +7 -3
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/summarization/node.mjs +28 -14
- package/dist/esm/summarization/node.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +54 -41
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/toolOutputReferences.mjs +6 -0
- package/dist/esm/tools/toolOutputReferences.mjs.map +1 -1
- package/dist/esm/utils/index.mjs +1 -1
- package/dist/esm/utils/llm.mjs +1 -1
- package/dist/esm/utils/llm.mjs.map +1 -1
- package/dist/esm/utils/tokens.mjs +307 -21
- package/dist/esm/utils/tokens.mjs.map +1 -1
- package/dist/esm/utils/toolContent.mjs +1503 -0
- package/dist/esm/utils/toolContent.mjs.map +1 -0
- package/dist/types/graphs/Graph.d.ts +1 -1
- package/dist/types/llm/invoke.d.ts +27 -1
- package/dist/types/llm/openai/index.d.ts +4 -0
- package/dist/types/llm/openai/utils/index.d.ts +2 -0
- package/dist/types/llm/openrouter/index.d.ts +5 -0
- package/dist/types/messages/contextPruning.d.ts +2 -1
- package/dist/types/messages/core.d.ts +36 -0
- package/dist/types/messages/format.d.ts +6 -0
- package/dist/types/messages/prune.d.ts +16 -12
- package/dist/types/types/stream.d.ts +1 -1
- package/dist/types/utils/tokens.d.ts +7 -0
- package/dist/types/utils/toolContent.d.ts +107 -0
- package/package.json +13 -9
- package/src/__tests__/stream.eagerEventExecution.test.ts +74 -0
- package/src/agents/AgentContext.ts +1 -0
- package/src/graphs/Graph.ts +698 -200
- package/src/graphs/MultiAgentGraph.ts +6 -1
- package/src/graphs/__tests__/Graph.contextOverflow.test.ts +1128 -4
- package/src/langfuse.ts +15 -0
- package/src/llm/anthropic/utils/message_inputs.ts +158 -12
- package/src/llm/anthropic/utils/tool-id-normalization.test.ts +109 -0
- package/src/llm/bedrock/utils/message_inputs.test.ts +49 -1
- package/src/llm/bedrock/utils/message_inputs.ts +86 -13
- package/src/llm/google/utils/common.test.ts +75 -9
- package/src/llm/google/utils/common.ts +34 -3
- package/src/llm/invoke.test.ts +488 -0
- package/src/llm/invoke.ts +196 -2
- package/src/llm/openai/index.ts +106 -6
- package/src/llm/openai/utils/index.ts +109 -53
- package/src/llm/openai/utils/messages.test.ts +330 -1
- package/src/llm/openrouter/index.ts +12 -2
- package/src/llm/openrouter/reasoning.test.ts +314 -0
- package/src/messages/__tests__/observationMasking.test.ts +93 -2
- package/src/messages/cache.tail.test.ts +193 -0
- package/src/messages/cache.test.ts +113 -0
- package/src/messages/cache.ts +92 -60
- package/src/messages/contextPruning.test.ts +184 -0
- package/src/messages/contextPruning.ts +49 -42
- package/src/messages/core.ts +653 -40
- package/src/messages/ensureThinkingBlock.test.ts +49 -1
- package/src/messages/foldToollessToolBlocks.test.ts +163 -5
- package/src/messages/format.ts +363 -91
- package/src/messages/formatAgentMessages.test.ts +604 -14
- package/src/messages/formatAgentMessages.tools.test.ts +103 -0
- package/src/messages/prune.ts +996 -183
- package/src/specs/langfuse-callbacks.test.ts +203 -1
- package/src/specs/prune.test.ts +1083 -6
- package/src/specs/summarization-unit.test.ts +105 -0
- package/src/specs/tokens.test.ts +609 -32
- package/src/stream.ts +23 -15
- package/src/summarization/__tests__/node.test.ts +77 -0
- package/src/summarization/node.ts +46 -18
- package/src/tools/ToolNode.ts +147 -68
- package/src/tools/__tests__/ToolNode.onResultCompletion.test.ts +148 -1
- package/src/tools/__tests__/ToolNode.outputReferences.test.ts +112 -3
- package/src/tools/__tests__/annotateMessagesForLLM.test.ts +32 -0
- package/src/tools/__tests__/directToolHooks.test.ts +255 -7
- package/src/tools/toolOutputReferences.ts +6 -0
- package/src/types/stream.ts +1 -1
- package/src/utils/llm.test.ts +18 -0
- package/src/utils/llm.ts +4 -1
- package/src/utils/tokens.ts +586 -31
- package/src/utils/toolContent.test.ts +1432 -0
- package/src/utils/toolContent.ts +2707 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache.mjs","names":[],"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 * Whether a Bedrock model id is an Anthropic Claude model. Claude is the only\n * Bedrock family that accepts the explicit cache features below; Amazon Nova and\n * others reject them. Matches both the `anthropic.` and cross-region\n * (`us.anthropic.`) / bare `claude` forms.\n */\nfunction isBedrockAnthropicModel(model: string | undefined | null): boolean {\n return typeof model === 'string' && /claude|anthropic/i.test(model);\n}\n\n/**\n * A `cachePoint` under `toolConfig.tools` is only accepted on Anthropic Claude\n * models. Amazon Nova rejects it outright — `Malformed input request:\n * #/toolConfig/tools/0: extraneous key [cachePoint] is not permitted` — even\n * though it accepts `cachePoint` in `system` and `messages` (verified live:\n * Nova returns HTTP 200 with real `cacheWriteInputTokens` for both). Per the AWS\n * prompt-caching docs the support table lists `tools` for Claude only.\n *\n * Gate ONLY the tool checkpoint on this, so a `promptCache: true` config on Nova\n * keeps its valid message/system caching instead of either 400-ing (tool point)\n * or losing caching entirely.\n *\n * @see https://docs.aws.amazon.com/bedrock/latest/userguide/prompt-caching.html\n * @see https://github.com/danny-avila/LibreChat/issues/13838\n */\nexport function supportsBedrockToolCache(\n model: string | undefined | null\n): boolean {\n return isBedrockAnthropicModel(model);\n}\n\n/**\n * Resolve the Bedrock `cachePoint` TTL for a model. The extended `1h` TTL is\n * Anthropic-only: non-Claude models (e.g. Nova) hard-reject it with\n * `Extended TTL prompt caching is only supported for Anthropic models`, so they\n * are clamped to `5m` even when `1h` is configured (verified live). An omitted\n * model defaults to a Claude model, and Claude downgrades an unsupported `1h` to\n * `5m` server-side, so the `1h` default is safe for the Claude/undefined paths.\n *\n * @see https://github.com/danny-avila/LibreChat/issues/13838\n */\nexport function resolveBedrockPromptCacheTtl(\n ttl: PromptCacheTtl | undefined,\n model: string | undefined | null\n): PromptCacheTtl {\n if (model != null && !isBedrockAnthropicModel(model)) {\n return '5m';\n }\n return resolvePromptCacheTtl(ttl);\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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"}
|
|
1
|
+
{"version":3,"file":"cache.mjs","names":[],"sources":["../../../src/messages/cache.ts"],"sourcesContent":["import { isProxy } from 'node:util/types';\nimport { BaseMessage, MessageContentComplex } from '@langchain/core/messages';\nimport type Anthropic from '@anthropic-ai/sdk';\nimport type { AnthropicMessage } from '@/types/messages';\nimport { hasComputerCallOutputMarker } from '@/utils/toolContent';\nimport { toLangChainContent } from './langchain';\nimport { ContentTypes } from '@/common/enum';\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 * Whether a Bedrock model id is an Anthropic Claude model. Claude is the only\n * Bedrock family that accepts the explicit cache features below; Amazon Nova and\n * others reject them. Matches both the `anthropic.` and cross-region\n * (`us.anthropic.`) / bare `claude` forms.\n */\nfunction isBedrockAnthropicModel(model: string | undefined | null): boolean {\n return typeof model === 'string' && /claude|anthropic/i.test(model);\n}\n\n/**\n * A `cachePoint` under `toolConfig.tools` is only accepted on Anthropic Claude\n * models. Amazon Nova rejects it outright — `Malformed input request:\n * #/toolConfig/tools/0: extraneous key [cachePoint] is not permitted` — even\n * though it accepts `cachePoint` in `system` and `messages` (verified live:\n * Nova returns HTTP 200 with real `cacheWriteInputTokens` for both). Per the AWS\n * prompt-caching docs the support table lists `tools` for Claude only.\n *\n * Gate ONLY the tool checkpoint on this, so a `promptCache: true` config on Nova\n * keeps its valid message/system caching instead of either 400-ing (tool point)\n * or losing caching entirely.\n *\n * @see https://docs.aws.amazon.com/bedrock/latest/userguide/prompt-caching.html\n * @see https://github.com/danny-avila/LibreChat/issues/13838\n */\nexport function supportsBedrockToolCache(\n model: string | undefined | null\n): boolean {\n return isBedrockAnthropicModel(model);\n}\n\n/**\n * Resolve the Bedrock `cachePoint` TTL for a model. The extended `1h` TTL is\n * Anthropic-only: non-Claude models (e.g. Nova) hard-reject it with\n * `Extended TTL prompt caching is only supported for Anthropic models`, so they\n * are clamped to `5m` even when `1h` is configured (verified live). An omitted\n * model defaults to a Claude model, and Claude downgrades an unsupported `1h` to\n * `5m` server-side, so the `1h` default is safe for the Claude/undefined paths.\n *\n * @see https://github.com/danny-avila/LibreChat/issues/13838\n */\nexport function resolveBedrockPromptCacheTtl(\n ttl: PromptCacheTtl | undefined,\n model: string | undefined | null\n): PromptCacheTtl {\n if (model != null && !isBedrockAnthropicModel(model)) {\n return '5m';\n }\n return resolvePromptCacheTtl(ttl);\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 omittedProperty?: string\n): T {\n if (typeof content === 'string') {\n return content;\n }\n if (Array.isArray(content)) {\n return content.map((block) => {\n if (typeof block !== 'object' || block == null || isProxy(block)) {\n return block;\n }\n try {\n const descriptors = Object.getOwnPropertyDescriptors(block);\n if (omittedProperty != null) {\n delete descriptors[omittedProperty];\n }\n return Object.create(\n Object.getPrototypeOf(block),\n descriptors\n ) as MessageContentComplex;\n } catch {\n return block;\n }\n }) 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 descriptors = Object.getOwnPropertyDescriptors(message) as Record<\n PropertyKey,\n PropertyDescriptor | undefined\n >;\n const contentDescriptor = descriptors.content;\n descriptors.content = {\n configurable: contentDescriptor?.configurable ?? true,\n enumerable: contentDescriptor?.enumerable ?? true,\n value: toLangChainContent(content),\n writable: contentDescriptor?.writable ?? true,\n };\n const lcKwargs = descriptors.lc_kwargs;\n if (\n lcKwargs != null &&\n 'value' in lcKwargs &&\n typeof lcKwargs.value === 'object' &&\n lcKwargs.value != null\n ) {\n descriptors.lc_kwargs = {\n ...lcKwargs,\n value: {\n ...(lcKwargs.value as Record<string, unknown>),\n content: toLangChainContent(content),\n },\n };\n }\n return Object.create(\n Object.getPrototypeOf(message),\n descriptors as PropertyDescriptorMap\n ) as T;\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 descriptor = Object.getOwnPropertyDescriptor(block, 'cachePoint');\n const existing =\n descriptor != null && 'value' in descriptor\n ? (descriptor.value as { ttl?: unknown } | undefined)\n : undefined;\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: unknown): boolean {\n if (typeof block !== 'object' || block == null || isProxy(block)) {\n return false;\n }\n try {\n const cachePoint = Object.getOwnPropertyDescriptor(block, 'cachePoint');\n const type = Object.getOwnPropertyDescriptor(block, 'type');\n return cachePoint?.enumerable === true && type == null;\n } catch {\n return false;\n }\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 isComputerOutput =\n originalMessage instanceof BaseMessage &&\n hasComputerCallOutputMarker(originalMessage);\n const canPlaceMarker =\n !markerPlaced &&\n !isSyntheticMetaMessage(originalMessage) &&\n !isComputerOutput;\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 const block = content[i];\n if (typeof block !== 'object' || block == null || isProxy(block)) {\n continue;\n }\n try {\n const cacheControl = Object.getOwnPropertyDescriptor(\n block,\n 'cache_control'\n );\n if (cacheControl?.enumerable === true) {\n return true;\n }\n } catch {\n continue;\n }\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, 'cache_control');\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 isComputerOutput =\n originalMessage instanceof BaseMessage &&\n hasComputerCallOutputMarker(originalMessage);\n const canPlaceCachePoint =\n !cachePointPlaced &&\n !isEmptyString &&\n !isSyntheticMetaMessage(originalMessage) &&\n !isComputerOutput &&\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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"}
|
|
@@ -1,29 +1,7 @@
|
|
|
1
|
+
import { cloneToolMessageWithContent, compactToolContent, getToolContentCharLength, isAtomicToolContentBlock, isComputerCallOutputMessage, serializeToolContentBounded } from "../utils/toolContent.mjs";
|
|
1
2
|
import { resolveContextPruningSettings } from "./contextPruningSettings.mjs";
|
|
2
|
-
import { ToolMessage } from "@langchain/core/messages";
|
|
3
3
|
//#region src/messages/contextPruning.ts
|
|
4
4
|
/**
|
|
5
|
-
* Position-based context pruning for tool results.
|
|
6
|
-
*
|
|
7
|
-
* Uses position-based age: the distance of a message
|
|
8
|
-
* from the conversation end as a fraction of total messages.
|
|
9
|
-
*
|
|
10
|
-
* Two degradation levels:
|
|
11
|
-
* - Soft-trim: Keep head + tail of tool result content, drop middle.
|
|
12
|
-
* - Hard-clear: Replace entire content with a placeholder.
|
|
13
|
-
*
|
|
14
|
-
* Messages in the "protected zone" (recent assistant turns, system/pre-first-human
|
|
15
|
-
* messages, and messages with image content) are never pruned.
|
|
16
|
-
*/
|
|
17
|
-
/**
|
|
18
|
-
* Checks if a message contains image content blocks.
|
|
19
|
-
* Messages with images are skipped by position-based content degradation
|
|
20
|
-
* because images cannot be meaningfully soft-trimmed or replaced with placeholders.
|
|
21
|
-
*/
|
|
22
|
-
function hasImageContent(message) {
|
|
23
|
-
if (!Array.isArray(message.content)) return false;
|
|
24
|
-
return message.content.some((block) => typeof block === "object" && "type" in block && (block.type === "image_url" || block.type === "image"));
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
5
|
* Applies head+tail soft-trim to tool result content.
|
|
28
6
|
*/
|
|
29
7
|
function softTrimContent(content, settings) {
|
|
@@ -78,34 +56,30 @@ function applyContextPruning(params) {
|
|
|
78
56
|
for (let i = 0; i < totalMessages; i++) {
|
|
79
57
|
const message = messages[i];
|
|
80
58
|
if (message.getType() !== "tool") continue;
|
|
59
|
+
if (isComputerCallOutputMessage(message)) continue;
|
|
81
60
|
if (protectedIndices.has(i)) continue;
|
|
82
|
-
if (hasImageContent(message)) continue;
|
|
83
61
|
const content = message.content;
|
|
84
|
-
|
|
85
|
-
if (
|
|
62
|
+
const contentLength = getToolContentCharLength(content);
|
|
63
|
+
if (contentLength < settings.minPrunableToolChars) continue;
|
|
86
64
|
const ageRatio = (totalMessages - i) / totalMessages;
|
|
65
|
+
if (Array.isArray(content) && content.some(isAtomicToolContentBlock) && ageRatio >= settings.softTrimRatio) {
|
|
66
|
+
const compacted = compactToolContent(content, settings.softTrim.maxChars);
|
|
67
|
+
if (compacted.changed) {
|
|
68
|
+
const cloned = cloneToolMessageWithContent(message, compacted.content);
|
|
69
|
+
messages[i] = cloned;
|
|
70
|
+
indexTokenCountMap[i] = tokenCounter(cloned);
|
|
71
|
+
softTrimmed++;
|
|
72
|
+
}
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
87
75
|
if (ageRatio >= settings.hardClearRatio && settings.hardClear.enabled) {
|
|
88
|
-
const cloned =
|
|
89
|
-
content: settings.hardClear.placeholder,
|
|
90
|
-
tool_call_id: message.tool_call_id,
|
|
91
|
-
name: message.name,
|
|
92
|
-
id: message.id,
|
|
93
|
-
additional_kwargs: message.additional_kwargs,
|
|
94
|
-
response_metadata: message.response_metadata
|
|
95
|
-
});
|
|
76
|
+
const cloned = cloneToolMessageWithContent(message, settings.hardClear.placeholder);
|
|
96
77
|
messages[i] = cloned;
|
|
97
78
|
indexTokenCountMap[i] = tokenCounter(cloned);
|
|
98
79
|
hardCleared++;
|
|
99
80
|
} else if (ageRatio >= settings.softTrimRatio) {
|
|
100
|
-
if (
|
|
101
|
-
const cloned =
|
|
102
|
-
content: softTrimContent(content, settings.softTrim),
|
|
103
|
-
tool_call_id: message.tool_call_id,
|
|
104
|
-
name: message.name,
|
|
105
|
-
id: message.id,
|
|
106
|
-
additional_kwargs: message.additional_kwargs,
|
|
107
|
-
response_metadata: message.response_metadata
|
|
108
|
-
});
|
|
81
|
+
if (contentLength > settings.softTrim.maxChars) {
|
|
82
|
+
const cloned = cloneToolMessageWithContent(message, softTrimContent(typeof content === "string" ? content : serializeToolContentBounded(content, settings.softTrim.maxChars), settings.softTrim));
|
|
109
83
|
messages[i] = cloned;
|
|
110
84
|
indexTokenCountMap[i] = tokenCounter(cloned);
|
|
111
85
|
softTrimmed++;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contextPruning.mjs","names":[],"sources":["../../../src/messages/contextPruning.ts"],"sourcesContent":["/**\n * Position-based context pruning for tool results.\n *\n * Uses position-based age: the distance of a message\n * from the conversation end as a fraction of total messages.\n *\n * Two degradation levels:\n * - Soft-trim: Keep head + tail of tool result content, drop middle.\n * - Hard-clear: Replace entire content with a placeholder.\n *\n * Messages in the \"protected zone\" (recent assistant turns, system/pre-first-human\n * messages
|
|
1
|
+
{"version":3,"file":"contextPruning.mjs","names":[],"sources":["../../../src/messages/contextPruning.ts"],"sourcesContent":["/**\n * Position-based context pruning for tool results.\n *\n * Uses position-based age: the distance of a message\n * from the conversation end as a fraction of total messages.\n *\n * Two degradation levels:\n * - Soft-trim: Keep head + tail of tool result content, drop middle.\n * - Hard-clear: Replace entire content with a placeholder.\n *\n * Messages in the \"protected zone\" (recent assistant turns, system/pre-first-human\n * messages) are never pruned. Atomic media/resource blocks are preserved when\n * they fit; oversized inline payloads are replaced with bounded placeholders.\n */\n\nimport { ToolMessage, type BaseMessage } from '@langchain/core/messages';\nimport type { ContextPruningSettings } from './contextPruningSettings';\nimport type { ContextPruningConfig } from '@/types/graph';\nimport type { TokenCounter } from '@/types/run';\nimport {\n cloneToolMessageWithContent,\n compactToolContent,\n getToolContentCharLength,\n isAtomicToolContentBlock,\n isComputerCallOutputMessage,\n serializeToolContentBounded,\n} from '@/utils/toolContent';\nimport { resolveContextPruningSettings } from './contextPruningSettings';\n\n/**\n * Applies head+tail soft-trim to tool result content.\n */\nfunction softTrimContent(\n content: string,\n settings: ContextPruningSettings['softTrim']\n): string {\n const { headChars, tailChars } = settings;\n const indicator = `\\n\\n… [soft-trimmed: ${content.length} chars → ${headChars + tailChars} chars, middle removed] …\\n\\n`;\n return content.slice(0, headChars) + indicator + content.slice(-tailChars);\n}\n\nexport interface ContextPruningResult {\n /** Number of messages that were soft-trimmed. */\n softTrimmed: number;\n /** Number of messages that were hard-cleared. */\n hardCleared: number;\n}\n\n/**\n * Applies position-based context pruning to tool result messages.\n *\n * Modifies messages in-place and updates indexTokenCountMap with recounted\n * token values for modified messages.\n *\n * @param params.messages - The full message array (modified in-place).\n * @param params.indexTokenCountMap - Token count map (updated in-place).\n * @param params.tokenCounter - Function to recount tokens after modification.\n * @param params.config - Partial context pruning config (merged with defaults).\n * @returns Counts of soft-trimmed and hard-cleared messages.\n */\nexport function applyContextPruning(params: {\n messages: BaseMessage[];\n indexTokenCountMap: Record<string, number | undefined>;\n tokenCounter: TokenCounter;\n config?: ContextPruningConfig;\n resolvedSettings?: ContextPruningSettings;\n}): ContextPruningResult {\n const {\n messages,\n indexTokenCountMap,\n tokenCounter,\n config,\n resolvedSettings,\n } = params;\n const settings = resolvedSettings ?? resolveContextPruningSettings(config);\n\n if (!settings.enabled || messages.length === 0) {\n return { softTrimmed: 0, hardCleared: 0 };\n }\n\n const totalMessages = messages.length;\n let softTrimmed = 0;\n let hardCleared = 0;\n\n // Find the protected zone: last N assistant turns from the end.\n // An \"assistant turn\" is a contiguous sequence of AI + Tool messages.\n const protectedIndices = new Set<number>();\n\n // Always protect the system message (index 0 if present)\n if (messages[0]?.getType() === 'system') {\n protectedIndices.add(0);\n }\n\n // Protect messages before the first human message\n for (let i = 0; i < totalMessages; i++) {\n if (messages[i].getType() === 'human') {\n break;\n }\n protectedIndices.add(i);\n }\n\n // Protect the last N assistant turns (walking backwards)\n let assistantTurnsFound = 0;\n let inAssistantSequence = false;\n for (let i = totalMessages - 1; i >= 0; i--) {\n const type = messages[i].getType();\n if (type === 'ai' || type === 'tool') {\n protectedIndices.add(i);\n if (!inAssistantSequence) {\n inAssistantSequence = true;\n }\n } else {\n if (inAssistantSequence) {\n assistantTurnsFound++;\n inAssistantSequence = false;\n if (assistantTurnsFound >= settings.keepLastAssistants) {\n break;\n }\n }\n // Protect the human message between assistant turns in the protected zone\n if (assistantTurnsFound < settings.keepLastAssistants) {\n protectedIndices.add(i);\n }\n }\n }\n\n // Process each tool message outside the protected zone\n for (let i = 0; i < totalMessages; i++) {\n const message = messages[i];\n if (message.getType() !== 'tool') {\n continue;\n }\n if (isComputerCallOutputMessage(message)) {\n continue;\n }\n if (protectedIndices.has(i)) {\n continue;\n }\n const content = message.content;\n const contentLength = getToolContentCharLength(content);\n if (contentLength < settings.minPrunableToolChars) {\n continue;\n }\n\n // Compute age ratio: how far back from the end (0 = latest, 1 = oldest)\n const ageRatio = (totalMessages - i) / totalMessages;\n\n if (\n Array.isArray(content) &&\n content.some(isAtomicToolContentBlock) &&\n ageRatio >= settings.softTrimRatio\n ) {\n const compacted = compactToolContent(content, settings.softTrim.maxChars);\n if (compacted.changed) {\n const cloned = cloneToolMessageWithContent(\n message as ToolMessage,\n compacted.content\n );\n messages[i] = cloned;\n indexTokenCountMap[i] = tokenCounter(cloned);\n softTrimmed++;\n }\n continue;\n }\n\n if (ageRatio >= settings.hardClearRatio && settings.hardClear.enabled) {\n // Hard-clear: replace with placeholder\n const cloned = cloneToolMessageWithContent(\n message as ToolMessage,\n settings.hardClear.placeholder\n );\n messages[i] = cloned;\n indexTokenCountMap[i] = tokenCounter(cloned);\n hardCleared++;\n } else if (ageRatio >= settings.softTrimRatio) {\n // Soft-trim: keep head + tail\n if (contentLength > settings.softTrim.maxChars) {\n const cloned = cloneToolMessageWithContent(\n message as ToolMessage,\n softTrimContent(\n typeof content === 'string'\n ? content\n : serializeToolContentBounded(\n content,\n settings.softTrim.maxChars\n ),\n settings.softTrim\n )\n );\n messages[i] = cloned;\n indexTokenCountMap[i] = tokenCounter(cloned);\n softTrimmed++;\n }\n }\n }\n\n return { softTrimmed, hardCleared };\n}\n"],"mappings":";;;;;;AAgCA,SAAS,gBACP,SACA,UACQ;CACR,MAAM,EAAE,WAAW,cAAc;CACjC,MAAM,YAAY,wBAAwB,QAAQ,OAAO,WAAW,YAAY,UAAU;CAC1F,OAAO,QAAQ,MAAM,GAAG,SAAS,IAAI,YAAY,QAAQ,MAAM,CAAC,SAAS;AAC3E;;;;;;;;;;;;;AAqBA,SAAgB,oBAAoB,QAMX;CACvB,MAAM,EACJ,UACA,oBACA,cACA,QACA,qBACE;CACJ,MAAM,WAAW,oBAAoB,8BAA8B,MAAM;CAEzE,IAAI,CAAC,SAAS,WAAW,SAAS,WAAW,GAC3C,OAAO;EAAE,aAAa;EAAG,aAAa;CAAE;CAG1C,MAAM,gBAAgB,SAAS;CAC/B,IAAI,cAAc;CAClB,IAAI,cAAc;CAIlB,MAAM,mCAAmB,IAAI,IAAY;CAGzC,IAAI,SAAS,EAAE,EAAE,QAAQ,MAAM,UAC7B,iBAAiB,IAAI,CAAC;CAIxB,KAAK,IAAI,IAAI,GAAG,IAAI,eAAe,KAAK;EACtC,IAAI,SAAS,EAAE,CAAC,QAAQ,MAAM,SAC5B;EAEF,iBAAiB,IAAI,CAAC;CACxB;CAGA,IAAI,sBAAsB;CAC1B,IAAI,sBAAsB;CAC1B,KAAK,IAAI,IAAI,gBAAgB,GAAG,KAAK,GAAG,KAAK;EAC3C,MAAM,OAAO,SAAS,EAAE,CAAC,QAAQ;EACjC,IAAI,SAAS,QAAQ,SAAS,QAAQ;GACpC,iBAAiB,IAAI,CAAC;GACtB,IAAI,CAAC,qBACH,sBAAsB;EAE1B,OAAO;GACL,IAAI,qBAAqB;IACvB;IACA,sBAAsB;IACtB,IAAI,uBAAuB,SAAS,oBAClC;GAEJ;GAEA,IAAI,sBAAsB,SAAS,oBACjC,iBAAiB,IAAI,CAAC;EAE1B;CACF;CAGA,KAAK,IAAI,IAAI,GAAG,IAAI,eAAe,KAAK;EACtC,MAAM,UAAU,SAAS;EACzB,IAAI,QAAQ,QAAQ,MAAM,QACxB;EAEF,IAAI,4BAA4B,OAAO,GACrC;EAEF,IAAI,iBAAiB,IAAI,CAAC,GACxB;EAEF,MAAM,UAAU,QAAQ;EACxB,MAAM,gBAAgB,yBAAyB,OAAO;EACtD,IAAI,gBAAgB,SAAS,sBAC3B;EAIF,MAAM,YAAY,gBAAgB,KAAK;EAEvC,IACE,MAAM,QAAQ,OAAO,KACrB,QAAQ,KAAK,wBAAwB,KACrC,YAAY,SAAS,eACrB;GACA,MAAM,YAAY,mBAAmB,SAAS,SAAS,SAAS,QAAQ;GACxE,IAAI,UAAU,SAAS;IACrB,MAAM,SAAS,4BACb,SACA,UAAU,OACZ;IACA,SAAS,KAAK;IACd,mBAAmB,KAAK,aAAa,MAAM;IAC3C;GACF;GACA;EACF;EAEA,IAAI,YAAY,SAAS,kBAAkB,SAAS,UAAU,SAAS;GAErE,MAAM,SAAS,4BACb,SACA,SAAS,UAAU,WACrB;GACA,SAAS,KAAK;GACd,mBAAmB,KAAK,aAAa,MAAM;GAC3C;EACF,OAAO,IAAI,YAAY,SAAS;OAE1B,gBAAgB,SAAS,SAAS,UAAU;IAC9C,MAAM,SAAS,4BACb,SACA,gBACE,OAAO,YAAY,WACf,UACA,4BACA,SACA,SAAS,SAAS,QACpB,GACF,SAAS,QACX,CACF;IACA,SAAS,KAAK;IACd,mBAAmB,KAAK,aAAa,MAAM;IAC3C;GACF;;CAEJ;CAEA,OAAO;EAAE;EAAa;CAAY;AACpC"}
|