@librechat/agents 3.2.34 → 3.2.36

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 (128) hide show
  1. package/dist/cjs/agents/AgentContext.cjs +119 -9
  2. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  3. package/dist/cjs/agents/projection.cjs +25 -0
  4. package/dist/cjs/agents/projection.cjs.map +1 -0
  5. package/dist/cjs/common/enum.cjs +13 -0
  6. package/dist/cjs/common/enum.cjs.map +1 -1
  7. package/dist/cjs/graphs/Graph.cjs +106 -3
  8. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  9. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +26 -4
  10. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
  11. package/dist/cjs/llm/bedrock/utils/message_inputs.cjs +20 -0
  12. package/dist/cjs/llm/bedrock/utils/message_inputs.cjs.map +1 -1
  13. package/dist/cjs/llm/invoke.cjs +49 -8
  14. package/dist/cjs/llm/invoke.cjs.map +1 -1
  15. package/dist/cjs/main.cjs +7 -0
  16. package/dist/cjs/messages/budget.cjs +23 -0
  17. package/dist/cjs/messages/budget.cjs.map +1 -0
  18. package/dist/cjs/messages/cache.cjs +1 -0
  19. package/dist/cjs/messages/cache.cjs.map +1 -1
  20. package/dist/cjs/messages/content.cjs +12 -14
  21. package/dist/cjs/messages/content.cjs.map +1 -1
  22. package/dist/cjs/messages/index.cjs +1 -0
  23. package/dist/cjs/messages/prune.cjs +31 -13
  24. package/dist/cjs/messages/prune.cjs.map +1 -1
  25. package/dist/cjs/run.cjs +7 -2
  26. package/dist/cjs/run.cjs.map +1 -1
  27. package/dist/cjs/summarization/node.cjs +12 -1
  28. package/dist/cjs/summarization/node.cjs.map +1 -1
  29. package/dist/cjs/tools/search/format.cjs +91 -2
  30. package/dist/cjs/tools/search/format.cjs.map +1 -1
  31. package/dist/cjs/tools/search/tool.cjs +4 -3
  32. package/dist/cjs/tools/search/tool.cjs.map +1 -1
  33. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +138 -2
  34. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
  35. package/dist/cjs/utils/tokens.cjs +30 -0
  36. package/dist/cjs/utils/tokens.cjs.map +1 -1
  37. package/dist/esm/agents/AgentContext.mjs +121 -11
  38. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  39. package/dist/esm/agents/projection.mjs +25 -0
  40. package/dist/esm/agents/projection.mjs.map +1 -0
  41. package/dist/esm/common/enum.mjs +13 -0
  42. package/dist/esm/common/enum.mjs.map +1 -1
  43. package/dist/esm/graphs/Graph.mjs +107 -4
  44. package/dist/esm/graphs/Graph.mjs.map +1 -1
  45. package/dist/esm/llm/anthropic/utils/message_inputs.mjs +26 -4
  46. package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
  47. package/dist/esm/llm/bedrock/utils/message_inputs.mjs +20 -0
  48. package/dist/esm/llm/bedrock/utils/message_inputs.mjs.map +1 -1
  49. package/dist/esm/llm/invoke.mjs +49 -8
  50. package/dist/esm/llm/invoke.mjs.map +1 -1
  51. package/dist/esm/main.mjs +6 -4
  52. package/dist/esm/messages/budget.mjs +23 -0
  53. package/dist/esm/messages/budget.mjs.map +1 -0
  54. package/dist/esm/messages/cache.mjs +1 -1
  55. package/dist/esm/messages/cache.mjs.map +1 -1
  56. package/dist/esm/messages/content.mjs +12 -15
  57. package/dist/esm/messages/content.mjs.map +1 -1
  58. package/dist/esm/messages/index.mjs +1 -0
  59. package/dist/esm/messages/prune.mjs +31 -13
  60. package/dist/esm/messages/prune.mjs.map +1 -1
  61. package/dist/esm/run.mjs +7 -2
  62. package/dist/esm/run.mjs.map +1 -1
  63. package/dist/esm/summarization/node.mjs +12 -1
  64. package/dist/esm/summarization/node.mjs.map +1 -1
  65. package/dist/esm/tools/search/format.mjs +91 -2
  66. package/dist/esm/tools/search/format.mjs.map +1 -1
  67. package/dist/esm/tools/search/tool.mjs +4 -3
  68. package/dist/esm/tools/search/tool.mjs.map +1 -1
  69. package/dist/esm/tools/subagent/SubagentExecutor.mjs +138 -2
  70. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
  71. package/dist/esm/utils/tokens.mjs +30 -1
  72. package/dist/esm/utils/tokens.mjs.map +1 -1
  73. package/dist/types/agents/AgentContext.d.ts +37 -4
  74. package/dist/types/agents/projection.d.ts +26 -0
  75. package/dist/types/common/enum.d.ts +13 -0
  76. package/dist/types/graphs/Graph.d.ts +8 -1
  77. package/dist/types/index.d.ts +1 -0
  78. package/dist/types/llm/invoke.d.ts +1 -1
  79. package/dist/types/messages/budget.d.ts +11 -0
  80. package/dist/types/messages/cache.d.ts +7 -0
  81. package/dist/types/messages/content.d.ts +5 -0
  82. package/dist/types/messages/index.d.ts +1 -0
  83. package/dist/types/messages/prune.d.ts +4 -0
  84. package/dist/types/run.d.ts +1 -0
  85. package/dist/types/tools/search/format.d.ts +4 -1
  86. package/dist/types/tools/search/types.d.ts +7 -0
  87. package/dist/types/tools/subagent/SubagentExecutor.d.ts +11 -1
  88. package/dist/types/types/graph.d.ts +89 -3
  89. package/dist/types/types/run.d.ts +13 -0
  90. package/dist/types/utils/tokens.d.ts +7 -0
  91. package/package.json +1 -1
  92. package/src/agents/AgentContext.ts +172 -8
  93. package/src/agents/__tests__/AgentContext.test.ts +235 -2
  94. package/src/agents/__tests__/projection.test.ts +73 -0
  95. package/src/agents/projection.ts +46 -0
  96. package/src/common/enum.ts +13 -0
  97. package/src/graphs/Graph.ts +168 -0
  98. package/src/index.ts +3 -0
  99. package/src/llm/anthropic/utils/cross-provider-reasoning.test.ts +317 -0
  100. package/src/llm/anthropic/utils/message_inputs.ts +78 -16
  101. package/src/llm/bedrock/utils/cross-provider-reasoning.test.ts +131 -0
  102. package/src/llm/bedrock/utils/message_inputs.ts +35 -0
  103. package/src/llm/invoke.test.ts +79 -1
  104. package/src/llm/invoke.ts +58 -4
  105. package/src/messages/budget.ts +32 -0
  106. package/src/messages/cache.ts +1 -1
  107. package/src/messages/content.ts +24 -32
  108. package/src/messages/index.ts +1 -0
  109. package/src/messages/prune.ts +39 -2
  110. package/src/run.ts +5 -0
  111. package/src/scripts/subagent-usage-sink.ts +176 -0
  112. package/src/specs/context-accuracy.live.test.ts +409 -0
  113. package/src/specs/context-usage-event.test.ts +117 -0
  114. package/src/specs/context-usage.live.test.ts +297 -0
  115. package/src/specs/prune.test.ts +51 -1
  116. package/src/specs/subagent.test.ts +124 -1
  117. package/src/summarization/__tests__/node.test.ts +60 -1
  118. package/src/summarization/node.ts +20 -1
  119. package/src/tools/__tests__/SubagentExecutor.test.ts +443 -1
  120. package/src/tools/search/format.test.ts +242 -0
  121. package/src/tools/search/format.ts +122 -5
  122. package/src/tools/search/tool.ts +5 -1
  123. package/src/tools/search/types.ts +7 -0
  124. package/src/tools/subagent/SubagentExecutor.ts +221 -3
  125. package/src/types/graph.ts +94 -1
  126. package/src/types/run.ts +13 -0
  127. package/src/utils/__tests__/apportion.test.ts +32 -0
  128. package/src/utils/tokens.ts +33 -0
