@librechat/agents 3.3.4 → 3.3.6

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 (263) hide show
  1. package/dist/cjs/common/constants.cjs +21 -0
  2. package/dist/cjs/common/constants.cjs.map +1 -1
  3. package/dist/cjs/events.cjs +10 -1
  4. package/dist/cjs/events.cjs.map +1 -1
  5. package/dist/cjs/graphs/Graph.cjs +456 -7
  6. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  7. package/dist/cjs/hooks/HookRegistry.cjs +25 -0
  8. package/dist/cjs/hooks/HookRegistry.cjs.map +1 -1
  9. package/dist/cjs/hooks/createWorkspacePolicyHook.cjs +1 -1
  10. package/dist/cjs/hooks/index.cjs +12 -0
  11. package/dist/cjs/hooks/index.cjs.map +1 -1
  12. package/dist/cjs/hooks/types.cjs +1 -0
  13. package/dist/cjs/hooks/types.cjs.map +1 -1
  14. package/dist/cjs/langfuse.cjs +8 -0
  15. package/dist/cjs/langfuse.cjs.map +1 -1
  16. package/dist/cjs/llm/bedrock/utils/message_inputs.cjs +1 -3
  17. package/dist/cjs/llm/bedrock/utils/message_inputs.cjs.map +1 -1
  18. package/dist/cjs/llm/invoke.cjs +268 -3
  19. package/dist/cjs/llm/invoke.cjs.map +1 -1
  20. package/dist/cjs/llm/openai/index.cjs +51 -6
  21. package/dist/cjs/llm/openai/index.cjs.map +1 -1
  22. package/dist/cjs/llm/preempt.cjs +132 -0
  23. package/dist/cjs/llm/preempt.cjs.map +1 -0
  24. package/dist/cjs/main.cjs +31 -3
  25. package/dist/cjs/messages/alternation.cjs +92 -0
  26. package/dist/cjs/messages/alternation.cjs.map +1 -0
  27. package/dist/cjs/messages/format.cjs +72 -0
  28. package/dist/cjs/messages/format.cjs.map +1 -1
  29. package/dist/cjs/messages/handoffCue.cjs +63 -0
  30. package/dist/cjs/messages/handoffCue.cjs.map +1 -0
  31. package/dist/cjs/messages/index.cjs +3 -0
  32. package/dist/cjs/messages/injected.cjs +60 -0
  33. package/dist/cjs/messages/injected.cjs.map +1 -0
  34. package/dist/cjs/run.cjs +80 -7
  35. package/dist/cjs/run.cjs.map +1 -1
  36. package/dist/cjs/session/JsonlSessionStore.cjs +1 -1
  37. package/dist/cjs/session/handlers.cjs +18 -9
  38. package/dist/cjs/session/handlers.cjs.map +1 -1
  39. package/dist/cjs/stream.cjs +34 -8
  40. package/dist/cjs/stream.cjs.map +1 -1
  41. package/dist/cjs/tools/BashExecutor.cjs +3 -1
  42. package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
  43. package/dist/cjs/tools/BashProgrammaticToolCalling.cjs +2 -0
  44. package/dist/cjs/tools/BashProgrammaticToolCalling.cjs.map +1 -1
  45. package/dist/cjs/tools/CodeExecutor.cjs +19 -16
  46. package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
  47. package/dist/cjs/tools/ProgrammaticToolCalling.cjs +2 -0
  48. package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
  49. package/dist/cjs/tools/ReadFile.cjs +8 -4
  50. package/dist/cjs/tools/ReadFile.cjs.map +1 -1
  51. package/dist/cjs/tools/SkillTool.cjs +2 -0
  52. package/dist/cjs/tools/SkillTool.cjs.map +1 -1
  53. package/dist/cjs/tools/SubagentTool.cjs +3 -0
  54. package/dist/cjs/tools/SubagentTool.cjs.map +1 -1
  55. package/dist/cjs/tools/ToolNode.cjs +91 -50
  56. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  57. package/dist/cjs/tools/ToolSearch.cjs +5 -1
  58. package/dist/cjs/tools/ToolSearch.cjs.map +1 -1
  59. package/dist/cjs/tools/intentArg.cjs +230 -0
  60. package/dist/cjs/tools/intentArg.cjs.map +1 -0
  61. package/dist/cjs/tools/local/CompileCheckTool.cjs +3 -2
  62. package/dist/cjs/tools/local/CompileCheckTool.cjs.map +1 -1
  63. package/dist/cjs/tools/local/LocalCodingTools.cjs +13 -12
  64. package/dist/cjs/tools/local/LocalCodingTools.cjs.map +1 -1
  65. package/dist/cjs/tools/local/LocalExecutionEngine.cjs +2 -2
  66. package/dist/cjs/tools/ptcTimeout.cjs.map +1 -1
  67. package/dist/cjs/tools/search/keenable-scraper.cjs +90 -0
  68. package/dist/cjs/tools/search/keenable-scraper.cjs.map +1 -0
  69. package/dist/cjs/tools/search/schema.cjs +2 -0
  70. package/dist/cjs/tools/search/schema.cjs.map +1 -1
  71. package/dist/cjs/tools/search/tool.cjs +40 -2
  72. package/dist/cjs/tools/search/tool.cjs.map +1 -1
  73. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +1 -0
  74. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
  75. package/dist/cjs/tools/toolOutputReferences.cjs +41 -7
  76. package/dist/cjs/tools/toolOutputReferences.cjs.map +1 -1
  77. package/dist/esm/common/constants.mjs +19 -1
  78. package/dist/esm/common/constants.mjs.map +1 -1
  79. package/dist/esm/events.mjs +10 -1
  80. package/dist/esm/events.mjs.map +1 -1
  81. package/dist/esm/graphs/Graph.mjs +455 -6
  82. package/dist/esm/graphs/Graph.mjs.map +1 -1
  83. package/dist/esm/hooks/HookRegistry.mjs +25 -0
  84. package/dist/esm/hooks/HookRegistry.mjs.map +1 -1
  85. package/dist/esm/hooks/createWorkspacePolicyHook.mjs +1 -1
  86. package/dist/esm/hooks/index.mjs +12 -1
  87. package/dist/esm/hooks/index.mjs.map +1 -1
  88. package/dist/esm/hooks/types.mjs +1 -0
  89. package/dist/esm/hooks/types.mjs.map +1 -1
  90. package/dist/esm/langfuse.mjs +9 -1
  91. package/dist/esm/langfuse.mjs.map +1 -1
  92. package/dist/esm/llm/bedrock/utils/message_inputs.mjs +1 -3
  93. package/dist/esm/llm/bedrock/utils/message_inputs.mjs.map +1 -1
  94. package/dist/esm/llm/invoke.mjs +270 -6
  95. package/dist/esm/llm/invoke.mjs.map +1 -1
  96. package/dist/esm/llm/openai/index.mjs +51 -6
  97. package/dist/esm/llm/openai/index.mjs.map +1 -1
  98. package/dist/esm/llm/preempt.mjs +131 -0
  99. package/dist/esm/llm/preempt.mjs.map +1 -0
  100. package/dist/esm/main.mjs +11 -7
  101. package/dist/esm/messages/alternation.mjs +91 -0
  102. package/dist/esm/messages/alternation.mjs.map +1 -0
  103. package/dist/esm/messages/format.mjs +72 -0
  104. package/dist/esm/messages/format.mjs.map +1 -1
  105. package/dist/esm/messages/handoffCue.mjs +61 -0
  106. package/dist/esm/messages/handoffCue.mjs.map +1 -0
  107. package/dist/esm/messages/index.mjs +3 -0
  108. package/dist/esm/messages/injected.mjs +60 -0
  109. package/dist/esm/messages/injected.mjs.map +1 -0
  110. package/dist/esm/run.mjs +80 -7
  111. package/dist/esm/run.mjs.map +1 -1
  112. package/dist/esm/session/JsonlSessionStore.mjs +1 -1
  113. package/dist/esm/session/handlers.mjs +19 -10
  114. package/dist/esm/session/handlers.mjs.map +1 -1
  115. package/dist/esm/stream.mjs +33 -9
  116. package/dist/esm/stream.mjs.map +1 -1
  117. package/dist/esm/tools/BashExecutor.mjs +3 -1
  118. package/dist/esm/tools/BashExecutor.mjs.map +1 -1
  119. package/dist/esm/tools/BashProgrammaticToolCalling.mjs +2 -0
  120. package/dist/esm/tools/BashProgrammaticToolCalling.mjs.map +1 -1
  121. package/dist/esm/tools/CodeExecutor.mjs +19 -16
  122. package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
  123. package/dist/esm/tools/ProgrammaticToolCalling.mjs +2 -0
  124. package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
  125. package/dist/esm/tools/ReadFile.mjs +8 -4
  126. package/dist/esm/tools/ReadFile.mjs.map +1 -1
  127. package/dist/esm/tools/SkillTool.mjs +2 -0
  128. package/dist/esm/tools/SkillTool.mjs.map +1 -1
  129. package/dist/esm/tools/SubagentTool.mjs +3 -0
  130. package/dist/esm/tools/SubagentTool.mjs.map +1 -1
  131. package/dist/esm/tools/ToolNode.mjs +91 -51
  132. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  133. package/dist/esm/tools/ToolSearch.mjs +5 -1
  134. package/dist/esm/tools/ToolSearch.mjs.map +1 -1
  135. package/dist/esm/tools/intentArg.mjs +220 -0
  136. package/dist/esm/tools/intentArg.mjs.map +1 -0
  137. package/dist/esm/tools/local/CompileCheckTool.mjs +3 -2
  138. package/dist/esm/tools/local/CompileCheckTool.mjs.map +1 -1
  139. package/dist/esm/tools/local/LocalCodingTools.mjs +13 -12
  140. package/dist/esm/tools/local/LocalCodingTools.mjs.map +1 -1
  141. package/dist/esm/tools/local/LocalExecutionEngine.mjs +2 -2
  142. package/dist/esm/tools/ptcTimeout.mjs.map +1 -1
  143. package/dist/esm/tools/search/keenable-scraper.mjs +88 -0
  144. package/dist/esm/tools/search/keenable-scraper.mjs.map +1 -0
  145. package/dist/esm/tools/search/schema.mjs +2 -0
  146. package/dist/esm/tools/search/schema.mjs.map +1 -1
  147. package/dist/esm/tools/search/tool.mjs +40 -3
  148. package/dist/esm/tools/search/tool.mjs.map +1 -1
  149. package/dist/esm/tools/subagent/SubagentExecutor.mjs +1 -0
  150. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
  151. package/dist/esm/tools/toolOutputReferences.mjs +41 -7
  152. package/dist/esm/tools/toolOutputReferences.mjs.map +1 -1
  153. package/dist/types/common/constants.d.ts +18 -0
  154. package/dist/types/graphs/Graph.d.ts +138 -1
  155. package/dist/types/hooks/HookRegistry.d.ts +15 -0
  156. package/dist/types/hooks/index.d.ts +12 -1
  157. package/dist/types/hooks/types.d.ts +45 -6
  158. package/dist/types/index.d.ts +2 -1
  159. package/dist/types/llm/invoke.d.ts +7 -0
  160. package/dist/types/llm/openai/index.d.ts +0 -11
  161. package/dist/types/llm/preempt.d.ts +40 -0
  162. package/dist/types/messages/alternation.d.ts +23 -0
  163. package/dist/types/messages/handoffCue.d.ts +40 -0
  164. package/dist/types/messages/index.d.ts +3 -0
  165. package/dist/types/messages/injected.d.ts +3 -0
  166. package/dist/types/run.d.ts +7 -0
  167. package/dist/types/stream.d.ts +14 -0
  168. package/dist/types/tools/BashExecutor.d.ts +18 -0
  169. package/dist/types/tools/CodeExecutor.d.ts +18 -0
  170. package/dist/types/tools/ReadFile.d.ts +18 -0
  171. package/dist/types/tools/SkillTool.d.ts +18 -0
  172. package/dist/types/tools/SubagentTool.d.ts +9 -0
  173. package/dist/types/tools/ToolNode.d.ts +37 -12
  174. package/dist/types/tools/ToolSearch.d.ts +18 -0
  175. package/dist/types/tools/intentArg.d.ts +119 -0
  176. package/dist/types/tools/ptcTimeout.d.ts +2 -0
  177. package/dist/types/tools/search/keenable-scraper.d.ts +15 -0
  178. package/dist/types/tools/search/schema.d.ts +18 -0
  179. package/dist/types/tools/search/tool.d.ts +18 -0
  180. package/dist/types/tools/search/types.d.ts +31 -2
  181. package/dist/types/tools/toolOutputReferences.d.ts +12 -2
  182. package/dist/types/types/graph.d.ts +8 -1
  183. package/dist/types/types/run.d.ts +65 -0
  184. package/dist/types/types/stream.d.ts +12 -25
  185. package/dist/types/types/tools.d.ts +27 -2
  186. package/package.json +1 -1
  187. package/src/{splitStream.test.ts → aggregator.test.ts} +59 -666
  188. package/src/common/constants.ts +21 -0
  189. package/src/events.ts +15 -1
  190. package/src/graphs/Graph.ts +568 -3
  191. package/src/graphs/__tests__/Graph.contextOverflow.test.ts +18 -7
  192. package/src/graphs/__tests__/Graph.preemptSignal.test.ts +126 -0
  193. package/src/hooks/HookRegistry.ts +40 -0
  194. package/src/hooks/__tests__/preemptBoundary.test.ts +152 -0
  195. package/src/hooks/index.ts +16 -2
  196. package/src/hooks/types.ts +47 -3
  197. package/src/index.ts +2 -1
  198. package/src/langfuse.ts +26 -1
  199. package/src/llm/bedrock/utils/message_inputs.test.ts +82 -0
  200. package/src/llm/bedrock/utils/message_inputs.ts +14 -11
  201. package/src/llm/bedrock/utils/toolResultCachePoint.test.ts +15 -0
  202. package/src/llm/invoke.alternation.test.ts +87 -0
  203. package/src/llm/invoke.handoffCue.test.ts +112 -0
  204. package/src/llm/invoke.ts +417 -8
  205. package/src/llm/openai/index.ts +74 -10
  206. package/src/llm/openai/llm.spec.ts +105 -0
  207. package/src/llm/preempt.test.ts +323 -0
  208. package/src/llm/preempt.ts +178 -0
  209. package/src/messages/alternation.test.ts +212 -0
  210. package/src/messages/alternation.ts +112 -0
  211. package/src/messages/format.ts +91 -0
  212. package/src/messages/formatAgentMessages.steer.test.ts +267 -0
  213. package/src/messages/handoffCue.test.ts +96 -0
  214. package/src/messages/handoffCue.ts +78 -0
  215. package/src/messages/index.ts +3 -0
  216. package/src/messages/injected.test.ts +90 -0
  217. package/src/messages/injected.ts +74 -0
  218. package/src/run.ts +91 -6
  219. package/src/scripts/preempt-probe.ts +330 -0
  220. package/src/scripts/preempt-scenarios.ts +388 -0
  221. package/src/session/handlers.ts +32 -12
  222. package/src/specs/handoffCue.test.ts +165 -0
  223. package/src/specs/langfuse-callbacks.test.ts +352 -2
  224. package/src/specs/preemptSeal.test.ts +309 -0
  225. package/src/stream.dispatch.test.ts +63 -0
  226. package/src/stream.ts +48 -9
  227. package/src/tools/BashExecutor.ts +6 -0
  228. package/src/tools/BashProgrammaticToolCalling.ts +2 -0
  229. package/src/tools/CodeExecutor.ts +6 -0
  230. package/src/tools/ProgrammaticToolCalling.ts +2 -0
  231. package/src/tools/ReadFile.ts +2 -0
  232. package/src/tools/SkillTool.ts +2 -0
  233. package/src/tools/SubagentTool.ts +3 -0
  234. package/src/tools/ToolNode.ts +193 -74
  235. package/src/tools/ToolSearch.ts +3 -0
  236. package/src/tools/__tests__/ToolNode.onResultCompletion.test.ts +174 -0
  237. package/src/tools/__tests__/intentArg.test.ts +281 -0
  238. package/src/tools/__tests__/intentCoverage.test.ts +139 -0
  239. package/src/tools/__tests__/toolOutputReferences.test.ts +83 -0
  240. package/src/tools/intentArg.ts +323 -0
  241. package/src/tools/local/CompileCheckTool.ts +4 -3
  242. package/src/tools/local/LocalCodingTools.ts +14 -13
  243. package/src/tools/ptcTimeout.ts +2 -0
  244. package/src/tools/search/keenable-scraper.test.ts +153 -0
  245. package/src/tools/search/keenable-scraper.ts +137 -0
  246. package/src/tools/search/outcome.test.ts +90 -0
  247. package/src/tools/search/schema.ts +3 -0
  248. package/src/tools/search/tool.ts +60 -3
  249. package/src/tools/search/types.ts +50 -3
  250. package/src/tools/subagent/SubagentExecutor.ts +2 -1
  251. package/src/tools/toolOutputReferences.ts +77 -9
  252. package/src/types/graph.ts +12 -1
  253. package/src/types/run.ts +67 -0
  254. package/src/types/stream.ts +12 -41
  255. package/src/types/tools.ts +24 -2
  256. package/dist/cjs/splitStream.cjs +0 -151
  257. package/dist/cjs/splitStream.cjs.map +0 -1
  258. package/dist/esm/splitStream.mjs +0 -150
  259. package/dist/esm/splitStream.mjs.map +0 -1
  260. package/dist/types/mockStream.d.ts +0 -32
  261. package/dist/types/splitStream.d.ts +0 -37
  262. package/src/mockStream.ts +0 -99
  263. package/src/splitStream.ts +0 -234
