@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,132 @@
1
+ require("../common/constants.cjs");
2
+ require("../common/enum.cjs");
3
+ require("../common/index.cjs");
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
+ exports.canSealPreempt = canSealPreempt;
130
+ exports.resolveMaxSeals = resolveMaxSeals;
131
+
132
+ //# sourceMappingURL=preempt.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preempt.cjs","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/cjs/main.cjs CHANGED
@@ -5,6 +5,7 @@ const require_enum = require("./common/enum.cjs");
5
5
  require("./common/index.cjs");
6
6
  const require_truncation = require("./utils/truncation.cjs");
7
7
  const require_tokens = require("./utils/tokens.cjs");
8
+ const require_preempt = require("./llm/preempt.cjs");
8
9
  const require_cache = require("./messages/cache.cjs");
9
10
  const require_core = require("./messages/core.cjs");
10
11
  const require_ids = require("./messages/ids.cjs");
@@ -16,12 +17,16 @@ const require_format = require("./messages/format.cjs");
16
17
  const require_anthropicToolCache = require("./messages/anthropicToolCache.cjs");
17
18
  const require_content = require("./messages/content.cjs");
18
19
  const require_tools = require("./messages/tools.cjs");
20
+ const require_injected = require("./messages/injected.cjs");
21
+ const require_alternation = require("./messages/alternation.cjs");
22
+ const require_handoffCue = require("./messages/handoffCue.cjs");
19
23
  const require_reducer = require("./messages/reducer.cjs");
20
24
  const require_recency = require("./messages/recency.cjs");
21
25
  require("./messages/index.cjs");
22
26
  const require_graph = require("./utils/graph.cjs");
23
27
  const require_llm = require("./utils/llm.cjs");
24
28
  const require_handlers = require("./tools/handlers.cjs");
29
+ const require_intentArg = require("./tools/intentArg.cjs");
25
30
  const require_stream = require("./stream.cjs");
26
31
  const require_events = require("./events.cjs");
27
32
  const require_handlers$1 = require("./utils/handlers.cjs");
@@ -75,7 +80,6 @@ const require_fake = require("./llm/fake.cjs");
75
80
  const require_Graph = require("./graphs/Graph.cjs");
76
81
  const require_MultiAgentGraph = require("./graphs/MultiAgentGraph.cjs");
77
82
  const require_run$1 = require("./run.cjs");
78
- const require_splitStream = require("./splitStream.cjs");
79
83
  require("./graphs/index.cjs");
80
84
  const require_projection = require("./agents/projection.cjs");
81
85
  const require_Calculator = require("./tools/Calculator.cjs");
@@ -182,8 +186,10 @@ exports.DATE_RANGE = require_schema$2.DATE_RANGE;
182
186
  exports.DEFAULT_CONTEXT_PRUNING_SETTINGS = require_contextPruningSettings.DEFAULT_CONTEXT_PRUNING_SETTINGS;
183
187
  exports.DEFAULT_COUNTRY_DESCRIPTION = require_schema$2.DEFAULT_COUNTRY_DESCRIPTION;
184
188
  exports.DEFAULT_HOOK_TIMEOUT_MS = require_executeHooks.DEFAULT_HOOK_TIMEOUT_MS;
189
+ exports.DEFAULT_MAX_SEALS = require_constants.DEFAULT_MAX_SEALS;
185
190
  exports.DEFAULT_PROMPT_CACHE_TTL = require_cache.DEFAULT_PROMPT_CACHE_TTL;
186
191
  exports.DEFAULT_QUERY_DESCRIPTION = require_schema$2.DEFAULT_QUERY_DESCRIPTION;
192
+ exports.DEFAULT_RECURSION_LIMIT = require_constants.DEFAULT_RECURSION_LIMIT;
187
193
  exports.DEFAULT_RESERVE_RATIO = require_prune.DEFAULT_RESERVE_RATIO;
188
194
  exports.DEFAULT_RETAIN_RECENT_TURNS = require_recency.DEFAULT_RETAIN_RECENT_TURNS;
189
195
  exports.DEFAULT_TOOL_TOKEN_MULTIPLIER = require_constants.DEFAULT_TOOL_TOKEN_MULTIPLIER;
@@ -204,6 +210,7 @@ exports.HARD_MAX_TOOL_RESULT_CHARS = require_truncation.HARD_MAX_TOOL_RESULT_CHA
204
210
  exports.HARD_MAX_TOTAL_TOOL_OUTPUT_SIZE = require_truncation.HARD_MAX_TOTAL_TOOL_OUTPUT_SIZE;
205
211
  exports.HOOK_EVENTS = require_types.HOOK_EVENTS;