@@ -1 +1 @@
1
- {"version":3,"file":"invoke.mjs","names":[],"sources":["../../../src/llm/invoke.ts"],"sourcesContent":["import { concat } from '@langchain/core/utils/stream';\nimport { AIMessageChunk } from '@langchain/core/messages';\nimport type { RunnableConfig } from '@langchain/core/runnables';\nimport type { ToolCall } from '@langchain/core/messages/tool';\nimport type { BaseMessage } from '@langchain/core/messages';\nimport type { ToolOutputReferenceRegistry } from '@/tools/toolOutputReferences';\nimport type * as t from '@/types';\nimport { annotateMessagesForLLM } from '@/tools/toolOutputReferences';\nimport { manualToolStreamProviders } from '@/llm/providers';\nimport { modifyDeltaProperties } from '@/messages';\nimport { ChatModelStreamHandler } from '@/stream';\nimport { GraphEvents, Providers } from '@/common';\nimport { initializeModel } from '@/llm/init';\n\n/**\n * Context passed to `attemptInvoke`. Matches the subset of Graph that\n * `ChatModelStreamHandler.handle` needs *plus* the explicit\n * `getOrCreateToolOutputRegistry()` accessor that `attemptInvoke`\n * itself calls to pull the run-scoped tool-output registry off the\n * graph and project each relevant ToolMessage into a transient\n * annotated copy before the provider call.\n *\n * The intersection is intentional: `Parameters<...>[3]` resolves\n * indirectly through the stream handler's signature (which returns\n * `StandardGraph` and already exposes the accessor since #117), but\n * stating it explicitly here surfaces the contract at the call site —\n * a developer reading `attemptInvoke` doesn't have to chase the\n * upstream handler's parameter list to discover that\n * `context?.getOrCreateToolOutputRegistry()` is a real thing. Single\n * optional chain only — the method itself is required on the\n * `StandardGraph` branch of the intersection, so the second `?.` is\n * unnecessary at the call site.\n *\n * `NonNullable<...>` strips `undefined` from the upstream parameter\n * type so the intersection doesn't collapse to `never` on the\n * undefined branch; callers express optionality via `context?:\n * InvokeContext` on the function signature instead.\n *\n * Callers without a registry (e.g. summarization) simply pass no\n * `context` and the transform safely no-ops.\n */\nexport type InvokeContext = NonNullable<\n Parameters<ChatModelStreamHandler['handle']>[3]\n> & {\n getOrCreateToolOutputRegistry?(): ToolOutputReferenceRegistry | undefined;\n};\n\n/**\n * Per-chunk callback for custom stream processing.\n * When provided, replaces the default `ChatModelStreamHandler`.\n */\nexport type OnChunk = (chunk: AIMessageChunk) => void | Promise<void>;\n\nfunction getRegisteredDefaultChatStreamHandler(\n context?: InvokeContext\n): ChatModelStreamHandler | undefined {\n const handler = context?.handlerRegistry?.getHandler(\n GraphEvents.CHAT_MODEL_STREAM\n );\n return handler instanceof ChatModelStreamHandler ? handler : undefined;\n}\n\nfunction hasReasoningDetails(chunk: AIMessageChunk): boolean {\n const reasoningDetails = chunk.additional_kwargs.reasoning_details;\n return Array.isArray(reasoningDetails) && reasoningDetails.length > 0;\n}\n\nfunction removeOpenRouterFinalReasoningReplayContent({\n current,\n next,\n provider,\n}: {\n current?: AIMessageChunk;\n next: AIMessageChunk;\n provider: Providers;\n}): AIMessageChunk {\n const content = getOpenRouterFinalReasoningContent({\n current,\n next,\n provider,\n });\n if (content == null || content === next.content) {\n return next;\n }\n\n return new AIMessageChunk(\n Object.assign({}, next, {\n content,\n })\n );\n}\n\nfunction getOpenRouterFinalReasoningContent({\n current,\n next,\n provider,\n}: {\n current?: AIMessageChunk;\n next: AIMessageChunk;\n provider: Providers;\n}): string | undefined {\n if (\n provider !== Providers.OPENROUTER ||\n current == null ||\n !hasReasoningDetails(next) ||\n typeof current.content !== 'string' ||\n current.content === '' ||\n typeof next.content !== 'string' ||\n next.content === ''\n ) {\n return undefined;\n }\n if (!next.content.startsWith(current.content)) {\n return next.content;\n }\n return next.content.slice(current.content.length);\n}\n\nfunction removeReasoningDetails(\n additionalKwargs: AIMessageChunk['additional_kwargs']\n): AIMessageChunk['additional_kwargs'] {\n return Object.fromEntries(\n Object.entries(additionalKwargs).filter(\n ([key]) => key !== 'reasoning_details'\n )\n );\n}\n\nfunction getStreamHandlingChunk({\n current,\n next,\n provider,\n}: {\n current?: AIMessageChunk;\n next: AIMessageChunk;\n provider: Providers;\n}): AIMessageChunk | undefined {\n const content = getOpenRouterFinalReasoningContent({\n current,\n next,\n provider,\n });\n if (content == null) {\n return next;\n }\n if (content === '') {\n return undefined;\n }\n return new AIMessageChunk(\n Object.assign({}, next, {\n content,\n additional_kwargs: removeReasoningDetails(next.additional_kwargs),\n })\n );\n}\n\nfunction appendStreamChunk({\n current,\n next,\n provider,\n}: {\n current?: AIMessageChunk;\n next: AIMessageChunk;\n provider: Providers;\n}): AIMessageChunk {\n if (current == null) {\n return next;\n }\n return concat(\n current,\n removeOpenRouterFinalReasoningReplayContent({ current, next, provider })\n );\n}\n\n/**\n * Invokes a chat model with the given messages, handling both streaming and\n * non-streaming paths.\n *\n * By default, stream chunks are processed through a `ChatModelStreamHandler`\n * that dispatches run steps (MESSAGE_CREATION, TOOL_CALLS) for the graph.\n * Pass an `onChunk` callback to override this with custom chunk processing\n * (e.g. summarization delta events).\n */\nexport async function attemptInvoke(\n {\n model,\n messages,\n provider,\n context,\n onChunk,\n }: {\n model: t.ChatModel;\n messages: BaseMessage[];\n provider: Providers;\n context?: InvokeContext;\n onChunk?: OnChunk;\n },\n config?: RunnableConfig\n): Promise<Partial<t.BaseGraphState>> {\n /**\n * Pull the run-scoped tool output registry off the graph (when one\n * exists) and project ToolMessages carrying ref metadata into a\n * transient annotated copy. The original `messages` array stays\n * untouched so the graph state never sees `[ref: …]` / `_ref`\n * payload.\n */\n const registry = context?.getOrCreateToolOutputRegistry();\n const runId = config?.configurable?.run_id as string | undefined;\n const messagesForProvider = annotateMessagesForLLM(messages, registry, runId);\n\n if (model.stream) {\n const stream = await model.stream(messagesForProvider, config);\n let finalChunk: AIMessageChunk | undefined;\n const registeredStreamHandler =\n getRegisteredDefaultChatStreamHandler(context);\n\n if (onChunk) {\n for await (const chunk of stream) {\n await onChunk(chunk);\n finalChunk = appendStreamChunk({\n current: finalChunk,\n next: chunk,\n provider,\n });\n }\n } else if (registeredStreamHandler == null) {\n const metadata = config?.metadata as Record<string, unknown> | undefined;\n const streamHandler = new ChatModelStreamHandler();\n for await (const chunk of stream) {\n const handlingChunk = getStreamHandlingChunk({\n current: finalChunk,\n next: chunk,\n provider,\n });\n if (handlingChunk != null) {\n await streamHandler.handle(\n GraphEvents.CHAT_MODEL_STREAM,\n { chunk: handlingChunk },\n metadata,\n context\n );\n }\n finalChunk = appendStreamChunk({\n current: finalChunk,\n next: chunk,\n provider,\n });\n }\n } else {\n const metadata = config?.metadata as Record<string, unknown> | undefined;\n for await (const chunk of stream) {\n const handlingChunk = getStreamHandlingChunk({\n current: finalChunk,\n next: chunk,\n provider,\n });\n if (handlingChunk != null && handlingChunk !== chunk) {\n await registeredStreamHandler.handle(\n GraphEvents.CHAT_MODEL_STREAM,\n { chunk: handlingChunk },\n metadata,\n context\n );\n }\n finalChunk = appendStreamChunk({\n current: finalChunk,\n next: chunk,\n provider,\n });\n }\n }\n\n if (manualToolStreamProviders.has(provider)) {\n finalChunk = modifyDeltaProperties(provider, finalChunk);\n }\n\n if ((finalChunk?.tool_calls?.length ?? 0) > 0) {\n finalChunk!.tool_calls = finalChunk!.tool_calls?.filter(\n (tool_call: ToolCall) => !!tool_call.name\n );\n }\n\n return { messages: [finalChunk as AIMessageChunk] };\n }\n\n const finalMessage = await model.invoke(messagesForProvider, config);\n if ((finalMessage.tool_calls?.length ?? 0) > 0) {\n finalMessage.tool_calls = finalMessage.tool_calls?.filter(\n (tool_call: ToolCall) => !!tool_call.name\n );\n }\n return { messages: [finalMessage] };\n}\n\n/**\n * Attempts each fallback provider in order until one succeeds.\n * Throws the last error if all fallbacks fail.\n */\nexport async function tryFallbackProviders({\n fallbacks,\n tools,\n messages,\n config,\n primaryError,\n context,\n onChunk,\n}: {\n fallbacks: Array<{ provider: Providers; clientOptions?: t.ClientOptions }>;\n tools?: t.GraphTools;\n messages: BaseMessage[];\n config?: RunnableConfig;\n primaryError: unknown;\n context?: InvokeContext;\n onChunk?: OnChunk;\n}): Promise<Partial<t.BaseGraphState> | undefined> {\n let lastError: unknown = primaryError;\n for (const fb of fallbacks) {\n try {\n const fbModel = initializeModel({\n provider: fb.provider,\n clientOptions: fb.clientOptions,\n tools,\n });\n const result = await attemptInvoke(\n {\n model: fbModel as t.ChatModel,\n messages,\n provider: fb.provider,\n context,\n onChunk,\n },\n config\n );\n return result;\n } catch (e) {\n lastError = e;\n continue;\n }\n }\n if (lastError !== undefined) {\n throw lastError;\n }\n return undefined;\n}\n"],"mappings":";;;;;;;;;;;AAqDA,SAAS,sCACP,SACoC;CACpC,MAAM,UAAU,SAAS,iBAAiB,WAAA,sBAE1C;CACA,OAAO,mBAAmB,yBAAyB,UAAU,KAAA;AAC/D;AAEA,SAAS,oBAAoB,OAAgC;CAC3D,MAAM,mBAAmB,MAAM,kBAAkB;CACjD,OAAO,MAAM,QAAQ,gBAAgB,KAAK,iBAAiB,SAAS;AACtE;AAEA,SAAS,4CAA4C,EACnD,SACA,MACA,YAKiB;CACjB,MAAM,UAAU,mCAAmC;EACjD;EACA;EACA;CACF,CAAC;CACD,IAAI,WAAW,QAAQ,YAAY,KAAK,SACtC,OAAO;CAGT,OAAO,IAAI,eACT,OAAO,OAAO,CAAC,GAAG,MAAM,EACtB,QACF,CAAC,CACH;AACF;AAEA,SAAS,mCAAmC,EAC1C,SACA,MACA,YAKqB;CACrB,IACE,aAAA,gBACA,WAAW,QACX,CAAC,oBAAoB,IAAI,KACzB,OAAO,QAAQ,YAAY,YAC3B,QAAQ,YAAY,MACpB,OAAO,KAAK,YAAY,YACxB,KAAK,YAAY,IAEjB;CAEF,IAAI,CAAC,KAAK,QAAQ,WAAW,QAAQ,OAAO,GAC1C,OAAO,KAAK;CAEd,OAAO,KAAK,QAAQ,MAAM,QAAQ,QAAQ,MAAM;AAClD;AAEA,SAAS,uBACP,kBACqC;CACrC,OAAO,OAAO,YACZ,OAAO,QAAQ,gBAAgB,CAAC,CAAC,QAC9B,CAAC,SAAS,QAAQ,mBACrB,CACF;AACF;AAEA,SAAS,uBAAuB,EAC9B,SACA,MACA,YAK6B;CAC7B,MAAM,UAAU,mCAAmC;EACjD;EACA;EACA;CACF,CAAC;CACD,IAAI,WAAW,MACb,OAAO;CAET,IAAI,YAAY,IACd;CAEF,OAAO,IAAI,eACT,OAAO,OAAO,CAAC,GAAG,MAAM;EACtB;EACA,mBAAmB,uBAAuB,KAAK,iBAAiB;CAClE,CAAC,CACH;AACF;AAEA,SAAS,kBAAkB,EACzB,SACA,MACA,YAKiB;CACjB,IAAI,WAAW,MACb,OAAO;CAET,OAAO,OACL,SACA,4CAA4C;EAAE;EAAS;EAAM;CAAS,CAAC,CACzE;AACF;;;;;;;;;;AAWA,eAAsB,cACpB,EACE,OACA,UACA,UACA,SACA,WAQF,QACoC;;;;;;;;CAQpC,MAAM,WAAW,SAAS,8BAA8B;CACxD,MAAM,QAAQ,QAAQ,cAAc;CACpC,MAAM,sBAAsB,uBAAuB,UAAU,UAAU,KAAK;CAE5E,IAAI,MAAM,QAAQ;EAChB,MAAM,SAAS,MAAM,MAAM,OAAO,qBAAqB,MAAM;EAC7D,IAAI;EACJ,MAAM,0BACJ,sCAAsC,OAAO;EAE/C,IAAI,SACF,WAAW,MAAM,SAAS,QAAQ;GAChC,MAAM,QAAQ,KAAK;GACnB,aAAa,kBAAkB;IAC7B,SAAS;IACT,MAAM;IACN;GACF,CAAC;EACH;OACK,IAAI,2BAA2B,MAAM;GAC1C,MAAM,WAAW,QAAQ;GACzB,MAAM,gBAAgB,IAAI,uBAAuB;GACjD,WAAW,MAAM,SAAS,QAAQ;IAChC,MAAM,gBAAgB,uBAAuB;KAC3C,SAAS;KACT,MAAM;KACN;IACF,CAAC;IACD,IAAI,iBAAiB,MACnB,MAAM,cAAc,OAAA,wBAElB,EAAE,OAAO,cAAc,GACvB,UACA,OACF;IAEF,aAAa,kBAAkB;KAC7B,SAAS;KACT,MAAM;KACN;IACF,CAAC;GACH;EACF,OAAO;GACL,MAAM,WAAW,QAAQ;GACzB,WAAW,MAAM,SAAS,QAAQ;IAChC,MAAM,gBAAgB,uBAAuB;KAC3C,SAAS;KACT,MAAM;KACN;IACF,CAAC;IACD,IAAI,iBAAiB,QAAQ,kBAAkB,OAC7C,MAAM,wBAAwB,OAAA,wBAE5B,EAAE,OAAO,cAAc,GACvB,UACA,OACF;IAEF,aAAa,kBAAkB;KAC7B,SAAS;KACT,MAAM;KACN;IACF,CAAC;GACH;EACF;EAEA,IAAI,0BAA0B,IAAI,QAAQ,GACxC,aAAa,sBAAsB,UAAU,UAAU;EAGzD,KAAK,YAAY,YAAY,UAAU,KAAK,GAC1C,WAAY,aAAa,WAAY,YAAY,QAC9C,cAAwB,CAAC,CAAC,UAAU,IACvC;EAGF,OAAO,EAAE,UAAU,CAAC,UAA4B,EAAE;CACpD;CAEA,MAAM,eAAe,MAAM,MAAM,OAAO,qBAAqB,MAAM;CACnE,KAAK,aAAa,YAAY,UAAU,KAAK,GAC3C,aAAa,aAAa,aAAa,YAAY,QAChD,cAAwB,CAAC,CAAC,UAAU,IACvC;CAEF,OAAO,EAAE,UAAU,CAAC,YAAY,EAAE;AACpC;;;;;AAMA,eAAsB,qBAAqB,EACzC,WACA,OACA,UACA,QACA,cACA,SACA,WASiD;CACjD,IAAI,YAAqB;CACzB,KAAK,MAAM,MAAM,WACf,IAAI;EAgBF,OAAO,MAVc,cACnB;GACE,OAPY,gBAAgB;IAC9B,UAAU,GAAG;IACb,eAAe,GAAG;IAClB;GACF,CAGiB;GACb;GACA,UAAU,GAAG;GACb;GACA;EACF,GACA,MACF;CAEF,SAAS,GAAG;EACV,YAAY;EACZ;CACF;CAEF,IAAI,cAAc,KAAA,GAChB,MAAM;AAGV"}
1
+ {"version":3,"file":"invoke.mjs","names":[],"sources":["../../../src/llm/invoke.ts"],"sourcesContent":["import { concat } from '@langchain/core/utils/stream';\nimport { AIMessageChunk } from '@langchain/core/messages';\nimport type { RunnableConfig } from '@langchain/core/runnables';\nimport type { ToolCall } from '@langchain/core/messages/tool';\nimport type { BaseMessage } from '@langchain/core/messages';\nimport type { ToolOutputReferenceRegistry } from '@/tools/toolOutputReferences';\nimport type * as t from '@/types';\nimport { annotateMessagesForLLM } from '@/tools/toolOutputReferences';\nimport { Constants, GraphEvents, Providers } from '@/common';\nimport { manualToolStreamProviders } from '@/llm/providers';\nimport { modifyDeltaProperties } from '@/messages';\nimport { ChatModelStreamHandler } from '@/stream';\nimport { initializeModel } from '@/llm/init';\n\n/**\n * Context passed to `attemptInvoke`. Matches the subset of Graph that\n * `ChatModelStreamHandler.handle` needs *plus* the explicit\n * `getOrCreateToolOutputRegistry()` accessor that `attemptInvoke`\n * itself calls to pull the run-scoped tool-output registry off the\n * graph and project each relevant ToolMessage into a transient\n * annotated copy before the provider call.\n *\n * The intersection is intentional: `Parameters<...>[3]` resolves\n * indirectly through the stream handler's signature (which returns\n * `StandardGraph` and already exposes the accessor since #117), but\n * stating it explicitly here surfaces the contract at the call site —\n * a developer reading `attemptInvoke` doesn't have to chase the\n * upstream handler's parameter list to discover that\n * `context?.getOrCreateToolOutputRegistry()` is a real thing. Single\n * optional chain only — the method itself is required on the\n * `StandardGraph` branch of the intersection, so the second `?.` is\n * unnecessary at the call site.\n *\n * `NonNullable<...>` strips `undefined` from the upstream parameter\n * type so the intersection doesn't collapse to `never` on the\n * undefined branch; callers express optionality via `context?:\n * InvokeContext` on the function signature instead.\n *\n * Callers without a registry (e.g. summarization) simply pass no\n * `context` and the transform safely no-ops.\n */\nexport type InvokeContext = NonNullable<\n Parameters<ChatModelStreamHandler['handle']>[3]\n> & {\n getOrCreateToolOutputRegistry?(): ToolOutputReferenceRegistry | undefined;\n};\n\n/**\n * Per-chunk callback for custom stream processing.\n * When provided, replaces the default `ChatModelStreamHandler`.\n */\nexport type OnChunk = (chunk: AIMessageChunk) => void | Promise<void>;\n\nfunction getRegisteredDefaultChatStreamHandler(\n context?: InvokeContext\n): ChatModelStreamHandler | undefined {\n const handler = context?.handlerRegistry?.getHandler(\n GraphEvents.CHAT_MODEL_STREAM\n );\n return handler instanceof ChatModelStreamHandler ? handler : undefined;\n}\n\nfunction hasReasoningDetails(chunk: AIMessageChunk): boolean {\n const reasoningDetails = chunk.additional_kwargs.reasoning_details;\n return Array.isArray(reasoningDetails) && reasoningDetails.length > 0;\n}\n\nfunction removeOpenRouterFinalReasoningReplayContent({\n current,\n next,\n provider,\n}: {\n current?: AIMessageChunk;\n next: AIMessageChunk;\n provider: Providers;\n}): AIMessageChunk {\n const content = getOpenRouterFinalReasoningContent({\n current,\n next,\n provider,\n });\n if (content == null || content === next.content) {\n return next;\n }\n\n return new AIMessageChunk(\n Object.assign({}, next, {\n content,\n })\n );\n}\n\nfunction getOpenRouterFinalReasoningContent({\n current,\n next,\n provider,\n}: {\n current?: AIMessageChunk;\n next: AIMessageChunk;\n provider: Providers;\n}): string | undefined {\n if (\n provider !== Providers.OPENROUTER ||\n current == null ||\n !hasReasoningDetails(next) ||\n typeof current.content !== 'string' ||\n current.content === '' ||\n typeof next.content !== 'string' ||\n next.content === ''\n ) {\n return undefined;\n }\n if (!next.content.startsWith(current.content)) {\n return next.content;\n }\n return next.content.slice(current.content.length);\n}\n\nfunction removeReasoningDetails(\n additionalKwargs: AIMessageChunk['additional_kwargs']\n): AIMessageChunk['additional_kwargs'] {\n return Object.fromEntries(\n Object.entries(additionalKwargs).filter(\n ([key]) => key !== 'reasoning_details'\n )\n );\n}\n\nfunction getStreamHandlingChunk({\n current,\n next,\n provider,\n}: {\n current?: AIMessageChunk;\n next: AIMessageChunk;\n provider: Providers;\n}): AIMessageChunk | undefined {\n const content = getOpenRouterFinalReasoningContent({\n current,\n next,\n provider,\n });\n if (content == null) {\n return next;\n }\n if (content === '') {\n return undefined;\n }\n return new AIMessageChunk(\n Object.assign({}, next, {\n content,\n additional_kwargs: removeReasoningDetails(next.additional_kwargs),\n })\n );\n}\n\nfunction appendStreamChunk({\n current,\n next,\n provider,\n}: {\n current?: AIMessageChunk;\n next: AIMessageChunk;\n provider: Providers;\n}): AIMessageChunk {\n if (current == null) {\n return next;\n }\n return concat(\n current,\n removeOpenRouterFinalReasoningReplayContent({ current, next, provider })\n );\n}\n\n/**\n * Invokes a chat model with the given messages, handling both streaming and\n * non-streaming paths.\n *\n * By default, stream chunks are processed through a `ChatModelStreamHandler`\n * that dispatches run steps (MESSAGE_CREATION, TOOL_CALLS) for the graph.\n * Pass an `onChunk` callback to override this with custom chunk processing\n * (e.g. summarization delta events).\n */\nexport async function attemptInvoke(\n {\n model,\n messages,\n provider,\n context,\n onChunk,\n }: {\n model: t.ChatModel;\n messages: BaseMessage[];\n provider: Providers;\n context?: InvokeContext;\n onChunk?: OnChunk;\n },\n config?: RunnableConfig\n): Promise<Partial<t.BaseGraphState>> {\n /**\n * Pull the run-scoped tool output registry off the graph (when one\n * exists) and project ToolMessages carrying ref metadata into a\n * transient annotated copy. The original `messages` array stays\n * untouched so the graph state never sees `[ref: …]` / `_ref`\n * payload.\n */\n const registry = context?.getOrCreateToolOutputRegistry();\n const runId = config?.configurable?.run_id as string | undefined;\n const messagesForProvider = annotateMessagesForLLM(messages, registry, runId);\n\n /**\n * Stamp the provider that is ACTUALLY serving this invocation onto the\n * callback metadata. `attemptInvoke` is the single funnel for primary,\n * fallback, and summarization model calls, so consumers that need\n * provider attribution per call (the subagent usage-capture handler)\n * read this key instead of trusting static agent config — which is\n * wrong for fallback-served calls — or `ls_provider` — which derived\n * providers inherit from their base class.\n */\n config = {\n ...config,\n metadata: {\n ...(config?.metadata ?? {}),\n [Constants.INVOKED_PROVIDER]: provider,\n },\n };\n\n if (model.stream) {\n const stream = await model.stream(messagesForProvider, config);\n let finalChunk: AIMessageChunk | undefined;\n const registeredStreamHandler =\n getRegisteredDefaultChatStreamHandler(context);\n\n if (onChunk) {\n for await (const chunk of stream) {\n await onChunk(chunk);\n finalChunk = appendStreamChunk({\n current: finalChunk,\n next: chunk,\n provider,\n });\n }\n } else if (registeredStreamHandler == null) {\n const metadata = config.metadata as Record<string, unknown> | undefined;\n const streamHandler = new ChatModelStreamHandler();\n for await (const chunk of stream) {\n const handlingChunk = getStreamHandlingChunk({\n current: finalChunk,\n next: chunk,\n provider,\n });\n if (handlingChunk != null) {\n await streamHandler.handle(\n GraphEvents.CHAT_MODEL_STREAM,\n { chunk: handlingChunk },\n metadata,\n context\n );\n }\n finalChunk = appendStreamChunk({\n current: finalChunk,\n next: chunk,\n provider,\n });\n }\n } else {\n const metadata = config.metadata as Record<string, unknown> | undefined;\n for await (const chunk of stream) {\n const handlingChunk = getStreamHandlingChunk({\n current: finalChunk,\n next: chunk,\n provider,\n });\n if (handlingChunk != null && handlingChunk !== chunk) {\n await registeredStreamHandler.handle(\n GraphEvents.CHAT_MODEL_STREAM,\n { chunk: handlingChunk },\n metadata,\n context\n );\n }\n finalChunk = appendStreamChunk({\n current: finalChunk,\n next: chunk,\n provider,\n });\n }\n }\n\n if (manualToolStreamProviders.has(provider)) {\n finalChunk = modifyDeltaProperties(provider, finalChunk);\n }\n\n if ((finalChunk?.tool_calls?.length ?? 0) > 0) {\n finalChunk!.tool_calls = finalChunk!.tool_calls?.filter(\n (tool_call: ToolCall) => !!tool_call.name\n );\n }\n\n return { messages: [finalChunk as AIMessageChunk] };\n }\n\n const finalMessage = await model.invoke(messagesForProvider, config);\n if ((finalMessage.tool_calls?.length ?? 0) > 0) {\n finalMessage.tool_calls = finalMessage.tool_calls?.filter(\n (tool_call: ToolCall) => !!tool_call.name\n );\n }\n return { messages: [finalMessage] };\n}\n\n/**\n * Best-effort read of the configured model name from client options.\n * Providers disagree on the key (`model` vs `modelName`).\n */\nfunction extractClientOptionsModel(\n clientOptions: t.ClientOptions | undefined\n): string | undefined {\n const options = clientOptions as\n | { model?: unknown; modelName?: unknown }\n | undefined;\n if (typeof options?.model === 'string' && options.model !== '') {\n return options.model;\n }\n if (typeof options?.modelName === 'string' && options.modelName !== '') {\n return options.modelName;\n }\n return undefined;\n}\n\n/**\n * Attempts each fallback provider in order until one succeeds.\n * Throws the last error if all fallbacks fail.\n */\nexport async function tryFallbackProviders({\n fallbacks,\n tools,\n messages,\n config,\n primaryError,\n context,\n onChunk,\n}: {\n fallbacks: Array<{ provider: Providers; clientOptions?: t.ClientOptions }>;\n tools?: t.GraphTools;\n messages: BaseMessage[];\n config?: RunnableConfig;\n primaryError: unknown;\n context?: InvokeContext;\n onChunk?: OnChunk;\n}): Promise<Partial<t.BaseGraphState> | undefined> {\n let lastError: unknown = primaryError;\n for (const fb of fallbacks) {\n try {\n const fbModel = initializeModel({\n provider: fb.provider,\n clientOptions: fb.clientOptions,\n tools,\n });\n /**\n * Stamp the fallback's configured model onto callback metadata so\n * per-call attribution (subagent usage capture) doesn't fall back to\n * the PRIMARY config's model when the provider reports no\n * `ls_model_name`. The serving provider is stamped uniformly by\n * `attemptInvoke` (`INVOKED_PROVIDER`).\n */\n const fbModelName = extractClientOptionsModel(fb.clientOptions);\n const fbConfig: RunnableConfig | undefined =\n fbModelName == null\n ? config\n : {\n ...config,\n metadata: {\n ...(config?.metadata ?? {}),\n [Constants.INVOKED_MODEL]: fbModelName,\n },\n };\n const result = await attemptInvoke(\n {\n model: fbModel as t.ChatModel,\n messages,\n provider: fb.provider,\n context,\n onChunk,\n },\n fbConfig\n );\n return result;\n } catch (e) {\n lastError = e;\n continue;\n }\n }\n if (lastError !== undefined) {\n throw lastError;\n }\n return undefined;\n}\n"],"mappings":"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"}
package/dist/esm/main.mjs CHANGED
@@ -2,17 +2,18 @@ import { isPresent, unescapeObject } from "./utils/misc.mjs";
2
2
  import { ANTHROPIC_TOOL_TOKEN_MULTIPLIER, DEFAULT_TOOL_TOKEN_MULTIPLIER } from "./common/constants.mjs";