@@ -0,0 +1,131 @@
1
+ import "../common/constants.mjs";
2
+ import "../common/enum.mjs";
3
+ import "../common/index.mjs";
4
+ //#region src/llm/preempt.ts
5
+ /**
6
+ * Normalizes a host-supplied seal budget.
7
+ *
8
+ * Read in two places that interpret it differently — a numeric comparison in
9
+ * the seal gate and an addition into the recursion limit — so a value like
10
+ * `1.5` would permit two seals while reserving fractional headroom, `NaN`
11
+ * would poison the recursion limit outright, and `Infinity` would remove both
12
+ * bounds at once. Normalizing once keeps the two readings in agreement.
13
+ *
14
+ * `0` is honored as a deliberate "never seal"; anything not finite falls back
15
+ * to the default rather than silently disabling the feature.
16
+ */
17
+ function resolveMaxSeals(maxSeals) {
18
+ if (maxSeals == null || !Number.isFinite(maxSeals)) return 8;
19
+ const whole = Math.floor(maxSeals);
20
+ return whole > 0 ? whole : 0;
21
+ }
22
+ /**
23
+ * True when the accumulated content carries at least one non-whitespace text
24
+ * block. `thinking` / `reasoning` blocks deliberately do not count: they are
25
+ * stripped or signed on several providers and cannot stand in for the visible
26
+ * assistant turn a sealed sequence needs.
27
+ */
28
+ function hasNonEmptyTextContent(content) {
29
+ if (typeof content === "string") return content.trim() !== "";
30
+ for (const block of content) {
31
+ if (block.type !== "text") continue;
32
+ const text = block["text"];
33
+ if (typeof text === "string" && text.trim() !== "") return true;
34
+ }
35
+ return false;
36
+ }
37
+ /**
38
+ * True while a Gemini server-side tool call is still awaiting its response.
39
+ *
40
+ * Google's server-side tools (Search, URL context) never populate
41
+ * `tool_calls` or `tool_call_chunks` — those are derived from `functionCall`
42
+ * parts only, while a server-side invocation arrives as a `toolCall` CONTENT
43
+ * block and its result as a later `toolResponse` block. The tool-call gates
44
+ * cannot see it, so sealing between the two would replay a model turn holding
45
+ * an unanswered server-side call.
46
+ *
47
+ * Counted rather than id-matched: Google answers calls in order, and counting
48
+ * stays correct when a part omits its id.
49
+ */
50
+ function hasOpenGoogleServerToolCall(content) {
51
+ if (typeof content === "string") return false;
52
+ let calls = 0;
53
+ let responses = 0;
54
+ for (const block of content) if (block.type === "toolCall") calls += 1;
55
+ else if (block.type === "toolResponse") responses += 1;
56
+ return calls > responses;
57
+ }
58
+ /**
59
+ * Ids of Anthropic server-side tool calls whose paired result block is already
60
+ * on the accumulated content. Those calls are answered and cannot be orphaned
61
+ * by a seal.
62
+ */
63
+ function settledServerToolCallIds(content) {
64
+ const settled = /* @__PURE__ */ new Set();
65
+ if (typeof content === "string") return settled;
66
+ for (const block of content) {
67
+ if (block.type !== "web_search_tool_result") continue;
68
+ const id = block.tool_use_id;
69
+ if (typeof id === "string") settled.add(id);
70
+ }
71
+ return settled;
72
+ }
73
+ /** Tool calls still awaiting an answer, ignoring ones already settled. */
74
+ function countOpenToolCalls(calls, settled) {
75
+ if (calls == null || calls.length === 0) return 0;
76
+ if (settled.size === 0) return calls.length;
77
+ let open = 0;
78
+ for (const call of calls) if (call.id == null || !settled.has(call.id)) open += 1;
79
+ return open;
80
+ }
81
+ /**
82
+ * Cooperative mid-generation seal gate. Returns true ONLY when sealing here
83
+ * yields a message sequence valid on EVERY supported provider:
84
+ * - non-whitespace TEXT content, so the FIRST injected user turn is preceded
85
+ * by a non-empty assistant turn — no empty-content 400s. Note this says
86
+ * nothing about adjacency AMONG several injected turns: a boundary that
87
+ * drains two steers emits two consecutive user messages, which strict
88
+ * providers reject. That is normalized at the provider-facing hop by
89
+ * `coalesceAdjacentUserTurns`, not here;
90
+ * - no tool call in flight, so no `tool_use` can be orphaned AND no eagerly
91
+ * prestarted execution can be stripped out from under the model.
92
+ *
93
+ * Anthropic's server-side tools need no check of their own. Every
94
+ * `server_tool_use` content block also emits a `tool_call_chunk`
95
+ * (`_makeMessageChunkFromAnthropicEvent`), and `concat` keeps that chunk on
96
+ * the accumulated message for the remainder of the turn, so the tool-call
97
+ * gates below already cover it. The practical consequence is worth stating
98
+ * plainly: once a turn starts a web search it is no longer preemptible, and
99
+ * a queued message waits for the ordinary tool boundary instead.
100
+ *
101
+ * Nothing is stripped and nothing is repaired: when the accumulated shape is
102
+ * not already safe the stream simply runs on, and whatever the host queued
103
+ * lands at the next tool boundary instead. Chunks accumulate monotonically
104
+ * through `concat`, so an unsafe shape can never be observed at a seal point.
105
+ */
106
+ function canSealPreempt(chunk) {
107
+ if (chunk == null) return false;
108
+ /**
109
+ * Anthropic's server-side tools run inside the provider, so unlike a client
110
+ * tool they never reach `ToolNode` and never produce a `PostToolBatch`
111
+ * boundary. `concat` also keeps their `server_tool_use` chunk on the
112
+ * accumulated message for the rest of the turn. Together that means a naive
113
+ * tool-call gate makes a turn permanently unsealable the moment a web
114
+ * search starts, with no later boundary to drain into — the queued message
115
+ * waits for the whole turn to finish rather than for the next tool step.
116
+ *
117
+ * So calls whose paired result block has already landed are treated as
118
+ * settled: they cannot be orphaned, because the provider has already
119
+ * answered them.
120
+ */
121
+ const settled = settledServerToolCallIds(chunk.content);
122
+ if (countOpenToolCalls(chunk.tool_calls, settled) > 0) return false;
123
+ if (countOpenToolCalls(chunk.tool_call_chunks, settled) > 0) return false;
124
+ if ((chunk.invalid_tool_calls?.length ?? 0) > 0) return false;
125
+ if (hasOpenGoogleServerToolCall(chunk.content)) return false;
126
+ return hasNonEmptyTextContent(chunk.content);
127
+ }
128
+ //#endregion
129
+ export { canSealPreempt, resolveMaxSeals };
130
+
131
+ //# sourceMappingURL=preempt.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preempt.mjs","names":[],"sources":["../../../src/llm/preempt.ts"],"sourcesContent":["// src/llm/preempt.ts\nimport type { AIMessageChunk } from '@langchain/core/messages';\nimport { ContentTypes, DEFAULT_MAX_SEALS } from '@/common';\n\n/**\n * Normalizes a host-supplied seal budget.\n *\n * Read in two places that interpret it differently — a numeric comparison in\n * the seal gate and an addition into the recursion limit — so a value like\n * `1.5` would permit two seals while reserving fractional headroom, `NaN`\n * would poison the recursion limit outright, and `Infinity` would remove both\n * bounds at once. Normalizing once keeps the two readings in agreement.\n *\n * `0` is honored as a deliberate \"never seal\"; anything not finite falls back\n * to the default rather than silently disabling the feature.\n */\nexport function resolveMaxSeals(maxSeals: number | undefined): number {\n if (maxSeals == null || !Number.isFinite(maxSeals)) {\n return DEFAULT_MAX_SEALS;\n }\n const whole = Math.floor(maxSeals);\n return whole > 0 ? whole : 0;\n}\n\n/**\n * True when the accumulated content carries at least one non-whitespace text\n * block. `thinking` / `reasoning` blocks deliberately do not count: they are\n * stripped or signed on several providers and cannot stand in for the visible\n * assistant turn a sealed sequence needs.\n */\nfunction hasNonEmptyTextContent(content: AIMessageChunk['content']): boolean {\n if (typeof content === 'string') {\n return content.trim() !== '';\n }\n for (const block of content) {\n if (block.type !== ContentTypes.TEXT) {\n continue;\n }\n const text = block[ContentTypes.TEXT];\n if (typeof text === 'string' && text.trim() !== '') {\n return true;\n }\n }\n return false;\n}\n\n/**\n * True while a Gemini server-side tool call is still awaiting its response.\n *\n * Google's server-side tools (Search, URL context) never populate\n * `tool_calls` or `tool_call_chunks` — those are derived from `functionCall`\n * parts only, while a server-side invocation arrives as a `toolCall` CONTENT\n * block and its result as a later `toolResponse` block. The tool-call gates\n * cannot see it, so sealing between the two would replay a model turn holding\n * an unanswered server-side call.\n *\n * Counted rather than id-matched: Google answers calls in order, and counting\n * stays correct when a part omits its id.\n */\nfunction hasOpenGoogleServerToolCall(\n content: AIMessageChunk['content']\n): boolean {\n if (typeof content === 'string') {\n return false;\n }\n let calls = 0;\n let responses = 0;\n for (const block of content) {\n if (block.type === 'toolCall') {\n calls += 1;\n } else if (block.type === 'toolResponse') {\n responses += 1;\n }\n }\n return calls > responses;\n}\n\n/**\n * Ids of Anthropic server-side tool calls whose paired result block is already\n * on the accumulated content. Those calls are answered and cannot be orphaned\n * by a seal.\n */\nfunction settledServerToolCallIds(\n content: AIMessageChunk['content']\n): Set<string> {\n const settled = new Set<string>();\n if (typeof content === 'string') {\n return settled;\n }\n for (const block of content) {\n if (block.type !== 'web_search_tool_result') {\n continue;\n }\n const id = block.tool_use_id;\n if (typeof id === 'string') {\n settled.add(id);\n }\n }\n return settled;\n}\n\n/** Tool calls still awaiting an answer, ignoring ones already settled. */\nfunction countOpenToolCalls(\n calls: ReadonlyArray<{ id?: string }> | undefined,\n settled: Set<string>\n): number {\n if (calls == null || calls.length === 0) {\n return 0;\n }\n if (settled.size === 0) {\n return calls.length;\n }\n let open = 0;\n for (const call of calls) {\n if (call.id == null || !settled.has(call.id)) {\n open += 1;\n }\n }\n return open;\n}\n\n/**\n * Cooperative mid-generation seal gate. Returns true ONLY when sealing here\n * yields a message sequence valid on EVERY supported provider:\n * - non-whitespace TEXT content, so the FIRST injected user turn is preceded\n * by a non-empty assistant turn — no empty-content 400s. Note this says\n * nothing about adjacency AMONG several injected turns: a boundary that\n * drains two steers emits two consecutive user messages, which strict\n * providers reject. That is normalized at the provider-facing hop by\n * `coalesceAdjacentUserTurns`, not here;\n * - no tool call in flight, so no `tool_use` can be orphaned AND no eagerly\n * prestarted execution can be stripped out from under the model.\n *\n * Anthropic's server-side tools need no check of their own. Every\n * `server_tool_use` content block also emits a `tool_call_chunk`\n * (`_makeMessageChunkFromAnthropicEvent`), and `concat` keeps that chunk on\n * the accumulated message for the remainder of the turn, so the tool-call\n * gates below already cover it. The practical consequence is worth stating\n * plainly: once a turn starts a web search it is no longer preemptible, and\n * a queued message waits for the ordinary tool boundary instead.\n *\n * Nothing is stripped and nothing is repaired: when the accumulated shape is\n * not already safe the stream simply runs on, and whatever the host queued\n * lands at the next tool boundary instead. Chunks accumulate monotonically\n * through `concat`, so an unsafe shape can never be observed at a seal point.\n */\nexport function canSealPreempt(chunk: AIMessageChunk | undefined): boolean {\n if (chunk == null) {\n return false;\n }\n /**\n * Anthropic's server-side tools run inside the provider, so unlike a client\n * tool they never reach `ToolNode` and never produce a `PostToolBatch`\n * boundary. `concat` also keeps their `server_tool_use` chunk on the\n * accumulated message for the rest of the turn. Together that means a naive\n * tool-call gate makes a turn permanently unsealable the moment a web\n * search starts, with no later boundary to drain into — the queued message\n * waits for the whole turn to finish rather than for the next tool step.\n *\n * So calls whose paired result block has already landed are treated as\n * settled: they cannot be orphaned, because the provider has already\n * answered them.\n */\n const settled = settledServerToolCallIds(chunk.content);\n if (countOpenToolCalls(chunk.tool_calls, settled) > 0) {\n return false;\n }\n if (countOpenToolCalls(chunk.tool_call_chunks, settled) > 0) {\n return false;\n }\n if ((chunk.invalid_tool_calls?.length ?? 0) > 0) {\n return false;\n }\n if (hasOpenGoogleServerToolCall(chunk.content)) {\n return false;\n }\n return hasNonEmptyTextContent(chunk.content);\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAgBA,SAAgB,gBAAgB,UAAsC;CACpE,IAAI,YAAY,QAAQ,CAAC,OAAO,SAAS,QAAQ,GAC/C,OAAA;CAEF,MAAM,QAAQ,KAAK,MAAM,QAAQ;CACjC,OAAO,QAAQ,IAAI,QAAQ;AAC7B;;;;;;;AAQA,SAAS,uBAAuB,SAA6C;CAC3E,IAAI,OAAO,YAAY,UACrB,OAAO,QAAQ,KAAK,MAAM;CAE5B,KAAK,MAAM,SAAS,SAAS;EAC3B,IAAI,MAAM,SAAA,QACR;EAEF,MAAM,OAAO,MAAA;EACb,IAAI,OAAO,SAAS,YAAY,KAAK,KAAK,MAAM,IAC9C,OAAO;CAEX;CACA,OAAO;AACT;;;;;;;;;;;;;;AAeA,SAAS,4BACP,SACS;CACT,IAAI,OAAO,YAAY,UACrB,OAAO;CAET,IAAI,QAAQ;CACZ,IAAI,YAAY;CAChB,KAAK,MAAM,SAAS,SAClB,IAAI,MAAM,SAAS,YACjB,SAAS;MACJ,IAAI,MAAM,SAAS,gBACxB,aAAa;CAGjB,OAAO,QAAQ;AACjB;;;;;;AAOA,SAAS,yBACP,SACa;CACb,MAAM,0BAAU,IAAI,IAAY;CAChC,IAAI,OAAO,YAAY,UACrB,OAAO;CAET,KAAK,MAAM,SAAS,SAAS;EAC3B,IAAI,MAAM,SAAS,0BACjB;EAEF,MAAM,KAAK,MAAM;EACjB,IAAI,OAAO,OAAO,UAChB,QAAQ,IAAI,EAAE;CAElB;CACA,OAAO;AACT;;AAGA,SAAS,mBACP,OACA,SACQ;CACR,IAAI,SAAS,QAAQ,MAAM,WAAW,GACpC,OAAO;CAET,IAAI,QAAQ,SAAS,GACnB,OAAO,MAAM;CAEf,IAAI,OAAO;CACX,KAAK,MAAM,QAAQ,OACjB,IAAI,KAAK,MAAM,QAAQ,CAAC,QAAQ,IAAI,KAAK,EAAE,GACzC,QAAQ;CAGZ,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BA,SAAgB,eAAe,OAA4C;CACzE,IAAI,SAAS,MACX,OAAO;;;;;;;;;;;;;;CAeT,MAAM,UAAU,yBAAyB,MAAM,OAAO;CACtD,IAAI,mBAAmB,MAAM,YAAY,OAAO,IAAI,GAClD,OAAO;CAET,IAAI,mBAAmB,MAAM,kBAAkB,OAAO,IAAI,GACxD,OAAO;CAET,KAAK,MAAM,oBAAoB,UAAU,KAAK,GAC5C,OAAO;CAET,IAAI,4BAA4B,MAAM,OAAO,GAC3C,OAAO;CAET,OAAO,uBAAuB,MAAM,OAAO;AAC7C"}
package/dist/esm/main.mjs CHANGED
@@ -1,9 +1,10 @@
1
1
  import { isPresent, parseBooleanEnv, unescapeObject } from "./utils/misc.mjs";