206
212
  exports.HOOK_INJECTED_MESSAGES_CAPABLE = require_index$5.HOOK_INJECTED_MESSAGES_CAPABLE;
213
+ exports.HOOK_PREEMPT_BOUNDARY_CAPABLE = require_index$5.HOOK_PREEMPT_BOUNDARY_CAPABLE;
207
214
  exports.HandlerRegistry = require_events.HandlerRegistry;
208
215
  exports.HookRegistry = require_HookRegistry.HookRegistry;
209
216
  Object.defineProperty(exports, "HumanMessage", {
@@ -213,6 +220,9 @@ Object.defineProperty(exports, "HumanMessage", {
213
220
  }
214
221
  });
215
222
  exports.IMAGE_TOKEN_SAFETY_MARGIN = require_tokens.IMAGE_TOKEN_SAFETY_MARGIN;
223
+ exports.INTENT_ARG = require_intentArg.INTENT_ARG;
224
+ exports.INTENT_DESCRIPTION = require_intentArg.INTENT_DESCRIPTION;
225
+ exports.INTENT_PROPERTY = require_intentArg.INTENT_PROPERTY;
216
226
  Object.defineProperty(exports, "INTERRUPT", {
217
227
  enumerable: true,
218
228
  get: function() {
@@ -249,6 +259,8 @@ Object.defineProperty(exports, "MemorySaver", {
249
259
  exports.ModelEndHandler = require_events.ModelEndHandler;
250
260
  exports.MultiAgentGraph = require_MultiAgentGraph.MultiAgentGraph;
251
261
  exports.ORIGINAL_CONTENT_MAX_CHARS = require_prune.ORIGINAL_CONTENT_MAX_CHARS;
262
+ exports.PREDECESSOR_HANDOFF_CUE = require_handoffCue.PREDECESSOR_HANDOFF_CUE;
263
+ exports.PREEMPT_BOUNDARY_HOOK_TIMEOUT_MS = require_constants.PREEMPT_BOUNDARY_HOOK_TIMEOUT_MS;
252
264
  exports.ProgrammaticToolCallingDefinition = require_ProgrammaticToolCalling.ProgrammaticToolCallingDefinition;
253
265
  exports.ProgrammaticToolCallingDescription = require_ProgrammaticToolCalling.ProgrammaticToolCallingDescription;
254
266
  exports.ProgrammaticToolCallingName = require_ProgrammaticToolCalling.ProgrammaticToolCallingName;
@@ -286,7 +298,7 @@ Object.defineProperty(exports, "RunnableSequence", {
286
298
  return _langchain_core_runnables.RunnableSequence;
287
299
  }
288
300
  });
289
- exports.SEPARATORS = require_splitStream.SEPARATORS;
301
+ exports.SDK_STREAM_DISPATCH = require_stream.SDK_STREAM_DISPATCH;
290
302
  exports.STATEFUL_BASH_NOTE = require_BashExecutor.STATEFUL_BASH_NOTE;
291
303
  exports.STATEFUL_ENV_NOTE = require_CodeExecutor.STATEFUL_ENV_NOTE;
292
304
  exports.SessionManager = require_JsonlSessionStore.SessionManager;
@@ -294,7 +306,6 @@ exports.SkillToolDefinition = require_SkillTool.SkillToolDefinition;
294
306
  exports.SkillToolDescription = require_SkillTool.SkillToolDescription;
295
307
  exports.SkillToolName = require_SkillTool.SkillToolName;
296
308
  exports.SkillToolSchema = require_SkillTool.SkillToolSchema;
297
- exports.SplitStreamHandler = require_splitStream.SplitStreamHandler;
298
309
  exports.StandardGraph = require_Graph.StandardGraph;
299
310
  exports.StatefulBashExecutionToolDescription = require_BashExecutor.StatefulBashExecutionToolDescription;
300
311
  exports.StatefulCodeExecutionToolDescription = require_CodeExecutor.StatefulCodeExecutionToolDescription;
@@ -356,9 +367,11 @@ exports.addCacheControlToStablePrefixMessages = require_cache.addCacheControlToS
356
367
  exports.addTailCacheControl = require_cache.addTailCacheControl;
357
368
  exports.appendCodeSessionFileSummary = require_CodeSessionFileSummary.appendCodeSessionFileSummary;
358
369
  exports.appendFailedExecutionFileReminder = require_CodeExecutor.appendFailedExecutionFileReminder;
370
+ exports.appendPredecessorHandoffCue = require_handoffCue.appendPredecessorHandoffCue;
359
371
  exports.appendTmpScratchReminder = require_CodeExecutor.appendTmpScratchReminder;
360
372
  exports.applyContextPruning = require_contextPruning.applyContextPruning;
361
373
  exports.applyEdit = require_editStrategies.applyEdit;
374
+ exports.applyOutcome = require_intentArg.applyOutcome;
362
375
  exports.applyPreToolUseHooksForBridge = require_LocalProgrammaticToolCalling.applyPreToolUseHooksForBridge;
363
376
  exports.apportionTokenCounts = require_tokens.apportionTokenCounts;
364
377
  exports.askUserQuestion = require_askUserQuestion.askUserQuestion;
@@ -379,13 +392,16 @@ exports.calculateMaxToolCallInputChars = require_prune.calculateMaxToolCallInput
379
392
  exports.calculateMaxToolResultChars = require_truncation.calculateMaxToolResultChars;
380
393
  exports.calculateMaxTotalToolOutputSize = require_truncation.calculateMaxTotalToolOutputSize;
381
394
  exports.calculateTotalTokens = require_prune.calculateTotalTokens;
395
+ exports.canSealPreempt = require_preempt.canSealPreempt;
382
396
  exports.checkValidNumber = require_prune.checkValidNumber;
383
397
  exports.clampCalibrationRatio = require_prune.clampCalibrationRatio;
384
398
  exports.classifyAttachment = require_attachments.classifyAttachment;
385
399
  exports.clientExecTimeoutMs = require_CloudflareSandboxExecutionEngine.clientExecTimeoutMs;
386
400
  exports.clientFsTimeoutMs = require_CloudflareSandboxExecutionEngine.clientFsTimeoutMs;
387
401
  exports.cloneMessage = require_cache.cloneMessage;
402
+ exports.coalesceAdjacentUserTurns = require_alternation.coalesceAdjacentUserTurns;
388
403
  exports.composeEventHandlers = require_events.composeEventHandlers;
404
+ exports.convertInjectedMessages = require_injected.convertInjectedMessages;
389
405
  exports.convertMessagesToContent = require_core.convertMessagesToContent;
390
406
  exports.countNestedGroups = require_ToolSearch.countNestedGroups;
391
407
  exports.countrySchema = require_schema$2.countrySchema;
@@ -435,6 +451,7 @@ exports.createSchemaOnlyTools = require_schema$1.createSchemaOnlyTools;
435
451
  exports.createSearchTool = require_tool.createSearchTool;
436
452
  exports.createSubagentToolDefinition = require_SubagentTool.createSubagentToolDefinition;
437
453
  exports.createTokenCounter = require_tokens.createTokenCounter;
454
+ exports.createToolErrorOwnership = require_ToolNode.createToolErrorOwnership;
438
455
  exports.createToolPolicyHook = require_createToolPolicyHook.createToolPolicyHook;
439
456
  exports.createToolSearch = require_ToolSearch.createToolSearch;
440
457
  exports.createWorkspacePolicyHook = require_createWorkspacePolicyHook.createWorkspacePolicyHook;
@@ -442,6 +459,7 @@ exports.dateSchema = require_schema$2.dateSchema;
442
459
  exports.decodeFile = require_textEncoding.decodeFile;
443
460
  exports.defaultOmitOptions = require_run$1.defaultOmitOptions;
444
461
  exports.deserializeMessage = require_messageSerialization.deserializeMessage;
462
+ exports.dispatchesChatModelStream = require_stream.dispatchesChatModelStream;
445
463
  exports.emptyOutputMessage = require_CodeExecutor.emptyOutputMessage;
446
464
  exports.encodeFile = require_textEncoding.encodeFile;
447
465
  exports.encodingForModel = require_tokens.encodingForModel;
@@ -548,6 +566,7 @@ exports.isDangerousPattern = require_ToolSearch.isDangerousPattern;
548
566
  exports.isFromAnyMcpServer = require_ToolSearch.isFromAnyMcpServer;
549
567
  exports.isFromMcpServer = require_ToolSearch.isFromMcpServer;
550
568
  exports.isGoogleLike = require_llm.isGoogleLike;
569
+ exports.isIntentLabelProperty = require_intentArg.isIntentLabelProperty;
551
570
  Object.defineProperty(exports, "isInterrupted", {
552
571
  enumerable: true,
553
572
  get: function() {
@@ -582,6 +601,7 @@ exports.normalizeCodeApiRequestError = require_CodeExecutor.normalizeCodeApiRequ
582
601
  exports.normalizeServerFilter = require_ToolSearch.normalizeServerFilter;
583
602
  exports.normalizeToBashIdentifier = require_BashProgrammaticToolCalling.normalizeToBashIdentifier;
584
603
  exports.normalizeToPythonIdentifier = require_ProgrammaticToolCalling.normalizeToPythonIdentifier;
604
+ exports.outcomeFieldsFromResult = require_intentArg.outcomeFieldsFromResult;
585
605
  exports.parseBooleanEnv = require_misc.parseBooleanEnv;
586
606
  exports.partitionAndMarkAnthropicToolCache = require_anthropicToolCache.partitionAndMarkAnthropicToolCache;
587
607
  exports.performLocalSearch = require_ToolSearch.performLocalSearch;
@@ -601,6 +621,9 @@ exports.projectStructuredToolOutputsToText = require_core.projectStructuredToolO
601
621
  exports.projectToolCallInputs = require_prune.projectToolCallInputs;
602
622
  exports.projectToolStreamContentForProvider = require_core.projectToolStreamContentForProvider;
603
623
  exports.querySchema = require_schema$2.querySchema;
624
+ exports.readIntent = require_intentArg.readIntent;
625
+ exports.readOutcomeFields = require_intentArg.readOutcomeFields;
626
+ exports.removePredecessorHandoffCue = require_handoffCue.removePredecessorHandoffCue;
604
627
  exports.repairOrphanedToolMessages = require_prune.repairOrphanedToolMessages;
605
628
  exports.resetIfNotEmpty = require_graph.resetIfNotEmpty;
606
629
  exports.resolveBedrockPromptCacheTtl = require_cache.resolveBedrockPromptCacheTtl;
@@ -612,7 +635,9 @@ exports.resolveLocalExecutionTools = require_resolveLocalExecutionTools.resolveL
612
635
  exports.resolveLocalToolRegistry = require_resolveLocalExecutionTools.resolveLocalToolRegistry;
613
636
  exports.resolveLocalToolsForBinding = require_resolveLocalExecutionTools.resolveLocalToolsForBinding;
614
637
  exports.resolvePromptCacheTtl = require_cache.resolvePromptCacheTtl;
638
+ exports.resolveSearchOutcome = require_tool.resolveSearchOutcome;
615
639
  exports.resolveSubagentConfigs = require_SubagentExecutor.resolveSubagentConfigs;
640
+ exports.resolveToolOutcome = require_intentArg.resolveToolOutcome;
616
641
  exports.resolveWorkspacePath = require_LocalExecutionEngine.resolveWorkspacePath;
617
642
  exports.resolveWorkspacePathSafe = require_LocalExecutionEngine.resolveWorkspacePathSafe;
618
643
  exports.runBashAstChecks = require_bashAst.runBashAstChecks;
@@ -627,9 +652,11 @@ exports.shouldTriggerSummarization = require_index$9.shouldTriggerSummarization;
627
652
  exports.sleep = require_run.sleep;
628
653
  exports.spawnLocalProcess = require_LocalExecutionEngine.spawnLocalProcess;
629
654
  exports.splitAtRecencyBoundary = require_recency.splitAtRecencyBoundary;
655
+ exports.strictAlternationProviders = require_alternation.strictAlternationProviders;
630
656
  exports.stripAnthropicCacheControl = require_cache.stripAnthropicCacheControl;
631
657
  exports.stripBedrockCacheControl = require_cache.stripBedrockCacheControl;
632
658
  exports.stripCodeSessionFileSummary = require_CodeSessionFileSummary.stripCodeSessionFileSummary;
659
+ exports.stripIntent = require_intentArg.stripIntent;
633
660
  exports.summarizeEvent = require_SubagentExecutor.summarizeEvent;
634
661
  exports.supportsBedrockToolCache = require_cache.supportsBedrockToolCache;
635
662
  exports.syncBudgetDerivedFields = require_budget.syncBudgetDerivedFields;
@@ -652,4 +679,5 @@ exports.validateBashCommand = require_LocalExecutionEngine.validateBashCommand;
652
679
  exports.validateCloudflareBashCommand = require_CloudflareSandboxExecutionEngine.validateCloudflareBashCommand;
653
680
  exports.videosSchema = require_schema$2.videosSchema;
654
681
  exports.withClientTimeout = require_CloudflareSandboxExecutionEngine.withClientTimeout;
682
+ exports.withIntent = require_intentArg.withIntent;
655
683
  exports.withMessageRole = require_format.withMessageRole;
@@ -0,0 +1,92 @@
1
+ require("../common/enum.cjs");
2
+ require("../common/index.cjs");
3
+ let _langchain_core_messages = require("@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 _langchain_core_messages.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
+ exports.coalesceAdjacentUserTurns = coalesceAdjacentUserTurns;
90
+ exports.strictAlternationProviders = strictAlternationProviders;
91
+
92
+ //# sourceMappingURL=alternation.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"alternation.cjs","names":["HumanMessage"],"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,IAAIA,yBAAAA,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 require_toolContent.compactToolContent(output, require_truncation.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 _langchain_core_messages.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;