3
3
  import { CODE_EXECUTION_TOOLS, Callback, CommonEvents, Constants, ContentTypes, EnvVar, GraphEvents, GraphNodeActions, GraphNodeKeys, LOCAL_CODING_BUNDLE_NAMES, LOCAL_CODING_TOOL_NAMES, Providers, StepTypes, TitleMethod, ToolCallTypes } from "./common/enum.mjs";
4
4
  import "./common/index.mjs";
5
- import { TokenEncoderManager, createTokenCounter, encodingForModel, estimateAnthropicImageTokens, estimateOpenAIImageTokens, extractImageDimensions, getTokenCountForMessage } from "./utils/tokens.mjs";
5
+ import { TokenEncoderManager, apportionTokenCounts, createTokenCounter, encodingForModel, estimateAnthropicImageTokens, estimateOpenAIImageTokens, extractImageDimensions, getTokenCountForMessage } from "./utils/tokens.mjs";
6
6
  import { convertMessagesToContent, findLastIndex, formatAnthropicArtifactContent, formatAnthropicMessage, formatArtifactPayload, getConverseOverrideMessage, modifyDeltaProperties } from "./messages/core.mjs";
7
7
  import { getMessageId } from "./messages/ids.mjs";
8
8
  import { HARD_MAX_TOOL_RESULT_CHARS, HARD_MAX_TOTAL_TOOL_OUTPUT_SIZE, calculateMaxToolResultChars, calculateMaxTotalToolOutputSize, truncateToolInput, truncateToolResultContent } from "./utils/truncation.mjs";