2
- import { ANTHROPIC_TOOL_TOKEN_MULTIPLIER, DEFAULT_TOOL_TOKEN_MULTIPLIER } from "./common/constants.mjs";
2
+ import { ANTHROPIC_TOOL_TOKEN_MULTIPLIER, DEFAULT_MAX_SEALS, DEFAULT_RECURSION_LIMIT, DEFAULT_TOOL_TOKEN_MULTIPLIER, PREEMPT_BOUNDARY_HOOK_TIMEOUT_MS } 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
5
  import { HARD_MAX_TOOL_RESULT_CHARS, HARD_MAX_TOTAL_TOOL_OUTPUT_SIZE, calculateMaxToolResultChars, calculateMaxTotalToolOutputSize, truncateToolInput, truncateToolResultContent } from "./utils/truncation.mjs";
6
6
  import { IMAGE_TOKEN_SAFETY_MARGIN, TokenEncoderManager, apportionTokenCounts, createTokenCounter, encodingForModel, estimateAnthropicImageTokens, estimateDocumentBlockTokens, estimateImageBlockTokens, estimateOpenAIImageTokens, estimateTimedMediaBlockTokens, extractImageDimensions, getTokenCountForMessage, hasUnsafeStructuredSerialization } from "./utils/tokens.mjs";