9
9
  import { DEFAULT_CONTEXT_PRUNING_SETTINGS, resolveContextPruningSettings } from "./messages/contextPruningSettings.mjs";
10
10
  import { applyContextPruning } from "./messages/contextPruning.mjs";
11
11
  import { DEFAULT_RESERVE_RATIO, ORIGINAL_CONTENT_MAX_CHARS, calculateTotalTokens, checkValidNumber, createPruneMessages, enforceOriginalContentCap, getMessagesWithinTokenLimit, maskConsumedToolResults, preFlightTruncateToolCallInputs, preFlightTruncateToolResults, repairOrphanedToolMessages, sanitizeOrphanToolBlocks } from "./messages/prune.mjs";
12
+ import { syncBudgetDerivedFields } from "./messages/budget.mjs";
12
13
  import { ensureThinkingBlockInMessages, formatAgentMessages, formatFromLangChain, formatLangChainMessages, formatMediaMessage, formatMessage, labelContentByAgent, shiftIndexTokenCountMap, withMessageRole } from "./messages/format.mjs";
13
- import { addBedrockCacheControl, addCacheControl, addCacheControlToStablePrefixMessages, stripAnthropicCacheControl, stripBedrockCacheControl } from "./messages/cache.mjs";
14
+ import { addBedrockCacheControl, addCacheControl, addCacheControlToStablePrefixMessages, cloneMessage, stripAnthropicCacheControl, stripBedrockCacheControl } from "./messages/cache.mjs";
14
15
  import { makeIsDeferred, partitionAndMarkAnthropicToolCache } from "./messages/anthropicToolCache.mjs";
15
- import { formatContentStrings } from "./messages/content.mjs";
16
+ import { formatContentStrings, isLegacyConvertible } from "./messages/content.mjs";
16
17
  import { extractToolDiscoveries, hasToolSearchInCurrentTurn } from "./messages/tools.mjs";
17
18
  import { REMOVE_ALL_MESSAGES, createRemoveAllMessage, messagesStateReducer } from "./messages/reducer.mjs";
18
19
  import { splitAtRecencyBoundary } from "./messages/recency.mjs";
@@ -75,6 +76,7 @@ import { MultiAgentGraph } from "./graphs/MultiAgentGraph.mjs";
75
76
  import { Run, defaultOmitOptions } from "./run.mjs";
76
77
  import { SEPARATORS, SplitStreamHandler } from "./splitStream.mjs";
77
78
  import "./graphs/index.mjs";
79
+ import { projectAgentContextUsage } from "./agents/projection.mjs";
78
80
  import { Calculator, CalculatorSchema, CalculatorToolDefinition, CalculatorToolDescription, CalculatorToolName } from "./tools/Calculator.mjs";
79
81
  import { SkillToolDefinition, SkillToolDescription, SkillToolName, SkillToolSchema } from "./tools/SkillTool.mjs";
80
82
  import { ReadFileToolDefinition, ReadFileToolDescription, ReadFileToolName, ReadFileToolSchema } from "./tools/ReadFile.mjs";
@@ -96,4 +98,4 @@ import { Runnable, RunnableLambda, RunnableSequence } from "./langchain/runnable
96
98
  import { DynamicStructuredTool, StructuredTool, Tool, tool } from "./langchain/tools.mjs";
97
99
  import "./langchain/index.mjs";
98
100
  import { BaseCheckpointSaver, Command, INTERRUPT, MemorySaver, interrupt, isInterrupted } from "@langchain/langgraph";
99
- export { AIMessage, AIMessageChunk, ANTHROPIC_TOOL_TOKEN_MULTIPLIER, AgentSession, BASH_SHELL_GUIDANCE, BaseCheckpointSaver, BaseMessage, BaseMessageChunk, BashExecutionToolDefinition, BashExecutionToolDescription, BashExecutionToolName, BashExecutionToolSchema, BashProgrammaticToolCallingDefinition, BashProgrammaticToolCallingDescription, BashProgrammaticToolCallingName, BashProgrammaticToolCallingSchema, BashToolOutputReferencesGuide, CLOUDFLARE_BASH_CODING_TOOL_NAMES, CLOUDFLARE_CODING_TOOL_NAMES, CODE_ARTIFACT_PATH_GUIDANCE, CODE_EXECUTION_TOOLS, Calculator, CalculatorSchema, CalculatorToolDefinition, CalculatorToolDescription, CalculatorToolName, Callback, ChatModelStreamHandler, ChatOpenRouter, CloudflareBashExecutionToolDescription, CloudflareCodeExecutionToolDescription, CodeExecutionToolDefinition, CodeExecutionToolDescription, CodeExecutionToolName, CodeExecutionToolSchema, Command, CommonEvents, CompileCheckToolName, Constants, ContentTypes, CustomOpenAIClient, DATE_RANGE, DEFAULT_CONTEXT_PRUNING_SETTINGS, DEFAULT_COUNTRY_DESCRIPTION, DEFAULT_HOOK_TIMEOUT_MS, DEFAULT_QUERY_DESCRIPTION, DEFAULT_RESERVE_RATIO, DEFAULT_TOOL_TOKEN_MULTIPLIER, DynamicStructuredTool, EnvVar, FAILED_EXECUTION_FILE_REMINDER, FakeChatModel, Graph, GraphEvents, GraphNodeActions, GraphNodeKeys, HARD_MAX_TOOL_RESULT_CHARS, HARD_MAX_TOTAL_TOOL_OUTPUT_SIZE, HOOK_EVENTS, HandlerRegistry, HookRegistry, HumanMessage, INTERRUPT, JsonlSessionStore, LLMStreamHandler, LOCAL_CODING_BUNDLE_NAMES, LOCAL_CODING_TOOL_NAMES, LOCAL_SPAWN_TIMEOUT_MS, LocalBashExecutionToolDescription, LocalCodeExecutionToolDescription, LocalEditFileToolName, LocalEditFileToolSchema, LocalFileCheckpointerImpl, LocalGlobSearchToolName, LocalGlobSearchToolSchema, LocalGrepSearchToolName, LocalGrepSearchToolSchema, LocalListDirectoryToolName, LocalListDirectoryToolSchema, LocalReadFileToolSchema, LocalWriteFileToolName, LocalWriteFileToolSchema, MAX_CACHE_SIZE, MAX_PATTERN_LENGTH, MemorySaver, ModelEndHandler, MultiAgentGraph, ORIGINAL_CONTENT_MAX_CHARS, ProgrammaticToolCallingDefinition, ProgrammaticToolCallingDescription, ProgrammaticToolCallingName, ProgrammaticToolCallingSchema, PromptTemplate, Providers, REMOVE_ALL_MESSAGES, ReadFileToolDefinition, ReadFileToolDescription, ReadFileToolName, ReadFileToolSchema, Run, Runnable, RunnableCallable, RunnableLambda, RunnableSequence, SEPARATORS, SessionManager, SkillToolDefinition, SkillToolDescription, SkillToolName, SkillToolSchema, SplitStreamHandler, StandardGraph, StepTypes, StructuredTool, SubagentExecutor, SubagentToolDefinition, SubagentToolDescription, SubagentToolName, SubagentToolSchema, SystemMessage, TMP_SCRATCH_OUTPUT_REMINDER, TestChatStreamHandler, TestLLMStreamHandler, TitleMethod, TokenEncoderManager, Tool, ToolCallTypes, ToolEndHandler, ToolMessage, ToolNode, ToolSearchToolDefinition, ToolSearchToolDescription, ToolSearchToolName, ToolSearchToolSchema, WebSearchToolDefinition, WebSearchToolDescription, WebSearchToolName, WebSearchToolSchema, _createBashProgramForTests, _resetLocalEngineWarningsForTests, _resetRipgrepCacheForTests, _resetSyntaxCheckProbeCacheForTests, _resetUnrecognizedTriggerWarnings, addBedrockCacheControl, addCacheControl, addCacheControlToStablePrefixMessages, appendCodeSessionFileSummary, appendFailedExecutionFileReminder, appendTmpScratchReminder, applyContextPruning, applyEdit, applyPreToolUseHooksForBridge, askUserQuestion, attemptInvoke, bashAstFindingsToErrors, buildBashExecutionToolDescription, buildChildInputs, buildCodeApiHttpErrorMessage, buildSandboxRuntimeConfig, buildSubagentToolParams, calculateMaxToolResultChars, calculateMaxTotalToolOutputSize, calculateTotalTokens, checkValidNumber, classifyAttachment, composeEventHandlers, convertMessagesToContent, countNestedGroups, countrySchema, createAgentSession, createBashExecutionTool, createBashProgrammaticToolCallingSchema, createBashProgrammaticToolCallingTool, createCloudflareBashExecutionTool, createCloudflareBashProgrammaticToolCallingTool, createCloudflareBridgeRuntime, createCloudflareCodeExecutionTool, createCloudflareCodingToolBundle, createCloudflareCodingTools, createCloudflareExecutionTool, createCloudflareLocalExecutionConfig, createCloudflareProgrammaticToolCallingTool, createCloudflareWorkspaceFS, createCodeExecutionTool, createCompileCheckTool, createCompileCheckToolDefinition, createContentAggregator, createFakeStreamingLLM, createHandlers, createLocalBashExecutionTool, createLocalBashProgrammaticToolCallingTool, createLocalCodeExecutionTool, createLocalCodingToolBundle, createLocalCodingToolDefinitions, createLocalCodingToolRegistry, createLocalCodingTools, createLocalEditFileTool, createLocalFileCheckpointer, createLocalGlobSearchTool, createLocalGrepSearchTool, createLocalListDirectoryTool, createLocalProgrammaticToolCallingTool, createLocalReadFileTool, createLocalWriteFileTool, createMetadataAggregator, createProgrammaticToolCallingSchema, createProgrammaticToolCallingTool, createPruneMessages, createRemoveAllMessage, createRunHandlers, createSchemaOnlyTool, createSchemaOnlyTools, createSearchTool, createSubagentToolDefinition, createTokenCounter, createToolPolicyHook, createToolSearch, createWorkspacePolicyHook, dateSchema, decodeFile, defaultOmitOptions, deserializeMessage, emptyOutputMessage, encodeFile, encodingForModel, enforceOriginalContentCap, ensureThinkingBlockInMessages, escapeRegexSpecialChars, estimateAnthropicImageTokens, estimateOpenAIImageTokens, executeCloudflareBash, executeCloudflareCode, executeHooks, executeLocalBash, executeLocalBashWithArgs, executeLocalCode, executeParallelSearches, executeTools, extractErrorMessage, extractImageDimensions, extractMcpServerName, extractTextFromContent, extractToolDiscoveries, extractUsedBashToolNames, extractUsedToolNames, fetchSessionFiles, filterBashToolsByUsage, filterSubagentResult, filterToolsByUsage, findLastIndex, formatAgentMessages, formatAnthropicArtifactContent, formatAnthropicMessage, formatArtifactPayload, formatCloudflareOutput, formatCompletedResponse, formatContentStrings, formatFromLangChain, formatLangChainMessages, formatMediaMessage, formatMessage, formatServerListing, formatSkillCatalog, getAvailableMcpServers, getBaseToolName, getBufferString, getChatModelClass, getChunkContent, getCloudflareWorkspaceRoot, getCodeBaseURL, getConverseOverrideMessage, getDeferredToolsListing, getLocalCwd, getLocalSessionId, getMaxOutputTokensKey, getMessageId, getMessagesWithinTokenLimit, getReadRoots, getSpawn, getTokenCountForMessage, getWorkspaceFS, getWorkspaceRoots, getWriteRoots, handleServerToolResult, handleToolCallChunks, handleToolCalls, hasNestedQuantifier, hasNestedQuantifiers, hasToolSearchInCurrentTurn, imageAttachmentContent, imagesSchema, initializeModel, interrupt, isAIMessage, isAnthropicLike, isBaseMessage, isContextOverflowError, isDangerousPattern, isFromAnyMcpServer, isFromMcpServer, isGoogleLike, isInterrupted, isLikelyContextOverflowError, isOpenAILike, isPresent, isThinkingEnabled, isToolMessage, isZodSchema, joinKeys, labelContentByAgent, locateEdit, makeIsDeferred, makeRequest, maskConsumedToolResults, matchesQuery, messagesStateReducer, modifyDeltaProperties, newsSchema, normalizeBashToolResultsForReplay, normalizeServerFilter, normalizeToBashIdentifier, normalizeToPythonIdentifier, partitionAndMarkAnthropicToolCache, performLocalSearch, preFlightTruncateToolCallInputs, preFlightTruncateToolResults, querySchema, repairOrphanedToolMessages, resetIfNotEmpty, resolveCloudflareSandbox, resolveCodeApiAuthHeaders, resolveContextPruningSettings, resolveLocalExecutionConfig, resolveLocalExecutionTools, resolveLocalToolRegistry, resolveLocalToolsForBinding, resolveSubagentConfigs, resolveWorkspacePath, resolveWorkspacePathSafe, runBashAstChecks, runPostEditSyntaxCheck, sanitizeOrphanToolBlocks, sanitizeRegex, serializeMessage, shellQuote, shiftIndexTokenCountMap, shouldTriggerSummarization, sleep, spawnLocalProcess, splitAtRecencyBoundary, stripAnthropicCacheControl, stripBedrockCacheControl, stripCodeSessionFileSummary, summarizeEvent, toJsonSchema, tool, toolResultTypes, toolsCondition, truncateLocalOutput, truncateToolInput, truncateToolResultContent, tryFallbackProviders, unescapeObject, unwrapToolResponse, validateBashCommand, validateCloudflareBashCommand, videosSchema, withMessageRole };
101
+ export { AIMessage, AIMessageChunk, ANTHROPIC_TOOL_TOKEN_MULTIPLIER, AgentSession, BASH_SHELL_GUIDANCE, BaseCheckpointSaver, BaseMessage, BaseMessageChunk, BashExecutionToolDefinition, BashExecutionToolDescription, BashExecutionToolName, BashExecutionToolSchema, BashProgrammaticToolCallingDefinition, BashProgrammaticToolCallingDescription, BashProgrammaticToolCallingName, BashProgrammaticToolCallingSchema, BashToolOutputReferencesGuide, CLOUDFLARE_BASH_CODING_TOOL_NAMES, CLOUDFLARE_CODING_TOOL_NAMES, CODE_ARTIFACT_PATH_GUIDANCE, CODE_EXECUTION_TOOLS, Calculator, CalculatorSchema, CalculatorToolDefinition, CalculatorToolDescription, CalculatorToolName, Callback, ChatModelStreamHandler, ChatOpenRouter, CloudflareBashExecutionToolDescription, CloudflareCodeExecutionToolDescription, CodeExecutionToolDefinition, CodeExecutionToolDescription, CodeExecutionToolName, CodeExecutionToolSchema, Command, CommonEvents, CompileCheckToolName, Constants, ContentTypes, CustomOpenAIClient, DATE_RANGE, DEFAULT_CONTEXT_PRUNING_SETTINGS, DEFAULT_COUNTRY_DESCRIPTION, DEFAULT_HOOK_TIMEOUT_MS, DEFAULT_QUERY_DESCRIPTION, DEFAULT_RESERVE_RATIO, DEFAULT_TOOL_TOKEN_MULTIPLIER, DynamicStructuredTool, EnvVar, FAILED_EXECUTION_FILE_REMINDER, FakeChatModel, Graph, GraphEvents, GraphNodeActions, GraphNodeKeys, HARD_MAX_TOOL_RESULT_CHARS, HARD_MAX_TOTAL_TOOL_OUTPUT_SIZE, HOOK_EVENTS, HandlerRegistry, HookRegistry, HumanMessage, INTERRUPT, JsonlSessionStore, LLMStreamHandler, LOCAL_CODING_BUNDLE_NAMES, LOCAL_CODING_TOOL_NAMES, LOCAL_SPAWN_TIMEOUT_MS, LocalBashExecutionToolDescription, LocalCodeExecutionToolDescription, LocalEditFileToolName, LocalEditFileToolSchema, LocalFileCheckpointerImpl, LocalGlobSearchToolName, LocalGlobSearchToolSchema, LocalGrepSearchToolName, LocalGrepSearchToolSchema, LocalListDirectoryToolName, LocalListDirectoryToolSchema, LocalReadFileToolSchema, LocalWriteFileToolName, LocalWriteFileToolSchema, MAX_CACHE_SIZE, MAX_PATTERN_LENGTH, MemorySaver, ModelEndHandler, MultiAgentGraph, ORIGINAL_CONTENT_MAX_CHARS, ProgrammaticToolCallingDefinition, ProgrammaticToolCallingDescription, ProgrammaticToolCallingName, ProgrammaticToolCallingSchema, PromptTemplate, Providers, REMOVE_ALL_MESSAGES, ReadFileToolDefinition, ReadFileToolDescription, ReadFileToolName, ReadFileToolSchema, Run, Runnable, RunnableCallable, RunnableLambda, RunnableSequence, SEPARATORS, SessionManager, SkillToolDefinition, SkillToolDescription, SkillToolName, SkillToolSchema, SplitStreamHandler, StandardGraph, StepTypes, StructuredTool, SubagentExecutor, SubagentToolDefinition, SubagentToolDescription, SubagentToolName, SubagentToolSchema, SystemMessage, TMP_SCRATCH_OUTPUT_REMINDER, TestChatStreamHandler, TestLLMStreamHandler, TitleMethod, TokenEncoderManager, Tool, ToolCallTypes, ToolEndHandler, ToolMessage, ToolNode, ToolSearchToolDefinition, ToolSearchToolDescription, ToolSearchToolName, ToolSearchToolSchema, WebSearchToolDefinition, WebSearchToolDescription, WebSearchToolName, WebSearchToolSchema, _createBashProgramForTests, _resetLocalEngineWarningsForTests, _resetRipgrepCacheForTests, _resetSyntaxCheckProbeCacheForTests, _resetUnrecognizedTriggerWarnings, addBedrockCacheControl, addCacheControl, addCacheControlToStablePrefixMessages, appendCodeSessionFileSummary, appendFailedExecutionFileReminder, appendTmpScratchReminder, applyContextPruning, applyEdit, applyPreToolUseHooksForBridge, apportionTokenCounts, askUserQuestion, attemptInvoke, bashAstFindingsToErrors, buildBashExecutionToolDescription, buildChildInputs, buildCodeApiHttpErrorMessage, buildSandboxRuntimeConfig, buildSubagentToolParams, calculateMaxToolResultChars, calculateMaxTotalToolOutputSize, calculateTotalTokens, checkValidNumber, classifyAttachment, cloneMessage, composeEventHandlers, convertMessagesToContent, countNestedGroups, countrySchema, createAgentSession, createBashExecutionTool, createBashProgrammaticToolCallingSchema, createBashProgrammaticToolCallingTool, createCloudflareBashExecutionTool, createCloudflareBashProgrammaticToolCallingTool, createCloudflareBridgeRuntime, createCloudflareCodeExecutionTool, createCloudflareCodingToolBundle, createCloudflareCodingTools, createCloudflareExecutionTool, createCloudflareLocalExecutionConfig, createCloudflareProgrammaticToolCallingTool, createCloudflareWorkspaceFS, createCodeExecutionTool, createCompileCheckTool, createCompileCheckToolDefinition, createContentAggregator, createFakeStreamingLLM, createHandlers, createLocalBashExecutionTool, createLocalBashProgrammaticToolCallingTool, createLocalCodeExecutionTool, createLocalCodingToolBundle, createLocalCodingToolDefinitions, createLocalCodingToolRegistry, createLocalCodingTools, createLocalEditFileTool, createLocalFileCheckpointer, createLocalGlobSearchTool, createLocalGrepSearchTool, createLocalListDirectoryTool, createLocalProgrammaticToolCallingTool, createLocalReadFileTool, createLocalWriteFileTool, createMetadataAggregator, createProgrammaticToolCallingSchema, createProgrammaticToolCallingTool, createPruneMessages, createRemoveAllMessage, createRunHandlers, createSchemaOnlyTool, createSchemaOnlyTools, createSearchTool, createSubagentToolDefinition, createTokenCounter, createToolPolicyHook, createToolSearch, createWorkspacePolicyHook, dateSchema, decodeFile, defaultOmitOptions, deserializeMessage, emptyOutputMessage, encodeFile, encodingForModel, enforceOriginalContentCap, ensureThinkingBlockInMessages, escapeRegexSpecialChars, estimateAnthropicImageTokens, estimateOpenAIImageTokens, executeCloudflareBash, executeCloudflareCode, executeHooks, executeLocalBash, executeLocalBashWithArgs, executeLocalCode, executeParallelSearches, executeTools, extractErrorMessage, extractImageDimensions, extractMcpServerName, extractTextFromContent, extractToolDiscoveries, extractUsedBashToolNames, extractUsedToolNames, fetchSessionFiles, filterBashToolsByUsage, filterSubagentResult, filterToolsByUsage, findLastIndex, formatAgentMessages, formatAnthropicArtifactContent, formatAnthropicMessage, formatArtifactPayload, formatCloudflareOutput, formatCompletedResponse, formatContentStrings, formatFromLangChain, formatLangChainMessages, formatMediaMessage, formatMessage, formatServerListing, formatSkillCatalog, getAvailableMcpServers, getBaseToolName, getBufferString, getChatModelClass, getChunkContent, getCloudflareWorkspaceRoot, getCodeBaseURL, getConverseOverrideMessage, getDeferredToolsListing, getLocalCwd, getLocalSessionId, getMaxOutputTokensKey, getMessageId, getMessagesWithinTokenLimit, getReadRoots, getSpawn, getTokenCountForMessage, getWorkspaceFS, getWorkspaceRoots, getWriteRoots, handleServerToolResult, handleToolCallChunks, handleToolCalls, hasNestedQuantifier, hasNestedQuantifiers, hasToolSearchInCurrentTurn, imageAttachmentContent, imagesSchema, initializeModel, interrupt, isAIMessage, isAnthropicLike, isBaseMessage, isContextOverflowError, isDangerousPattern, isFromAnyMcpServer, isFromMcpServer, isGoogleLike, isInterrupted, isLegacyConvertible, isLikelyContextOverflowError, isOpenAILike, isPresent, isThinkingEnabled, isToolMessage, isZodSchema, joinKeys, labelContentByAgent, locateEdit, makeIsDeferred, makeRequest, maskConsumedToolResults, matchesQuery, messagesStateReducer, modifyDeltaProperties, newsSchema, normalizeBashToolResultsForReplay, normalizeServerFilter, normalizeToBashIdentifier, normalizeToPythonIdentifier, partitionAndMarkAnthropicToolCache, performLocalSearch, preFlightTruncateToolCallInputs, preFlightTruncateToolResults, projectAgentContextUsage, querySchema, repairOrphanedToolMessages, resetIfNotEmpty, resolveCloudflareSandbox, resolveCodeApiAuthHeaders, resolveContextPruningSettings, resolveLocalExecutionConfig, resolveLocalExecutionTools, resolveLocalToolRegistry, resolveLocalToolsForBinding, resolveSubagentConfigs, resolveWorkspacePath, resolveWorkspacePathSafe, runBashAstChecks, runPostEditSyntaxCheck, sanitizeOrphanToolBlocks, sanitizeRegex, serializeMessage, shellQuote, shiftIndexTokenCountMap, shouldTriggerSummarization, sleep, spawnLocalProcess, splitAtRecencyBoundary, stripAnthropicCacheControl, stripBedrockCacheControl, stripCodeSessionFileSummary, summarizeEvent, syncBudgetDerivedFields, toJsonSchema, tool, toolResultTypes, toolsCondition, truncateLocalOutput, truncateToolInput, truncateToolResultContent, tryFallbackProviders, unescapeObject, unwrapToolResponse, validateBashCommand, validateCloudflareBashCommand, videosSchema, withMessageRole };
@@ -0,0 +1,23 @@
1
+ //#region src/messages/budget.ts
2
+ /**
3
+ * Reconciles a context-usage breakdown's instruction/available/message fields
4
+ * from the pruner's budget metrics. `messageTokens` and `availableForMessages`
5
+ * are DERIVED from `contextBudget` / `effectiveInstructionTokens` /
6
+ * `remainingContextTokens` rather than summed from the index map — that map is
7
+ * keyed by pre-prune indices, so summing it over the kept context would missum.
8
+ * Shared by the live snapshot path (`Graph.createCallModel`) and the pre-send
9
+ * projection (`AgentContext.projectContextUsage`) so both yield identical numbers.
10
+ */
11
+ function syncBudgetDerivedFields(usage) {
12
+ const { breakdown, contextBudget, effectiveInstructionTokens } = usage;
13
+ if (effectiveInstructionTokens == null) return;
14
+ breakdown.instructionTokens = effectiveInstructionTokens;
15
+ if (contextBudget == null) return;
16
+ breakdown.availableForMessages = Math.max(0, contextBudget - effectiveInstructionTokens);
17
+ if (usage.remainingContextTokens == null) return;
18
+ breakdown.messageTokens = Math.max(0, contextBudget - effectiveInstructionTokens - usage.remainingContextTokens);
19
+ }
20
+ //#endregion
21
+ export { syncBudgetDerivedFields };
22
+
23
+ //# sourceMappingURL=budget.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"budget.mjs","names":[],"sources":["../../../src/messages/budget.ts"],"sourcesContent":["import type * as t from '@/types';\n\n/**\n * Reconciles a context-usage breakdown's instruction/available/message fields\n * from the pruner's budget metrics. `messageTokens` and `availableForMessages`\n * are DERIVED from `contextBudget` / `effectiveInstructionTokens` /\n * `remainingContextTokens` rather than summed from the index map — that map is\n * keyed by pre-prune indices, so summing it over the kept context would missum.\n * Shared by the live snapshot path (`Graph.createCallModel`) and the pre-send\n * projection (`AgentContext.projectContextUsage`) so both yield identical numbers.\n */\nexport function syncBudgetDerivedFields(usage: t.ContextUsageEvent): void {\n const { breakdown, contextBudget, effectiveInstructionTokens } = usage;\n if (effectiveInstructionTokens == null) {\n return;\n }\n breakdown.instructionTokens = effectiveInstructionTokens;\n if (contextBudget == null) {\n return;\n }\n breakdown.availableForMessages = Math.max(\n 0,\n contextBudget - effectiveInstructionTokens\n );\n if (usage.remainingContextTokens == null) {\n return;\n }\n breakdown.messageTokens = Math.max(\n 0,\n contextBudget - effectiveInstructionTokens - usage.remainingContextTokens\n );\n}\n"],"mappings":";;;;;;;;;;AAWA,SAAgB,wBAAwB,OAAkC;CACxE,MAAM,EAAE,WAAW,eAAe,+BAA+B;CACjE,IAAI,8BAA8B,MAChC;CAEF,UAAU,oBAAoB;CAC9B,IAAI,iBAAiB,MACnB;CAEF,UAAU,uBAAuB,KAAK,IACpC,GACA,gBAAgB,0BAClB;CACA,IAAI,MAAM,0BAA0B,MAClC;CAEF,UAAU,gBAAgB,KAAK,IAC7B,GACA,gBAAgB,6BAA6B,MAAM,sBACrD;AACF"}
@@ -347,6 +347,6 @@ function addBedrockCacheControl(messages) {
347
347
  return updatedMessages;
348
348
  }