7
+ import { canSealPreempt } from "./llm/preempt.mjs";
7
8
  import { DEFAULT_PROMPT_CACHE_TTL, addBedrockCacheControl, addBedrockTailCacheControl, addCacheControl, addCacheControlToStablePrefixMessages, addTailCacheControl, buildAnthropicCacheControl, buildBedrockCachePoint, cloneMessage, resolveBedrockPromptCacheTtl, resolvePromptCacheTtl, stripAnthropicCacheControl, stripBedrockCacheControl, supportsBedrockToolCache } from "./messages/cache.mjs";
8
9
  import { convertMessagesToContent, findLastIndex, formatAnthropicArtifactContent, formatAnthropicMessage, formatArtifactPayload, getConverseOverrideMessage, modifyDeltaProperties, projectAnthropicArtifactContent, projectArtifactPayload, projectCacheControlledToolOutputsToText, projectComputerCallOutputsToText, projectOpenAIChatToolMessageContent, projectOpenAIResponsesToolMessageContent, projectOpenAIToolMessageContent, projectOpenRouterToolMessageContent, projectSingleTextToolOutputsToText, projectStructuredToolOutputsToText, projectToolStreamContentForProvider } from "./messages/core.mjs";
9
10
  import { getMessageId } from "./messages/ids.mjs";