349
349
  //#endregion
350
- export { addBedrockCacheControl, addCacheControl, addCacheControlToStablePrefixMessages, stripAnthropicCacheControl, stripBedrockCacheControl };
350
+ export { addBedrockCacheControl, addCacheControl, addCacheControlToStablePrefixMessages, cloneMessage, stripAnthropicCacheControl, stripBedrockCacheControl };
351
351
 
352
352
  //# sourceMappingURL=cache.mjs.map
@@ -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 * 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 */\nfunction 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\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"],"mappings":";;;;;;;;AAyBA,SAAS,iBACP,SACG;CACH,IAAI,OAAO,YAAY,UACrB,OAAO;CAET,IAAI,MAAM,QAAQ,OAAO,GACvB,OAAO,QAAQ,KAAK,WAAW,EAAE,GAAG,MAAM,EAAE;CAE9C,OAAO;AACT;;;;;;;AAQA,SAAS,aACP,SACA,SACG;CACH,IAAI,mBAAmB,aAAa;EAClC,MAAM,aAAa;GACjB,SAAS,mBAAmB,OAAO;GACnC,mBAAmB,EAAE,GAAG,QAAQ,kBAAkB;GAClD,mBAAmB,EAAE,GAAG,QAAQ,kBAAkB;GAClD,IAAI,QAAQ;GACZ,MAAM,QAAQ;EAChB;EAGA,QADgB,QAAQ,QACV,GAAd;GACA,KAAK,MACH,OAAO,gBACL,IAAI,UAAU;IACZ,GAAG;IACH,YAAa,QAAiC;GAChD,CAAC,GACD,WACF;GACF,KAAK,SACH,OAAO,gBACL,IAAI,aAAa,UAAU,GAC3B,MACF;GACF,KAAK,UACH,OAAO,gBACL,IAAI,cAAc,UAAU,GAC5B,QACF;GACF,KAAK,QACH,OAAO,gBACL,IAAI,YAAY;IACd,GAAG;IACH,cAAe,QAAmC;GACpD,CAAC,GACD,MACF;GACF,SACE;EACF;CACF;CAEA,MAAM,EACJ,WAAW,YACX,iBAAiB,kBACjB,cAAc,eACd,GAAG,SACD;CAMJ,MAAM,SAAS;EAAE,GAAG;EAAM;CAAQ;CAGlC,IACE,aAAa,WACb,OAAO,QAAQ,YAAY,cAC3B,EAAE,UAAU,SACZ;EACA,MAAM,UAAW,QAAmC,QAAQ;EAO5D,OAAoC,OAAO;GALzC,OAAO;GACP,IAAI;GACJ,QAAQ;GACR,MAAM;EAEyC,EAAE,YAAY;CACjE;CAEA,OAAO;AACT;AAEA,SAAS,qCACP,SACyD;CACzD,IAAI,WAAW;CAYf,OAAO;EAAE,SAXe,QAAQ,KAAK,UAAU;GAC7C,IAAI,EAAE,mBAAmB,QACvB,OAAO;GAGT,MAAM,SAAyC,EAAE,GAAG,MAAM;GAC1D,OAAO,OAAO;GACd,WAAW;GACX,OAAO;EACT,CAEgC;EAAG;CAAS;AAC9C;AAEA,SAAS,6BACP,SACG;CACH,MAAM,UAAU,QAAQ;CACxB,IAAI,CAAC,MAAM,QAAQ,OAAO,GACxB,OAAO;CAGT,MAAM,WAAW,qCAAqC,OAAO;CAC7D,IAAI,CAAC,SAAS,UACZ,OAAO;CAGT,OAAO,aAAa,SAAS,SAAS,OAAO;AAC/C;;;;;;;;;;AAWA,SAAgB,gBACd,UACK;CACL,IAAI,CAAC,MAAM,QAAQ,QAAQ,KAAK,SAAS,SAAS,GAChD,OAAO;CAGT,MAAM,kBAAuB,CAAC,GAAG,QAAQ;CACzC,IAAI,uBAAuB;CAE3B,KAAK,IAAI,IAAI,gBAAgB,SAAS,GAAG,KAAK,GAAG,KAAK;EACpD,MAAM,kBAAkB,gBAAgB;EACxC,MAAM,UAAU,gBAAgB;EAChC,MAAM,gBACH,aAAa,mBAAmB,gBAAgB,QAAQ,MAAM,WAC9D,UAAU,mBAAmB,gBAAgB,SAAS;EACzD,MAAM,kBAAkB,MAAM,QAAQ,OAAO;EAC7C,MAAM,gBACJ,uBAAuB,KACvB,iBACA,CAAC,uBAAuB,eAAe,MACtC,OAAO,YAAY,YAAY;EAGlC,IAAI,CAAC,iBAAiB,CAAC,iBACrB;EAGF,IAAI;EACJ,IAAI,WAAW;EAEf,IAAI,iBAAiB;GAGnB,MAAM,MAAM;GACZ,iBAAiB,CAAC;GAClB,IAAI,gBAAgB;GACpB,KAAK,IAAI,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;IACnC,MAAM,QAAQ,IAAI;IAClB,IAAI,aAAa,KAAK,GAAG;KACvB,WAAW;KACX;IACF;IACA,MAAM,SAAS,EAAE,GAAG,MAAM;IAC1B,IAAI,mBAAmB,QAAQ;KAC7B,OAAQ,OAAmC;KAC3C,WAAW;IACb;IACA,IAAI,UAAU,UAAU,OAAO,SAAS,QACtC,gBAAgB,eAAe;IAEjC,eAAe,KAAK,MAA+B;GACrD;GAEA,IAAI,CAAC,YAAY,CAAC,eAChB;GAIF,IAAI,iBAAiB,iBAAiB,GAAG;IACvC,eACiB,cAAc,CAC7B,gBAAgB,EAChB,MAAM,YACR;IACA;GACF;EACF,OAAO,IAAI,OAAO,YAAY,YAAY,eAAe;GACvD,iBAAiB,CACf;IAAE,MAAM;IAAQ,MAAM;IAAS,eAAe,EAAE,MAAM,YAAY;GAAE,CACtE;GACA;EACF,OACE;EAGF,gBAAgB,KAAK,aACnB,iBACA,cACF;CACF;CAEA,OAAO;AACT;;;;AAKA,SAAS,aAAa,OAAuC;CAC3D,OAAO,gBAAgB,SAAS,EAAE,UAAU;AAC9C;AAEA,SAAS,eAAe,SAAiD;CACvE,IAAI,mBAAmB,aACrB,OAAO,QAAQ,QAAQ;CAEzB,IAAI,UAAU,WAAW,OAAO,QAAQ,SAAS,UAC/C,OAAO,QAAQ;AAGnB;AAEA,MAAM,uBAAuB;;;;;;;;;AAU7B,SAAS,uBAAuB,SAAsC;CACpE,MAAM,EAAE,mBAAmB,WAAW;CAGtC,IAAI,UAAU,MACZ,OAAO;CAET,OAAO,OAAO,WAAW,QAAQ,OAAO,WAAW;AACrD;AAEA,SAAS,+BAA+B,SAAsC;CAC5E,MAAM,OAAO,eAAe,OAAO;CACnC,OACE,SAAS,WAAW,SAAS,UAAU,SAAS,QAAQ,SAAS;AAErE;AAEA,SAAS,+BAA+B,SAAsC;CAC5E,MAAM,OAAO,eAAe,OAAO;CACnC,OAAO,SAAS,QAAQ,SAAS;AACnC;AAEA,SAAS,eAAe,SAAsC;CAC5D,OACE,MAAM,QAAQ,QAAQ,OAAO,KAC7B,QAAQ,QAAQ,MAAM,UAAU,mBAAmB,KAAK;AAE5D;AAEA,SAAS,gCAGP,UACA,gBACA,eACK;CACL,IACE,CAAC,MAAM,QAAQ,QAAQ,KACvB,SAAS,WAAW,KACpB,kBAAkB,GAElB,OAAO;CAGT,MAAM,kBAAuB,CAAC,GAAG,QAAQ;CACzC,IAAI,mBAAmB;CAEvB,KAAK,IAAI,IAAI,gBAAgB,SAAS,GAAG,KAAK,GAAG,KAAK;EACpD,MAAM,kBAAkB,gBAAgB;EACxC,MAAM,UAAU,gBAAgB;EAChC,MAAM,kBAAkB,MAAM,QAAQ,OAAO;EAC7C,MAAM,cACJ,mBAAmB,kBACnB,cAAc,eAAe,KAC7B,CAAC,uBAAuB,eAAe;EAEzC,IAAI,CAAC,eAAe,CAAC,iBACnB;EAGF,IAAI;EACJ,IAAI,WAAW;EAEf,IAAI,iBAAiB;GACnB,MAAM,MAAM;GACZ,iBAAiB,CAAC;GAClB,IAAI,wBAAwB;GAE5B,KAAK,IAAI,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;IACnC,MAAM,QAAQ,IAAI;IAClB,IAAI,aAAa,KAAK,GAAG;KACvB,WAAW;KACX;IACF;IAEA,MAAM,SAAS,EAAE,GAAG,MAAM;IAC1B,IAAI,mBAAmB,QAAQ;KAC7B,OAAQ,OAAmC;KAC3C,WAAW;IACb;IAEA,IAAI,UAAU,UAAU,OAAO,SAAS,QAAQ;KAC9C,MAAM,OAAQ,OAA6B;KAC3C,IAAI,QAAQ,QAAQ,KAAK,KAAK,MAAM,IAClC,wBAAwB,eAAe;IAE3C;IACA,eAAe,KAAK,MAA+B;GACrD;GAEA,IAAI,eAAe,yBAAyB,GAAG;IAC7C,eACiB,sBAAsB,CACrC,gBAAgB,EAChB,MAAM,YACR;IACA;IACA,WAAW;GACb;GAEA,IAAI,CAAC,UACH;EAEJ,OAAO,IACL,OAAO,YAAY,YACnB,QAAQ,KAAK,MAAM,MACnB,aACA;GACA,iBAAiB,CACf;IAAE,MAAM;IAAQ,MAAM;IAAS,eAAe,EAAE,MAAM,YAAY;GAAE,CACtE;GACA;EACF,OACE;EAGF,gBAAgB,KAAK,aACnB,iBACA,cACF;CACF;CAEA,OAAO;AACT;AAEA,SAAgB,sCAEd,UAAe,gBAA6B;CAC5C,MAAM,kBAAkB,gCACtB,UACA,gBACA,8BACF;CAEA,IAAI,gBAAgB,KAAK,cAAc,GACrC,OAAO;CAGT,OAAO,gCACL,UACA,gBACA,8BACF;AACF;;;;AAKA,SAAS,yBAAyB,SAA2C;CAC3E,KAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAClC,IAAI,mBAAmB,QAAQ,IAAI,OAAO;CAE5C,OAAO;AACT;;;;;;AAOA,SAAgB,2BACd,UACK;CACL,IAAI,CAAC,MAAM,QAAQ,QAAQ,GACzB,OAAO;CAGT,MAAM,kBAAuB,CAAC,GAAG,QAAQ;CAEzC,KAAK,IAAI,IAAI,GAAG,IAAI,gBAAgB,QAAQ,KAAK;EAC/C,MAAM,kBAAkB,gBAAgB;EACxC,MAAM,UAAU,gBAAgB;EAEhC,IAAI,CAAC,MAAM,QAAQ,OAAO,KAAK,CAAC,yBAAyB,OAAO,GAC9D;EAGF,MAAM,gBAAgB,iBAAiB,OAAO;EAC9C,KAAK,IAAI,IAAI,GAAG,IAAI,cAAc,QAAQ,KAAK;GAC7C,MAAM,QAAQ,cAAc;GAC5B,IAAI,mBAAmB,OACrB,OAAO,MAAM;EAEjB;EACA,gBAAgB,KAAK,aAAa,iBAAiB,aAAa;CAClE;CAEA,OAAO;AACT;;;;AAKA,SAAS,qBAAqB,SAA2C;CACvE,KAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAClC,IAAI,aAAa,QAAQ,EAAE,GAAG,OAAO;CAEvC,OAAO;AACT;;;;;;AAOA,SAAgB,yBACd,UACK;CACL,IAAI,CAAC,MAAM,QAAQ,QAAQ,GACzB,OAAO;CAGT,MAAM,kBAAuB,CAAC,GAAG,QAAQ;CAEzC,KAAK,IAAI,IAAI,GAAG,IAAI,gBAAgB,QAAQ,KAAK;EAC/C,MAAM,kBAAkB,gBAAgB;EACxC,MAAM,UAAU,gBAAgB;EAEhC,IAAI,CAAC,MAAM,QAAQ,OAAO,KAAK,CAAC,qBAAqB,OAAO,GAC1D;EAMF,gBAAgB,KAAK,aAAa,iBAHZ,iBAAiB,OAAO,CAAC,CAAC,QAC7C,UAAU,CAAC,aAAa,KAA8B,CAEM,CAAC;CAClE;CAEA,OAAO;AACT;;;;;;;;;;;;AAaA,SAAgB,uBAEd,UAAoB;CACpB,IAAI,CAAC,MAAM,QAAQ,QAAQ,KAAK,SAAS,WAAW,GAClD,OAAO;CAGT,MAAM,kBAAuB,CAAC,GAAG,QAAQ;CACzC,IAAI,mBAAmB;CAEvB,KAAK,IAAI,IAAI,gBAAgB,SAAS,GAAG,KAAK,GAAG,KAAK;EACpD,MAAM,kBAAkB,gBAAgB;EACxC,MAAM,cACJ,aAAa,mBACb,OAAO,gBAAgB,YAAY,aAC/B,gBAAgB,QAAQ,IACxB,KAAA;EACN,MAAM,cACJ,UAAU,mBAAmB,OAAO,gBAAgB,SAAS,WACzD,gBAAgB,OAChB,KAAA;EAIN,IADE,gBAAgB,YAAY,gBAAgB,UACzB;GACnB,gBAAgB,KAAK,6BAA6B,eAAe;GACjE;EACF;EAEA,MAAM,gBAAgB,gBAAgB,UAAU,gBAAgB;EAChE,MAAM,gBAAgB,gBAAgB,WAAW,gBAAgB;EACjE,MAAM,UAAU,gBAAgB;EAChC,MAAM,wBACJ,eAAe,mBACf,qBAAqB,mBACrB,kBAAkB;EACpB,MAAM,kBAAkB,MAAM,QAAQ,OAAO;EAE7C,MAAM,gBACJ,mBAAmB,KACnB,iBACA,CAAC,iBACD,EALoB,OAAO,YAAY,YAAY,YAAY,OAM/D,CAAC,uBAAuB,eAAe,MACtC,OAAO,YAAY,YAAY;EAElC,IAAI,CAAC,iBAAiB,CAAC,mBAAmB,CAAC,uBACzC;EAGF,IAAI;EACJ,IAAI,WAAW;EAEf,IAAI,iBAAiB;GAGnB,MAAM,MAAM;GACZ,iBAAiB,CAAC;GAClB,IAAI,wBAAwB;GAC5B,KAAK,IAAI,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;IACnC,MAAM,QAAQ,IAAI;IAClB,IAAI,aAAa,KAAK,GAAG;KACvB,WAAW;KACX;IACF;IACA,MAAM,SAAS,EAAE,GAAG,MAAM;IAC1B,IAAI,mBAAmB,QAAQ;KAC7B,OAAQ,OAAmC;KAC3C,WAAW;IACb;IACA,MAAM,OAAQ,OAA6B;IAC3C,IAAI,SAAA,UAA8B,SAAS,QAAQ;KACjD,MAAM,OAAQ,OAA6B;KAC3C,IAAI,QAAQ,QAAQ,KAAK,KAAK,MAAM,IAClC,wBAAwB,eAAe;IAE3C;IACA,eAAe,KAAK,MAA+B;GACrD;GAEA,IAAI,CAAC,YAAY,CAAC,eAChB;GAKF,IAAI,iBAAiB,yBAAyB,GAAG;IAC/C,eAAe,OAAO,wBAAwB,GAAG,GAAG,EAClD,YAAY,EAAE,MAAM,UAAU,EAChC,CAA0B;IAC1B;GACF;EACF,OAAO,IAAI,OAAO,YAAY,YAAY,eAAe;GACvD,iBAAiB,CACf;IAAE,MAAA;IAAyB,MAAM;GAAQ,GACzC,EAAE,YAAY,EAAE,MAAM,UAAU,EAAE,CACpC;GACA;EACF,OAAO,IAAI,OAAO,YAAY,YAAY,uBACxC,iBAAiB;OAEjB;EAGF,gBAAgB,KAAK,aAAa,iBAAiB,cAAc;CACnE;CAEA,OAAO;AACT"}
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 * 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\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"],"mappings":"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"}
@@ -2,6 +2,16 @@ import "../common/enum.mjs";
2
2
  import "../common/index.mjs";
3
3
  //#region src/messages/content.ts
4
4
  /**
5
+ * Whether {@link formatContentStrings} will flatten this message's content:
6
+ * a human/ai/system message whose content is an array of text-only blocks.
7
+ */
8
+ const isLegacyConvertible = (message) => {
9
+ const messageType = message.getType();
10
+ if (!(messageType === "human" || messageType === "ai" || messageType === "system")) return false;
11
+ if (!Array.isArray(message.content)) return false;
12
+ return message.content.every((block) => block.type === "text");
13
+ };
14
+ /**
5
15
  * Formats an array of messages for LangChain, making sure all content fields are strings
6
16
  * @param {Array<HumanMessage | AIMessage | SystemMessage | ToolMessage>} payload - The array of messages to format.
7
17
  * @returns {Array<HumanMessage | AIMessage | SystemMessage | ToolMessage>} - The array of formatted LangChain messages, including ToolMessages for tool calls.
@@ -9,20 +19,7 @@ import "../common/index.mjs";
9
19
  const formatContentStrings = (payload) => {
10
20
  const result = [];
11
21
  for (const message of payload) {
12
- const messageType = message.getType();
13
- if (!(messageType === "human" || messageType === "ai" || messageType === "system")) {
14
- result.push(message);
15
- continue;
16
- }
17
- if (typeof message.content === "string") {
18
- result.push(message);
19
- continue;
20
- }
21
- if (!Array.isArray(message.content)) {
22
- result.push(message);
23
- continue;
24
- }
25
- if (!message.content.every((block) => block.type === "text")) {
22
+ if (!isLegacyConvertible(message)) {
26
23
  result.push(message);
27
24
  continue;
28
25
  }
@@ -35,6 +32,6 @@ const formatContentStrings = (payload) => {
35
32
  return result;
36
33
  };
37
34
  //#endregion
38
- export { formatContentStrings };
35
+ export { formatContentStrings, isLegacyConvertible };
39
36
 
40
37
  //# sourceMappingURL=content.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"content.mjs","names":[],"sources":["../../../src/messages/content.ts"],"sourcesContent":["import type { BaseMessage } from '@langchain/core/messages';\nimport { ContentTypes } from '@/common';\n\n/**\n * Formats an array of messages for LangChain, making sure all content fields are strings\n * @param {Array<HumanMessage | AIMessage | SystemMessage | ToolMessage>} payload - The array of messages to format.\n * @returns {Array<HumanMessage | AIMessage | SystemMessage | ToolMessage>} - The array of formatted LangChain messages, including ToolMessages for tool calls.\n */\nexport const formatContentStrings = (\n payload: Array<BaseMessage>\n): Array<BaseMessage> => {\n // Create a new array to store the processed messages\n const result: Array<BaseMessage> = [];\n\n for (const message of payload) {\n const messageType = message.getType();\n const isValidMessage =\n messageType === 'human' ||\n messageType === 'ai' ||\n messageType === 'system';\n\n if (!isValidMessage) {\n result.push(message);\n continue;\n }\n\n // If content is already a string, add as-is\n if (typeof message.content === 'string') {\n result.push(message);\n continue;\n }\n\n // If content is not an array, add as-is\n if (!Array.isArray(message.content)) {\n result.push(message);\n continue;\n }\n\n // Check if all content blocks are text type\n const allTextBlocks = message.content.every(\n (block) => block.type === ContentTypes.TEXT\n );\n\n // Only convert to string if all blocks are text type\n if (!allTextBlocks) {\n result.push(message);\n continue;\n }\n\n // Reduce text types to a single string\n const content = message.content.reduce((acc, curr) => {\n if (curr.type === ContentTypes.TEXT) {\n return `${acc}${curr[ContentTypes.TEXT] || ''}\\n`;\n }\n return acc;\n }, '');\n\n message.content = content.trim();\n result.push(message);\n }\n\n return result;\n};\n"],"mappings":";;;;;;;;AAQA,MAAa,wBACX,YACuB;CAEvB,MAAM,SAA6B,CAAC;CAEpC,KAAK,MAAM,WAAW,SAAS;EAC7B,MAAM,cAAc,QAAQ,QAAQ;EAMpC,IAAI,EAJF,gBAAgB,WAChB,gBAAgB,QAChB,gBAAgB,WAEG;GACnB,OAAO,KAAK,OAAO;GACnB;EACF;EAGA,IAAI,OAAO,QAAQ,YAAY,UAAU;GACvC,OAAO,KAAK,OAAO;GACnB;EACF;EAGA,IAAI,CAAC,MAAM,QAAQ,QAAQ,OAAO,GAAG;GACnC,OAAO,KAAK,OAAO;GACnB;EACF;EAQA,IAAI,CALkB,QAAQ,QAAQ,OACnC,UAAU,MAAM,SAAA,MAIF,GAAG;GAClB,OAAO,KAAK,OAAO;GACnB;EACF;EAUA,QAAQ,UAPQ,QAAQ,QAAQ,QAAQ,KAAK,SAAS;GACpD,IAAI,KAAK,SAAA,QACP,OAAO,GAAG,MAAM,KAAA,WAA2B,GAAG;GAEhD,OAAO;EACT,GAAG,EAEqB,CAAC,CAAC,KAAK;EAC/B,OAAO,KAAK,OAAO;CACrB;CAEA,OAAO;AACT"}
1
+ {"version":3,"file":"content.mjs","names":[],"sources":["../../../src/messages/content.ts"],"sourcesContent":["import type {\n BaseMessage,\n MessageContentComplex,\n} from '@langchain/core/messages';\nimport { ContentTypes } from '@/common';\n\n/**\n * Whether {@link formatContentStrings} will flatten this message's content:\n * a human/ai/system message whose content is an array of text-only blocks.\n */\nexport const isLegacyConvertible = (message: BaseMessage): boolean => {\n const messageType = message.getType();\n const isValidMessage =\n messageType === 'human' || messageType === 'ai' || messageType === 'system';\n if (!isValidMessage) {\n return false;\n }\n if (!Array.isArray(message.content)) {\n return false;\n }\n return message.content.every((block) => block.type === ContentTypes.TEXT);\n};\n\n/**\n * Formats an array of messages for LangChain, making sure all content fields are strings\n * @param {Array<HumanMessage | AIMessage | SystemMessage | ToolMessage>} payload - The array of messages to format.\n * @returns {Array<HumanMessage | AIMessage | SystemMessage | ToolMessage>} - The array of formatted LangChain messages, including ToolMessages for tool calls.\n */\nexport const formatContentStrings = (\n payload: Array<BaseMessage>\n): Array<BaseMessage> => {\n // Create a new array to store the processed messages\n const result: Array<BaseMessage> = [];\n\n for (const message of payload) {\n if (!isLegacyConvertible(message)) {\n result.push(message);\n continue;\n }\n\n // Reduce text types to a single string\n const blocks = message.content as MessageContentComplex[];\n const content = blocks.reduce((acc, curr) => {\n if (curr.type === ContentTypes.TEXT) {\n return `${acc}${curr[ContentTypes.TEXT] || ''}\\n`;\n }\n return acc;\n }, '');\n\n message.content = content.trim();\n result.push(message);\n }\n\n return result;\n};\n"],"mappings":";;;;;;;AAUA,MAAa,uBAAuB,YAAkC;CACpE,MAAM,cAAc,QAAQ,QAAQ;CAGpC,IAAI,EADF,gBAAgB,WAAW,gBAAgB,QAAQ,gBAAgB,WAEnE,OAAO;CAET,IAAI,CAAC,MAAM,QAAQ,QAAQ,OAAO,GAChC,OAAO;CAET,OAAO,QAAQ,QAAQ,OAAO,UAAU,MAAM,SAAA,MAA0B;AAC1E;;;;;;AAOA,MAAa,wBACX,YACuB;CAEvB,MAAM,SAA6B,CAAC;CAEpC,KAAK,MAAM,WAAW,SAAS;EAC7B,IAAI,CAAC,oBAAoB,OAAO,GAAG;GACjC,OAAO,KAAK,OAAO;GACnB;EACF;EAWA,QAAQ,UARO,QAAQ,QACA,QAAQ,KAAK,SAAS;GAC3C,IAAI,KAAK,SAAA,QACP,OAAO,GAAG,MAAM,KAAA,WAA2B,GAAG;GAEhD,OAAO;EACT,GAAG,EAEqB,CAAC,CAAC,KAAK;EAC/B,OAAO,KAAK,OAAO;CACrB;CAEA,OAAO;AACT"}
@@ -3,6 +3,7 @@ import "./ids.mjs";
3
3
  import "./contextPruningSettings.mjs";
4
4
  import "./contextPruning.mjs";
5
5
  import "./prune.mjs";
6
+ import "./budget.mjs";
6
7
  import "./format.mjs";
7
8
  import "./cache.mjs";
8
9
  import "./anthropicToolCache.mjs";
@@ -685,15 +685,24 @@ function createPruneMessages(factoryParams) {
685
685
  let originalToolContentSize = 0;
686
686
  const contextPruningSettings = resolveContextPruningSettings(factoryParams.contextPruningConfig);
687
687
  return function pruneMessages(params) {
688
- if (params.messages.length === 0) return {
689
- context: [],
690
- indexTokenCountMap,
691
- messagesToRefine: [],
692
- prePruneContextTokens: 0,
693
- remainingContextTokens: factoryParams.maxTokens,
694
- calibrationRatio,
695
- resolvedInstructionOverhead: bestInstructionOverhead
696
- };
688
+ if (params.messages.length === 0) {
689
+ /** Post-compaction calls still invoke the model — report the same
690
+ * reserve-adjusted budget fields as the populated paths */
691
+ const emptyInstructionTokens = factoryParams.getInstructionTokens?.() ?? 0;
692
+ const emptyReserveRatio = factoryParams.reserveRatio ?? .05;
693
+ const emptyBudget = factoryParams.maxTokens - (emptyReserveRatio > 0 && emptyReserveRatio < 1 ? Math.round(factoryParams.maxTokens * emptyReserveRatio) : 0);
694
+ return {
695
+ context: [],
696
+ indexTokenCountMap,
697
+ messagesToRefine: [],
698
+ prePruneContextTokens: 0,
699
+ remainingContextTokens: Math.max(0, emptyBudget - emptyInstructionTokens),
700
+ calibrationRatio,
701
+ resolvedInstructionOverhead: bestInstructionOverhead,
702
+ contextBudget: emptyBudget,
703
+ effectiveInstructionTokens: emptyInstructionTokens
704
+ };
705
+ }
697
706
  if (factoryParams.provider === "openAI" && factoryParams.thinkingEnabled === true) for (let i = lastTurnStartIndex; i < params.messages.length; i++) {
698
707
  const m = params.messages[i];
699
708
  if (m.getType() === "ai" && typeof m.additional_kwargs.reasoning_content === "string" && Array.isArray(m.additional_kwargs.provider_specific_fields?.thinking_blocks) && m.tool_calls && (m.tool_calls?.length ?? 0) > 0) {
@@ -799,7 +808,9 @@ function createPruneMessages(factoryParams) {
799
808
  remainingContextTokens: 0,
800
809
  contextPressure: pruningBudget > 0 ? calibratedTotalTokens / pruningBudget : 0,
801
810
  calibrationRatio,
802
- resolvedInstructionOverhead: bestInstructionOverhead
811
+ resolvedInstructionOverhead: bestInstructionOverhead,
812
+ contextBudget: pruningBudget,
813
+ effectiveInstructionTokens: currentInstructionTokens
803
814
  };
804
815
  }
805
816
  totalTokens = sumTokenCounts(indexTokenCountMap, params.messages.length);
@@ -892,7 +903,9 @@ function createPruneMessages(factoryParams) {
892
903
  contextPressure,
893
904
  originalToolContent: originalToolContent.size > 0 ? originalToolContent : void 0,
894
905
  calibrationRatio,
895
- resolvedInstructionOverhead: bestInstructionOverhead
906
+ resolvedInstructionOverhead: bestInstructionOverhead,
907
+ contextBudget: pruningBudget,
908
+ effectiveInstructionTokens: currentInstructionTokens
896
909
  };
897
910
  const rawSpaceBudget = calibrationRatio > 0 ? Math.round(pruningBudget / calibrationRatio) : pruningBudget;
898
911
  const rawSpaceInstructionTokens = calibrationRatio > 0 ? Math.round(currentInstructionTokens / calibrationRatio) : currentInstructionTokens;
@@ -1085,7 +1098,10 @@ function createPruneMessages(factoryParams) {
1085
1098
  for (const [key, value] of Object.entries(preEmergencyTokenCounts)) indexTokenCountMap[key] = value;
1086
1099
  }
1087
1100
  }
1088
- const remainingContextTokens = Math.max(0, Math.min(pruningBudget, initialRemainingContextTokens + reclaimedTokens));
1101
+ /** Scale raw-space remaining back to calibrated/provider units so it is
1102
+ * directly comparable with pruningBudget and prePruneContextTokens */
1103
+ const rawRemaining = Math.max(0, initialRemainingContextTokens + reclaimedTokens);
1104
+ const remainingContextTokens = Math.max(0, Math.min(pruningBudget, calibrationRatio > 0 ? Math.round(rawRemaining * calibrationRatio) : rawRemaining));
1089
1105
  runThinkingStartIndex = thinkingStartIndex ?? -1;
1090
1106
  /** The index is the first value of `context`, index relative to `params.messages` */
1091
1107
  lastCutOffIndex = Math.max(params.messages.length - (context.length - (context[0]?.getType() === "system" ? 1 : 0)), 0);
@@ -1098,7 +1114,9 @@ function createPruneMessages(factoryParams) {
1098
1114
  contextPressure,
1099
1115
  originalToolContent: originalToolContent.size > 0 ? originalToolContent : void 0,
1100
1116
  calibrationRatio,
1101
- resolvedInstructionOverhead: bestInstructionOverhead
1117
+ resolvedInstructionOverhead: bestInstructionOverhead,
1118
+ contextBudget: pruningBudget,
1119
+ effectiveInstructionTokens: currentInstructionTokens
1102
1120
  };
1103
1121
  };
1104
1122
  }