@@ -15,13 +16,17 @@ import { ensureThinkingBlockInMessages, foldToolBlocksForToollessAgent, formatAg
15
16
  import { makeIsDeferred, partitionAndMarkAnthropicToolCache } from "./messages/anthropicToolCache.mjs";
16
17
  import { formatContentStrings, isLegacyConvertible } from "./messages/content.mjs";
17
18
  import { extractToolDiscoveries, hasToolSearchInCurrentTurn } from "./messages/tools.mjs";
19
+ import { convertInjectedMessages } from "./messages/injected.mjs";
20
+ import { coalesceAdjacentUserTurns, strictAlternationProviders } from "./messages/alternation.mjs";
21
+ import { PREDECESSOR_HANDOFF_CUE, appendPredecessorHandoffCue, removePredecessorHandoffCue } from "./messages/handoffCue.mjs";
18
22
  import { REMOVE_ALL_MESSAGES, createRemoveAllMessage, messagesStateReducer } from "./messages/reducer.mjs";
19
23
  import { DEFAULT_RETAIN_RECENT_TURNS, splitAtRecencyBoundary } from "./messages/recency.mjs";
20
24
  import "./messages/index.mjs";
21
25
  import { joinKeys, resetIfNotEmpty } from "./utils/graph.mjs";
22
26
  import { isAnthropicLike, isGoogleLike, isOpenAILike } from "./utils/llm.mjs";
23
27
  import { handleServerToolResult, handleToolCallChunks, handleToolCalls, toolResultTypes } from "./tools/handlers.mjs";
24
- import { ChatModelStreamHandler, createContentAggregator, getChunkContent } from "./stream.mjs";
28
+ import { INTENT_ARG, INTENT_DESCRIPTION, INTENT_PROPERTY, applyOutcome, isIntentLabelProperty, outcomeFieldsFromResult, readIntent, readOutcomeFields, resolveToolOutcome, stripIntent, withIntent } from "./tools/intentArg.mjs";
29
+ import { ChatModelStreamHandler, SDK_STREAM_DISPATCH, createContentAggregator, dispatchesChatModelStream, getChunkContent } from "./stream.mjs";
25
30
  import { HandlerRegistry, LLMStreamHandler, ModelEndHandler, TestChatStreamHandler, TestLLMStreamHandler, ToolEndHandler, composeEventHandlers, createMetadataAggregator } from "./events.mjs";
26
31
  import { createHandlers } from "./utils/handlers.mjs";
27
32
  import { RunnableCallable, sleep } from "./utils/run.mjs";
@@ -47,7 +52,7 @@ import { DEFAULT_HOOK_TIMEOUT_MS, executeHooks } from "./hooks/executeHooks.mjs"
47
52
  import { createToolPolicyHook } from "./hooks/createToolPolicyHook.mjs";
48
53
  import { createWorkspacePolicyHook } from "./hooks/createWorkspacePolicyHook.mjs";
49
54
  import { HOOK_EVENTS } from "./hooks/types.mjs";
50
- import { HOOK_INJECTED_MESSAGES_CAPABLE } from "./hooks/index.mjs";
55
+ import { HOOK_INJECTED_MESSAGES_CAPABLE, HOOK_PREEMPT_BOUNDARY_CAPABLE } from "./hooks/index.mjs";
51
56
  import { _createBashProgramForTests, applyPreToolUseHooksForBridge, createLocalBashProgrammaticToolCallingTool, createLocalProgrammaticToolCallingTool } from "./tools/local/LocalProgrammaticToolCalling.mjs";
52
57
  import { BashExecutionToolDefinition, BashExecutionToolDescription, BashExecutionToolName, BashExecutionToolSchema, BashToolOutputReferencesGuide, STATEFUL_BASH_NOTE, StatefulBashExecutionToolDescription, buildBashExecutionToolDescription, buildBashExecutionToolSchema, createBashExecutionTool } from "./tools/BashExecutor.mjs";
53
58
  import { LocalBashExecutionToolDescription, LocalCodeExecutionToolDescription, createLocalBashExecutionTool, createLocalCodeExecutionTool } from "./tools/local/LocalExecutionTools.mjs";
@@ -63,7 +68,7 @@ import { CLOUDFLARE_BASH_CODING_TOOL_NAMES, CLOUDFLARE_CODING_TOOL_NAMES, Cloudf
63
68
  import "./tools/cloudflare/index.mjs";
64
69
  import { resolveLocalExecutionTools, resolveLocalToolRegistry, resolveLocalToolsForBinding } from "./tools/local/resolveLocalExecutionTools.mjs";
65
70
  import "./tools/local/index.mjs";
66
- import { ToolNode, toolsCondition } from "./tools/ToolNode.mjs";
71
+ import { ToolNode, createToolErrorOwnership, toolsCondition } from "./tools/ToolNode.mjs";
67
72
  import { SubagentExecutor, buildChildInputs, filterSubagentResult, resolveSubagentConfigs, summarizeEvent } from "./tools/subagent/SubagentExecutor.mjs";
68
73
  import "./tools/subagent/index.mjs";
69
74
  import { SubagentToolDefinition, SubagentToolDescription, SubagentToolName, SubagentToolSchema, buildSubagentToolParams, createSubagentToolDefinition } from "./tools/SubagentTool.mjs";
@@ -74,7 +79,6 @@ import { FakeChatModel, createFakeStreamingLLM } from "./llm/fake.mjs";
74
79
  import { Graph, StandardGraph } from "./graphs/Graph.mjs";
75
80
  import { MultiAgentGraph } from "./graphs/MultiAgentGraph.mjs";
76
81
  import { Run, defaultOmitOptions } from "./run.mjs";
77
- import { SEPARATORS, SplitStreamHandler } from "./splitStream.mjs";
78
82
  import "./graphs/index.mjs";
79
83
  import { projectAgentContextUsage } from "./agents/projection.mjs";
80
84
  import { Calculator, CalculatorSchema, CalculatorToolDefinition, CalculatorToolDescription, CalculatorToolName } from "./tools/Calculator.mjs";
@@ -83,7 +87,7 @@ import { ReadFileToolDefinition, ReadFileToolDescription, ReadFileToolName, Read
83
87
  import { formatSkillCatalog } from "./tools/skillCatalog.mjs";
84
88
  import { ToolSearchToolDefinition, ToolSearchToolDescription, ToolSearchToolName, ToolSearchToolSchema, countNestedGroups, createToolSearch, escapeRegexSpecialChars, extractMcpServerName, formatServerListing, getAvailableMcpServers, getBaseToolName, getDeferredToolsListing, hasNestedQuantifiers, isDangerousPattern, isFromAnyMcpServer, isFromMcpServer, normalizeServerFilter, performLocalSearch, sanitizeRegex } from "./tools/ToolSearch.mjs";
85
89
  import { DATE_RANGE, DEFAULT_COUNTRY_DESCRIPTION, DEFAULT_QUERY_DESCRIPTION, WebSearchToolDefinition, WebSearchToolDescription, WebSearchToolName, WebSearchToolSchema, countrySchema, dateSchema, imagesSchema, newsSchema, querySchema, videosSchema } from "./tools/search/schema.mjs";
86
- import { createSearchTool, executeParallelSearches } from "./tools/search/tool.mjs";
90
+ import { createSearchTool, executeParallelSearches, resolveSearchOutcome } from "./tools/search/tool.mjs";
87
91
  import "./tools/search/index.mjs";
88
92
  import { deserializeMessage, extractTextFromContent, serializeMessage } from "./session/messageSerialization.mjs";
89
93
  import { JsonlSessionStore, SessionManager } from "./session/JsonlSessionStore.mjs";
@@ -98,4 +102,4 @@ import { Runnable, RunnableLambda, RunnableSequence } from "./langchain/runnable
98
102
  import { DynamicStructuredTool, StructuredTool, Tool, tool } from "./langchain/tools.mjs";
99
103
  import "./langchain/index.mjs";
100
104
  import { BaseCheckpointSaver, Command, INTERRUPT, MemorySaver, interrupt, isInterrupted } from "@langchain/langgraph";
101
- export { AIMessage, AIMessageChunk, ANTHROPIC_TOOL_TOKEN_MULTIPLIER, AgentSession, BASH_SHELL_GUIDANCE, BaseCheckpointSaver, BaseMessage, BaseMessageChunk, BashExecutionToolDefinition, BashExecutionToolDescription, BashExecutionToolName, BashExecutionToolSchema, BashProgrammaticToolCallingDefinition, BashProgrammaticToolCallingDescription, BashProgrammaticToolCallingName, BashProgrammaticToolCallingSchema, BashToolOutputReferencesGuide, CALIBRATION_RATIO_MAX, CALIBRATION_RATIO_MIN, CLOUDFLARE_BASH_CODING_TOOL_NAMES, CLOUDFLARE_CODING_TOOL_NAMES, CODE_API_AUTHORIZATION_ERROR_MESSAGE, CODE_API_EXECUTION_FAILED_ERROR_MESSAGE, CODE_API_INVALID_REQUEST_ERROR_MESSAGE, CODE_API_RATE_LIMITED_ERROR_MESSAGE, CODE_API_UNAVAILABLE_ERROR_MESSAGE, CODE_ARTIFACT_PATH_GUIDANCE, CODE_EXECUTION_TOOLS, Calculator, CalculatorSchema, CalculatorToolDefinition, CalculatorToolDescription, CalculatorToolName, Callback, ChatModelStreamHandler, ChatOpenRouter, CloudflareBashExecutionToolDescription, CloudflareCodeExecutionToolDescription, CodeApiRequestError, CodeExecutionToolDefinition, CodeExecutionToolDescription, CodeExecutionToolName, CodeExecutionToolSchema, Command, CommonEvents, CompileCheckToolName, Constants, ContentTypes, CustomOpenAIClient, DATE_RANGE, DEFAULT_CONTEXT_PRUNING_SETTINGS, DEFAULT_COUNTRY_DESCRIPTION, DEFAULT_HOOK_TIMEOUT_MS, DEFAULT_PROMPT_CACHE_TTL, DEFAULT_QUERY_DESCRIPTION, DEFAULT_RESERVE_RATIO, DEFAULT_RETAIN_RECENT_TURNS, 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, HOOK_INJECTED_MESSAGES_CAPABLE, HandlerRegistry, HookRegistry, HumanMessage, IMAGE_TOKEN_SAFETY_MARGIN, 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, REPLY_PRIMER_TOKENS, ReadFileToolDefinition, ReadFileToolDescription, ReadFileToolName, ReadFileToolSchema, Run, Runnable, RunnableCallable, RunnableLambda, RunnableSequence, SEPARATORS, STATEFUL_BASH_NOTE, STATEFUL_ENV_NOTE, SessionManager, SkillToolDefinition, SkillToolDescription, SkillToolName, SkillToolSchema, SplitStreamHandler, StandardGraph, StatefulBashExecutionToolDescription, StatefulCodeExecutionToolDescription, 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, addBedrockTailCacheControl, addCacheControl, addCacheControlToStablePrefixMessages, addTailCacheControl, appendCodeSessionFileSummary, appendFailedExecutionFileReminder, appendTmpScratchReminder, applyContextPruning, applyEdit, applyPreToolUseHooksForBridge, apportionTokenCounts, askUserQuestion, attemptInvoke, bashAstFindingsToErrors, buildAnthropicCacheControl, buildBashExecutionToolDescription, buildBashExecutionToolSchema, buildBedrockCachePoint, buildChildInputs, buildCodeApiExecutionErrorMessage, buildCodeApiHttpErrorMessage, buildCodeExecutionToolDescription, buildCodeExecutionToolSchema, buildSandboxRuntimeConfig, buildSubagentToolParams, calculateMaxToolCallInputChars, calculateMaxToolResultChars, calculateMaxTotalToolOutputSize, calculateTotalTokens, checkValidNumber, clampCalibrationRatio, classifyAttachment, clientExecTimeoutMs, clientFsTimeoutMs, 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, estimateDocumentBlockTokens, estimateImageBlockTokens, estimateOpenAIImageTokens, estimateTimedMediaBlockTokens, execWithClientTimeout, executeCloudflareBash, executeCloudflareCode, executeHooks, executeLocalBash, executeLocalBashWithArgs, executeLocalCode, executeParallelSearches, executeTools, extractErrorMessage, extractImageDimensions, extractMcpServerName, extractTextFromContent, extractToolDiscoveries, extractUsedBashToolNames, extractUsedToolNames, fetchSessionFiles, filterBashToolsByUsage, filterSubagentResult, filterToolsByUsage, findLastIndex, foldToolBlocksForToollessAgent, formatAgentMessages, formatAnthropicArtifactContent, formatAnthropicMessage, formatArtifactPayload, formatCloudflareOutput, formatCompletedResponse, formatContentStrings, formatFromLangChain, formatLangChainMessages, formatMediaMessage, formatMessage, formatServerListing, formatSkillCatalog, getAvailableMcpServers, getBaseToolName, getBufferString, getChatModelClass, getChunkContent, getCloudflareWorkspaceRoot, getCodeBaseURL, getContextOverflowInfo, getConverseOverrideMessage, getDeferredToolsListing, getLocalCwd, getLocalSessionId, getMaxOutputTokensKey, getMessageId, getMessagesWithinTokenLimit, getReadRoots, getSpawn, getTokenCountForMessage, getWorkspaceFS, getWorkspaceRoots, getWriteRoots, handleServerToolResult, handleToolCallChunks, handleToolCalls, hasNestedQuantifier, hasNestedQuantifiers, hasToolSearchInCurrentTurn, hasUnsafeStructuredSerialization, imageAttachmentContent, imagesSchema, initializeModel, interrupt, isAIMessage, isAnthropicLike, isBaseMessage, isContextOverflowError, isDangerousPattern, isFromAnyMcpServer, isFromMcpServer, isGoogleLike, isInterrupted, isLegacyConvertible, isLikelyContextOverflowError, isOpenAILike, isPresent, isSyntheticProviderContextMessage, isThinkingEnabled, isToolMessage, isZodSchema, joinKeys, labelContentByAgent, locateEdit, makeIsDeferred, makeRequest, maskConsumedToolResults, matchesQuery, messagesStateReducer, modifyDeltaProperties, newsSchema, normalizeBashToolResultsForReplay, normalizeCodeApiRequestError, normalizeServerFilter, normalizeToBashIdentifier, normalizeToPythonIdentifier, parseBooleanEnv, partitionAndMarkAnthropicToolCache, performLocalSearch, preFlightTruncateToolCallInputs, preFlightTruncateToolResults, projectAgentContextUsage, projectAnthropicArtifactContent, projectArtifactPayload, projectCacheControlledToolOutputsToText, projectComputerCallOutputsToText, projectOpenAIChatToolMessageContent, projectOpenAIResponsesToolMessageContent, projectOpenAIToolMessageContent, projectOpenRouterToolMessageContent, projectSingleTextToolOutputsToText, projectStructuredToolOutputsToText, projectToolCallInputs, projectToolStreamContentForProvider, querySchema, repairOrphanedToolMessages, resetIfNotEmpty, resolveBedrockPromptCacheTtl, resolveCloudflareSandbox, resolveCodeApiAuthHeaders, resolveContextPruningSettings, resolveLocalExecutionConfig, resolveLocalExecutionTools, resolveLocalToolRegistry, resolveLocalToolsForBinding, resolvePromptCacheTtl, resolveSubagentConfigs, resolveWorkspacePath, resolveWorkspacePathSafe, runBashAstChecks, runPostEditSyntaxCheck, sanitizeOrphanToolBlocks, sanitizeRegex, serializeMessage, serializeToolCallInput, shellQuote, shiftIndexTokenCountMap, shouldTriggerSummarization, sleep, spawnLocalProcess, splitAtRecencyBoundary, stripAnthropicCacheControl, stripBedrockCacheControl, stripCodeSessionFileSummary, summarizeEvent, supportsBedrockToolCache, syncBudgetDerivedFields, toJsonSchema, tool, toolResultTypes, toolsCondition, truncateLocalOutput, truncateToolInput, truncateToolResultContent, tryFallbackProviders, unescapeObject, unwrapToolResponse, validateBashCommand, validateCloudflareBashCommand, videosSchema, withClientTimeout, withMessageRole };
105
+ export { AIMessage, AIMessageChunk, ANTHROPIC_TOOL_TOKEN_MULTIPLIER, AgentSession, BASH_SHELL_GUIDANCE, BaseCheckpointSaver, BaseMessage, BaseMessageChunk, BashExecutionToolDefinition, BashExecutionToolDescription, BashExecutionToolName, BashExecutionToolSchema, BashProgrammaticToolCallingDefinition, BashProgrammaticToolCallingDescription, BashProgrammaticToolCallingName, BashProgrammaticToolCallingSchema, BashToolOutputReferencesGuide, CALIBRATION_RATIO_MAX, CALIBRATION_RATIO_MIN, CLOUDFLARE_BASH_CODING_TOOL_NAMES, CLOUDFLARE_CODING_TOOL_NAMES, CODE_API_AUTHORIZATION_ERROR_MESSAGE, CODE_API_EXECUTION_FAILED_ERROR_MESSAGE, CODE_API_INVALID_REQUEST_ERROR_MESSAGE, CODE_API_RATE_LIMITED_ERROR_MESSAGE, CODE_API_UNAVAILABLE_ERROR_MESSAGE, CODE_ARTIFACT_PATH_GUIDANCE, CODE_EXECUTION_TOOLS, Calculator, CalculatorSchema, CalculatorToolDefinition, CalculatorToolDescription, CalculatorToolName, Callback, ChatModelStreamHandler, ChatOpenRouter, CloudflareBashExecutionToolDescription, CloudflareCodeExecutionToolDescription, CodeApiRequestError, CodeExecutionToolDefinition, CodeExecutionToolDescription, CodeExecutionToolName, CodeExecutionToolSchema, Command, CommonEvents, CompileCheckToolName, Constants, ContentTypes, CustomOpenAIClient, DATE_RANGE, DEFAULT_CONTEXT_PRUNING_SETTINGS, DEFAULT_COUNTRY_DESCRIPTION, DEFAULT_HOOK_TIMEOUT_MS, DEFAULT_MAX_SEALS, DEFAULT_PROMPT_CACHE_TTL, DEFAULT_QUERY_DESCRIPTION, DEFAULT_RECURSION_LIMIT, DEFAULT_RESERVE_RATIO, DEFAULT_RETAIN_RECENT_TURNS, 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, HOOK_INJECTED_MESSAGES_CAPABLE, HOOK_PREEMPT_BOUNDARY_CAPABLE, HandlerRegistry, HookRegistry, HumanMessage, IMAGE_TOKEN_SAFETY_MARGIN, INTENT_ARG, INTENT_DESCRIPTION, INTENT_PROPERTY, 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, PREDECESSOR_HANDOFF_CUE, PREEMPT_BOUNDARY_HOOK_TIMEOUT_MS, ProgrammaticToolCallingDefinition, ProgrammaticToolCallingDescription, ProgrammaticToolCallingName, ProgrammaticToolCallingSchema, PromptTemplate, Providers, REMOVE_ALL_MESSAGES, REPLY_PRIMER_TOKENS, ReadFileToolDefinition, ReadFileToolDescription, ReadFileToolName, ReadFileToolSchema, Run, Runnable, RunnableCallable, RunnableLambda, RunnableSequence, SDK_STREAM_DISPATCH, STATEFUL_BASH_NOTE, STATEFUL_ENV_NOTE, SessionManager, SkillToolDefinition, SkillToolDescription, SkillToolName, SkillToolSchema, StandardGraph, StatefulBashExecutionToolDescription, StatefulCodeExecutionToolDescription, 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, addBedrockTailCacheControl, addCacheControl, addCacheControlToStablePrefixMessages, addTailCacheControl, appendCodeSessionFileSummary, appendFailedExecutionFileReminder, appendPredecessorHandoffCue, appendTmpScratchReminder, applyContextPruning, applyEdit, applyOutcome, applyPreToolUseHooksForBridge, apportionTokenCounts, askUserQuestion, attemptInvoke, bashAstFindingsToErrors, buildAnthropicCacheControl, buildBashExecutionToolDescription, buildBashExecutionToolSchema, buildBedrockCachePoint, buildChildInputs, buildCodeApiExecutionErrorMessage, buildCodeApiHttpErrorMessage, buildCodeExecutionToolDescription, buildCodeExecutionToolSchema, buildSandboxRuntimeConfig, buildSubagentToolParams, calculateMaxToolCallInputChars, calculateMaxToolResultChars, calculateMaxTotalToolOutputSize, calculateTotalTokens, canSealPreempt, checkValidNumber, clampCalibrationRatio, classifyAttachment, clientExecTimeoutMs, clientFsTimeoutMs, cloneMessage, coalesceAdjacentUserTurns, composeEventHandlers, convertInjectedMessages, 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, createToolErrorOwnership, createToolPolicyHook, createToolSearch, createWorkspacePolicyHook, dateSchema, decodeFile, defaultOmitOptions, deserializeMessage, dispatchesChatModelStream, emptyOutputMessage, encodeFile, encodingForModel, enforceOriginalContentCap, ensureThinkingBlockInMessages, escapeRegexSpecialChars, estimateAnthropicImageTokens, estimateDocumentBlockTokens, estimateImageBlockTokens, estimateOpenAIImageTokens, estimateTimedMediaBlockTokens, execWithClientTimeout, executeCloudflareBash, executeCloudflareCode, executeHooks, executeLocalBash, executeLocalBashWithArgs, executeLocalCode, executeParallelSearches, executeTools, extractErrorMessage, extractImageDimensions, extractMcpServerName, extractTextFromContent, extractToolDiscoveries, extractUsedBashToolNames, extractUsedToolNames, fetchSessionFiles, filterBashToolsByUsage, filterSubagentResult, filterToolsByUsage, findLastIndex, foldToolBlocksForToollessAgent, formatAgentMessages, formatAnthropicArtifactContent, formatAnthropicMessage, formatArtifactPayload, formatCloudflareOutput, formatCompletedResponse, formatContentStrings, formatFromLangChain, formatLangChainMessages, formatMediaMessage, formatMessage, formatServerListing, formatSkillCatalog, getAvailableMcpServers, getBaseToolName, getBufferString, getChatModelClass, getChunkContent, getCloudflareWorkspaceRoot, getCodeBaseURL, getContextOverflowInfo, getConverseOverrideMessage, getDeferredToolsListing, getLocalCwd, getLocalSessionId, getMaxOutputTokensKey, getMessageId, getMessagesWithinTokenLimit, getReadRoots, getSpawn, getTokenCountForMessage, getWorkspaceFS, getWorkspaceRoots, getWriteRoots, handleServerToolResult, handleToolCallChunks, handleToolCalls, hasNestedQuantifier, hasNestedQuantifiers, hasToolSearchInCurrentTurn, hasUnsafeStructuredSerialization, imageAttachmentContent, imagesSchema, initializeModel, interrupt, isAIMessage, isAnthropicLike, isBaseMessage, isContextOverflowError, isDangerousPattern, isFromAnyMcpServer, isFromMcpServer, isGoogleLike, isIntentLabelProperty, isInterrupted, isLegacyConvertible, isLikelyContextOverflowError, isOpenAILike, isPresent, isSyntheticProviderContextMessage, isThinkingEnabled, isToolMessage, isZodSchema, joinKeys, labelContentByAgent, locateEdit, makeIsDeferred, makeRequest, maskConsumedToolResults, matchesQuery, messagesStateReducer, modifyDeltaProperties, newsSchema, normalizeBashToolResultsForReplay, normalizeCodeApiRequestError, normalizeServerFilter, normalizeToBashIdentifier, normalizeToPythonIdentifier, outcomeFieldsFromResult, parseBooleanEnv, partitionAndMarkAnthropicToolCache, performLocalSearch, preFlightTruncateToolCallInputs, preFlightTruncateToolResults, projectAgentContextUsage, projectAnthropicArtifactContent, projectArtifactPayload, projectCacheControlledToolOutputsToText, projectComputerCallOutputsToText, projectOpenAIChatToolMessageContent, projectOpenAIResponsesToolMessageContent, projectOpenAIToolMessageContent, projectOpenRouterToolMessageContent, projectSingleTextToolOutputsToText, projectStructuredToolOutputsToText, projectToolCallInputs, projectToolStreamContentForProvider, querySchema, readIntent, readOutcomeFields, removePredecessorHandoffCue, repairOrphanedToolMessages, resetIfNotEmpty, resolveBedrockPromptCacheTtl, resolveCloudflareSandbox, resolveCodeApiAuthHeaders, resolveContextPruningSettings, resolveLocalExecutionConfig, resolveLocalExecutionTools, resolveLocalToolRegistry, resolveLocalToolsForBinding, resolvePromptCacheTtl, resolveSearchOutcome, resolveSubagentConfigs, resolveToolOutcome, resolveWorkspacePath, resolveWorkspacePathSafe, runBashAstChecks, runPostEditSyntaxCheck, sanitizeOrphanToolBlocks, sanitizeRegex, serializeMessage, serializeToolCallInput, shellQuote, shiftIndexTokenCountMap, shouldTriggerSummarization, sleep, spawnLocalProcess, splitAtRecencyBoundary, strictAlternationProviders, stripAnthropicCacheControl, stripBedrockCacheControl, stripCodeSessionFileSummary, stripIntent, summarizeEvent, supportsBedrockToolCache, syncBudgetDerivedFields, toJsonSchema, tool, toolResultTypes, toolsCondition, truncateLocalOutput, truncateToolInput, truncateToolResultContent, tryFallbackProviders, unescapeObject, unwrapToolResponse, validateBashCommand, validateCloudflareBashCommand, videosSchema, withClientTimeout, withIntent, withMessageRole };
@@ -0,0 +1,91 @@
1
+ import "../common/enum.mjs";
2
+ import "../common/index.mjs";
3
+ import { HumanMessage } from "@langchain/core/messages";
4
+ //#region src/messages/alternation.ts
5
+ /**
6
+ * Providers whose APIs specify strict user/assistant alternation. Mistral
7
+ * rejects consecutive user turns outright. Bedrock's Converse API documents
8
+ * the alternation requirement across many model families; enforcement varies
9
+ * by family — Claude on Converse currently tolerates adjacent user turns
10
+ * (verified live, 2026-07-28) — so the payload is normalized for all of them
11
+ * rather than betting on per-family leniency. Anthropic's own Messages API,
12
+ * OpenAI and Gemini all accept consecutive user turns, so they are
13
+ * deliberately absent.
14
+ */
15
+ const strictAlternationProviders = new Set([
16
+ "bedrock",
17
+ "mistral",
18
+ "mistralai"
19
+ ]);
20
+ const TOOL_RESULT_TYPES = new Set(["tool_result", "toolResult"]);
21
+ /**
22
+ * True when every block is a tool result. Both vendored converters already
23
+ * merge adjacent runs of these, and folding one into a text turn would break
24
+ * the tool pairing they depend on — so they are left alone here.
25
+ */
26
+ function isToolResultMessage(message) {
27
+ const { content } = message;
28
+ if (typeof content === "string" || content.length === 0) return false;
29
+ return content.every((block) => typeof block.type === "string" && TOOL_RESULT_TYPES.has(block.type));
30
+ }
31
+ function toBlocks(content) {
32
+ if (typeof content === "string") return content === "" ? [] : [{
33
+ type: "text",
34
+ text: content
35
+ }];
36
+ return content;
37
+ }
38
+ function joinContent(left, right) {
39
+ if (typeof left === "string" && typeof right === "string") return left === "" ? right : `${left}\n\n${right}`;
40
+ return [...toBlocks(left), ...toBlocks(right)];
41
+ }
42
+ /**
43
+ * Merges runs of consecutive human turns into one, for providers that reject
44
+ * them. Purely a wire-shaping pass: it returns a new array of new messages,
45
+ * so graph state and the host's persisted messages keep the per-message
46
+ * identity that steer rendering and the trailing-steer anchor rely on.
47
+ *
48
+ * Tool-result turns are excluded — the converters merge those themselves, and
49
+ * combining one with a text turn would orphan the pairing.
50
+ */
51
+ function coalesceAdjacentUserTurns(messages) {
52
+ const result = [];
53
+ let mergedAny = false;
54
+ for (const message of messages) {
55
+ const previous = result[result.length - 1];
56
+ if (!(result.length > 0 && previous.getType() === "human" && message.getType() === "human" && !isToolResultMessage(previous) && !isToolResultMessage(message))) {
57
+ result.push(message);
58
+ continue;
59
+ }
60
+ mergedAny = true;
61
+ result[result.length - 1] = new HumanMessage({
62
+ content: joinContent(previous.content, message.content),
63
+ /**
64
+ * The LATER turn's kwargs, deliberately. The one provider-path consumer
65
+ * of these flags is the prompt-cache tail anchor, and it reasons
66
+ * positionally: `isSyntheticMetaMessage` decides whether a breakpoint
67
+ * may be inserted after the message's LAST text block. Merging keeps
68
+ * the last block's provenance only if the last part's kwargs survive —
69
+ * a skill body absorbed into a real user turn must stay anchorable
70
+ * (the real turn ends it), while a real steer absorbed into a trailing
71
+ * skill body must not pin the cache to the volatile body. The first
72
+ * turn's id is kept so origin tracking can re-attach by key.
73
+ */
74
+ additional_kwargs: message.additional_kwargs,
75
+ ...previous.id != null && { id: previous.id }
76
+ });
77
+ }
78
+ /**
79
+ * Identity on the no-merge path. The pass runs twice for a primary
80
+ * Bedrock/Mistral call — once in `createCallModel` (must precede the cache
81
+ * breakpoint) and once in the `attemptInvoke` funnel (must cover fallback
82
+ * and summarization sends) — so the already-normalized second pass returns
83
+ * the SAME array rather than reallocating a context-sized copy, and
84
+ * callers can cheaply detect "nothing changed" by identity.
85
+ */
86
+ return mergedAny ? result : messages;
87
+ }
88
+ //#endregion
89
+ export { coalesceAdjacentUserTurns, strictAlternationProviders };
90
+
91
+ //# sourceMappingURL=alternation.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"alternation.mjs","names":[],"sources":["../../../src/messages/alternation.ts"],"sourcesContent":["// src/messages/alternation.ts\nimport { HumanMessage } from '@langchain/core/messages';\nimport type { BaseMessage, MessageContent } from '@langchain/core/messages';\nimport { Providers } from '@/common';\n\n/**\n * Providers whose APIs specify strict user/assistant alternation. Mistral\n * rejects consecutive user turns outright. Bedrock's Converse API documents\n * the alternation requirement across many model families; enforcement varies\n * by family — Claude on Converse currently tolerates adjacent user turns\n * (verified live, 2026-07-28) — so the payload is normalized for all of them\n * rather than betting on per-family leniency. Anthropic's own Messages API,\n * OpenAI and Gemini all accept consecutive user turns, so they are\n * deliberately absent.\n */\nexport const strictAlternationProviders: ReadonlySet<Providers> = new Set([\n Providers.BEDROCK,\n Providers.MISTRAL,\n Providers.MISTRALAI,\n]);\n\nconst TOOL_RESULT_TYPES = new Set(['tool_result', 'toolResult']);\n\n/**\n * True when every block is a tool result. Both vendored converters already\n * merge adjacent runs of these, and folding one into a text turn would break\n * the tool pairing they depend on — so they are left alone here.\n */\nfunction isToolResultMessage(message: BaseMessage): boolean {\n const { content } = message;\n if (typeof content === 'string' || content.length === 0) {\n return false;\n }\n return content.every(\n (block) =>\n typeof block.type === 'string' && TOOL_RESULT_TYPES.has(block.type)\n );\n}\n\nfunction toBlocks(content: MessageContent): Exclude<MessageContent, string> {\n if (typeof content === 'string') {\n return content === '' ? [] : [{ type: 'text', text: content }];\n }\n return content;\n}\n\nfunction joinContent(\n left: MessageContent,\n right: MessageContent\n): MessageContent {\n if (typeof left === 'string' && typeof right === 'string') {\n return left === '' ? right : `${left}\\n\\n${right}`;\n }\n return [...toBlocks(left), ...toBlocks(right)];\n}\n\n/**\n * Merges runs of consecutive human turns into one, for providers that reject\n * them. Purely a wire-shaping pass: it returns a new array of new messages,\n * so graph state and the host's persisted messages keep the per-message\n * identity that steer rendering and the trailing-steer anchor rely on.\n *\n * Tool-result turns are excluded — the converters merge those themselves, and\n * combining one with a text turn would orphan the pairing.\n */\nexport function coalesceAdjacentUserTurns(\n messages: BaseMessage[]\n): BaseMessage[] {\n const result: BaseMessage[] = [];\n let mergedAny = false;\n for (const message of messages) {\n const previous = result[result.length - 1];\n const mergeable =\n result.length > 0 &&\n previous.getType() === 'human' &&\n message.getType() === 'human' &&\n !isToolResultMessage(previous) &&\n !isToolResultMessage(message);\n\n if (!mergeable) {\n result.push(message);\n continue;\n }\n mergedAny = true;\n\n result[result.length - 1] = new HumanMessage({\n content: joinContent(previous.content, message.content),\n /**\n * The LATER turn's kwargs, deliberately. The one provider-path consumer\n * of these flags is the prompt-cache tail anchor, and it reasons\n * positionally: `isSyntheticMetaMessage` decides whether a breakpoint\n * may be inserted after the message's LAST text block. Merging keeps\n * the last block's provenance only if the last part's kwargs survive —\n * a skill body absorbed into a real user turn must stay anchorable\n * (the real turn ends it), while a real steer absorbed into a trailing\n * skill body must not pin the cache to the volatile body. The first\n * turn's id is kept so origin tracking can re-attach by key.\n */\n additional_kwargs: message.additional_kwargs,\n ...(previous.id != null && { id: previous.id }),\n });\n }\n /**\n * Identity on the no-merge path. The pass runs twice for a primary\n * Bedrock/Mistral call — once in `createCallModel` (must precede the cache\n * breakpoint) and once in the `attemptInvoke` funnel (must cover fallback\n * and summarization sends) — so the already-normalized second pass returns\n * the SAME array rather than reallocating a context-sized copy, and\n * callers can cheaply detect \"nothing changed\" by identity.\n */\n return mergedAny ? result : messages;\n}\n"],"mappings":";;;;;;;;;;;;;;AAeA,MAAa,6BAAqD,IAAI,IAAI;;;;AAI1E,CAAC;AAED,MAAM,oBAAoB,IAAI,IAAI,CAAC,eAAe,YAAY,CAAC;;;;;;AAO/D,SAAS,oBAAoB,SAA+B;CAC1D,MAAM,EAAE,YAAY;CACpB,IAAI,OAAO,YAAY,YAAY,QAAQ,WAAW,GACpD,OAAO;CAET,OAAO,QAAQ,OACZ,UACC,OAAO,MAAM,SAAS,YAAY,kBAAkB,IAAI,MAAM,IAAI,CACtE;AACF;AAEA,SAAS,SAAS,SAA0D;CAC1E,IAAI,OAAO,YAAY,UACrB,OAAO,YAAY,KAAK,CAAC,IAAI,CAAC;EAAE,MAAM;EAAQ,MAAM;CAAQ,CAAC;CAE/D,OAAO;AACT;AAEA,SAAS,YACP,MACA,OACgB;CAChB,IAAI,OAAO,SAAS,YAAY,OAAO,UAAU,UAC/C,OAAO,SAAS,KAAK,QAAQ,GAAG,KAAK,MAAM;CAE7C,OAAO,CAAC,GAAG,SAAS,IAAI,GAAG,GAAG,SAAS,KAAK,CAAC;AAC/C;;;;;;;;;;AAWA,SAAgB,0BACd,UACe;CACf,MAAM,SAAwB,CAAC;CAC/B,IAAI,YAAY;CAChB,KAAK,MAAM,WAAW,UAAU;EAC9B,MAAM,WAAW,OAAO,OAAO,SAAS;EAQxC,IAAI,EANF,OAAO,SAAS,KAChB,SAAS,QAAQ,MAAM,WACvB,QAAQ,QAAQ,MAAM,WACtB,CAAC,oBAAoB,QAAQ,KAC7B,CAAC,oBAAoB,OAAO,IAEd;GACd,OAAO,KAAK,OAAO;GACnB;EACF;EACA,YAAY;EAEZ,OAAO,OAAO,SAAS,KAAK,IAAI,aAAa;GAC3C,SAAS,YAAY,SAAS,SAAS,QAAQ,OAAO;;;;;;;;;;;;GAYtD,mBAAmB,QAAQ;GAC3B,GAAI,SAAS,MAAM,QAAQ,EAAE,IAAI,SAAS,GAAG;EAC/C,CAAC;CACH;;;;;;;;;CASA,OAAO,YAAY,SAAS;AAC9B"}
@@ -182,6 +182,19 @@ function formatToolCallOutput(output) {
182
182
  return compactToolContent(output, HARD_MAX_TOOL_RESULT_CHARS).content;
183
183
  }
184
184
  /**
185
+ * Content for the synthetic assistant turn that separates a trailing steer
186
+ * from the next user turn. Non-empty by necessity — see the push site.
187
+ */
188
+ const STEER_ANCHOR_PLACEHOLDER = "_";
189
+ /**
190
+ * True when an assistant message replayed as a steer and nothing followed it,
191
+ * so the emitted run ends on the steer's `HumanMessage`.
192
+ */
193
+ function endsWithSteerMessage(formatted) {
194
+ if (formatted.length === 0) return false;
195
+ return formatted[formatted.length - 1].additional_kwargs.source === "steer";
196
+ }
197
+ /**
185
198
  * Helper function to format an assistant message
186
199
  * @param message The message to format
187
200
  * @param options Optional formatting options
@@ -617,6 +630,27 @@ function extractSkillName(args) {
617
630
  */
618
631
  const formatAgentMessages = (payload, indexTokenCountMap, tools, skills, options) => {
619
632
  const messages = [];
633
+ /**
634
+ * A steer ended the previous payload entry, so the next message emitted —
635
+ * whichever entry finally produces one — must be separated from it by an
636
+ * assistant turn. Held rather than emitted so an entry that produces
637
+ * nothing cannot leave the anchor stranded as the final turn.
638
+ */
639
+ let pendingSteerAnchor = false;
640
+ /**
641
+ * Emits the deferred anchor ahead of `next` — the message about to be
642
+ * pushed. When that message is itself an assistant turn, it already IS the
643
+ * separation the anchor exists to synthesize, so the intent is simply
644
+ * discharged: emitting the placeholder anyway would put two assistant turns
645
+ * back to back, which strict-alternation providers can reject and nothing downstream
646
+ * repairs (`coalesceAdjacentUserTurns` merges user turns only).
647
+ */
648
+ const flushSteerAnchor = (next) => {
649
+ if (!pendingSteerAnchor) return;
650
+ pendingSteerAnchor = false;
651
+ if (next.role === "assistant") return;
652
+ messages.push(withMessageRole(new AIMessage({ content: STEER_ANCHOR_PLACEHOLDER }), "assistant"));
653
+ };
620
654
  const updatedIndexTokenCountMap = {};
621
655
  let boundaryTokenAdjustment;
622
656
  const indexMapping = {};
@@ -652,6 +686,7 @@ const formatAgentMessages = (payload, indexTokenCountMap, tools, skills, options
652
686
  langChain: true
653
687
  });
654
688
  if (sourceMessageId != null && sourceMessageId !== "") formattedMessage.id = sourceMessageId;
689
+ flushSteerAnchor(formattedMessage);
655
690
  messages.push(formattedMessage);
656
691
  indexMapping[i] = [messages.length - 1];
657
692
  continue;
@@ -755,7 +790,44 @@ const formatAgentMessages = (payload, indexTokenCountMap, tools, skills, options
755
790
  provider: options?.provider
756
791
  });
757
792
  if (sourceMessageId != null && sourceMessageId !== "") for (const formattedMessage of formattedMessages) formattedMessage.id = sourceMessageId;
793
+ /**
794
+ * A steer that ends an assistant message leaves the replay on a
795
+ * `HumanMessage`. The next payload message is itself a user turn, so the
796
+ * sequence would reach the provider as two adjacent user turns — rejected
797
+ * by strict-alternation providers. Anchor it with a placeholder assistant
798
+ * turn.
799
+ *
800
+ * The placeholder must be NON-EMPTY. A string-content assistant message
801
+ * with no tool calls passes through `_convertMessagesToAnthropicPayload`
802
+ * verbatim — the empty-text repair there only covers array content and
803
+ * tool-call turns — so an empty anchor would reach Anthropic as
804
+ * `{role: 'assistant', content: ''}` and trade one invalid sequence for
805
+ * another. Same single-underscore convention the Anthropic converter
806
+ * already uses when it has to synthesize a non-empty block.
807
+ *
808
+ * Deferred rather than decided by lookahead. `i < payload.length - 1` only
809
+ * proves a later ENTRY exists, not that it EMITS: entries with empty
810
+ * content, and entries dropped by `applySummaryBoundary`, are skipped
811
+ * silently. A trailing steer followed only by those would get the anchor
812
+ * as the FINAL turn — an assistant prefill with no request after it, which
813
+ * the model may simply never answer. So the intent is recorded and flushed
814
+ * only when a message actually follows.
815
+ *
816
+ * Pushed AFTER the id stamping above, deliberately. `messagesStateReducer`
817
+ * treats a repeated id as replace-in-place, so an anchor carrying the
818
+ * shared `sourceMessageId` would overwrite the steer it exists to protect.
819
+ * Left unstamped, it reaches the reducer with a null id and is assigned a
820
+ * fresh one. `endsWithSteerMessage` reads only `additional_kwargs.source`,
821
+ * so the deferral cannot change which messages get anchored.
822
+ */
823
+ /**
824
+ * Guarded on emission: an assistant entry whose blocks all filtered away
825
+ * emits nothing, and flushing for it would strand the anchor as the final
826
+ * turn — the pending flag stays set for whichever entry emits next.
827
+ */
828
+ if (formattedMessages.length > 0) flushSteerAnchor(formattedMessages[0]);
758
829
  messages.push(...formattedMessages);
830
+ if (endsWithSteerMessage(formattedMessages)) pendingSteerAnchor = true;
759
831
  const endMessageIndex = messages.length;
760
832
  if (pendingSkillNames?.size != null && pendingSkillNames.size > 0) {
761
833
  const skipSkillBodyNames = options?.skipSkillBodyNames;