@illuma-ai/agents 1.1.25 → 1.3.0

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 (272) hide show
  1. package/dist/cjs/agents/AgentContext.cjs +20 -3
  2. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  3. package/dist/cjs/common/spawnPath.cjs +104 -0
  4. package/dist/cjs/common/spawnPath.cjs.map +1 -0
  5. package/dist/cjs/graphs/Graph.cjs +87 -31
  6. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  7. package/dist/cjs/graphs/HandoffRegistry.cjs +143 -0
  8. package/dist/cjs/graphs/HandoffRegistry.cjs.map +1 -0
  9. package/dist/cjs/graphs/MultiAgentGraph.cjs +587 -184
  10. package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
  11. package/dist/cjs/graphs/phases/flushLoop.cjs +214 -0
  12. package/dist/cjs/graphs/phases/flushLoop.cjs.map +1 -0
  13. package/dist/cjs/graphs/phases/memoryFlushPhase.cjs +102 -0
  14. package/dist/cjs/graphs/phases/memoryFlushPhase.cjs.map +1 -0
  15. package/dist/cjs/llm/bedrock/index.cjs +4 -3
  16. package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
  17. package/dist/cjs/main.cjs +115 -0
  18. package/dist/cjs/main.cjs.map +1 -1
  19. package/dist/cjs/memory/citations.cjs +69 -0
  20. package/dist/cjs/memory/citations.cjs.map +1 -0
  21. package/dist/cjs/memory/compositeBackend.cjs +60 -0
  22. package/dist/cjs/memory/compositeBackend.cjs.map +1 -0
  23. package/dist/cjs/memory/constants.cjs +232 -0
  24. package/dist/cjs/memory/constants.cjs.map +1 -0
  25. package/dist/cjs/memory/embeddings.cjs +151 -0
  26. package/dist/cjs/memory/embeddings.cjs.map +1 -0
  27. package/dist/cjs/memory/factory.cjs +95 -0
  28. package/dist/cjs/memory/factory.cjs.map +1 -0
  29. package/dist/cjs/memory/migrate.cjs +81 -0
  30. package/dist/cjs/memory/migrate.cjs.map +1 -0
  31. package/dist/cjs/memory/mmr.cjs +138 -0
  32. package/dist/cjs/memory/mmr.cjs.map +1 -0
  33. package/dist/cjs/memory/paths.cjs +217 -0
  34. package/dist/cjs/memory/paths.cjs.map +1 -0
  35. package/dist/cjs/memory/pgvectorStore.cjs +225 -0
  36. package/dist/cjs/memory/pgvectorStore.cjs.map +1 -0
  37. package/dist/cjs/memory/recallTracking.cjs +98 -0
  38. package/dist/cjs/memory/recallTracking.cjs.map +1 -0
  39. package/dist/cjs/memory/schema.sql +51 -0
  40. package/dist/cjs/memory/temporalDecay.cjs +118 -0
  41. package/dist/cjs/memory/temporalDecay.cjs.map +1 -0
  42. package/dist/cjs/nodes/ApprovalGateNode.cjs +1 -1
  43. package/dist/cjs/nodes/ApprovalGateNode.cjs.map +1 -1
  44. package/dist/cjs/prompts/memoryFlushPrompt.cjs +49 -0
  45. package/dist/cjs/prompts/memoryFlushPrompt.cjs.map +1 -0
  46. package/dist/cjs/run.cjs +16 -3
  47. package/dist/cjs/run.cjs.map +1 -1
  48. package/dist/cjs/stream.cjs +4 -4
  49. package/dist/cjs/stream.cjs.map +1 -1
  50. package/dist/cjs/tools/AskUser.cjs +6 -1
  51. package/dist/cjs/tools/AskUser.cjs.map +1 -1
  52. package/dist/cjs/tools/BrowserTools.cjs +1 -1
  53. package/dist/cjs/tools/BrowserTools.cjs.map +1 -1
  54. package/dist/cjs/tools/ToolNode.cjs +127 -10
  55. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  56. package/dist/cjs/tools/approval/constants.cjs +2 -2
  57. package/dist/cjs/tools/approval/constants.cjs.map +1 -1
  58. package/dist/cjs/tools/memory/index.cjs +58 -0
  59. package/dist/cjs/tools/memory/index.cjs.map +1 -0
  60. package/dist/cjs/tools/memory/memoryAppendTool.cjs +69 -0
  61. package/dist/cjs/tools/memory/memoryAppendTool.cjs.map +1 -0
  62. package/dist/cjs/tools/memory/memoryGetTool.cjs +49 -0
  63. package/dist/cjs/tools/memory/memoryGetTool.cjs.map +1 -0
  64. package/dist/cjs/tools/memory/memorySearchTool.cjs +65 -0
  65. package/dist/cjs/tools/memory/memorySearchTool.cjs.map +1 -0
  66. package/dist/cjs/tools/memory/shared.cjs +106 -0
  67. package/dist/cjs/tools/memory/shared.cjs.map +1 -0
  68. package/dist/cjs/types/graph.cjs.map +1 -1
  69. package/dist/cjs/utils/childAgentContext.cjs +242 -0
  70. package/dist/cjs/utils/childAgentContext.cjs.map +1 -0
  71. package/dist/cjs/utils/events.cjs +36 -4
  72. package/dist/cjs/utils/events.cjs.map +1 -1
  73. package/dist/cjs/utils/finishReasons.cjs +44 -0
  74. package/dist/cjs/utils/finishReasons.cjs.map +1 -0
  75. package/dist/cjs/utils/llm.cjs.map +1 -1
  76. package/dist/cjs/utils/logging.cjs +34 -0
  77. package/dist/cjs/utils/logging.cjs.map +1 -0
  78. package/dist/cjs/utils/toolCallNormalization.cjs +250 -0
  79. package/dist/cjs/utils/toolCallNormalization.cjs.map +1 -0
  80. package/dist/esm/agents/AgentContext.mjs +20 -3
  81. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  82. package/dist/esm/common/spawnPath.mjs +95 -0
  83. package/dist/esm/common/spawnPath.mjs.map +1 -0
  84. package/dist/esm/graphs/Graph.mjs +87 -31
  85. package/dist/esm/graphs/Graph.mjs.map +1 -1
  86. package/dist/esm/graphs/HandoffRegistry.mjs +141 -0
  87. package/dist/esm/graphs/HandoffRegistry.mjs.map +1 -0
  88. package/dist/esm/graphs/MultiAgentGraph.mjs +587 -184
  89. package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
  90. package/dist/esm/graphs/phases/flushLoop.mjs +209 -0
  91. package/dist/esm/graphs/phases/flushLoop.mjs.map +1 -0
  92. package/dist/esm/graphs/phases/memoryFlushPhase.mjs +99 -0
  93. package/dist/esm/graphs/phases/memoryFlushPhase.mjs.map +1 -0
  94. package/dist/esm/llm/bedrock/index.mjs +4 -3
  95. package/dist/esm/llm/bedrock/index.mjs.map +1 -1
  96. package/dist/esm/main.mjs +21 -0
  97. package/dist/esm/main.mjs.map +1 -1
  98. package/dist/esm/memory/citations.mjs +64 -0
  99. package/dist/esm/memory/citations.mjs.map +1 -0
  100. package/dist/esm/memory/compositeBackend.mjs +58 -0
  101. package/dist/esm/memory/compositeBackend.mjs.map +1 -0
  102. package/dist/esm/memory/constants.mjs +198 -0
  103. package/dist/esm/memory/constants.mjs.map +1 -0
  104. package/dist/esm/memory/embeddings.mjs +148 -0
  105. package/dist/esm/memory/embeddings.mjs.map +1 -0
  106. package/dist/esm/memory/factory.mjs +93 -0
  107. package/dist/esm/memory/factory.mjs.map +1 -0
  108. package/dist/esm/memory/migrate.mjs +78 -0
  109. package/dist/esm/memory/migrate.mjs.map +1 -0
  110. package/dist/esm/memory/mmr.mjs +130 -0
  111. package/dist/esm/memory/mmr.mjs.map +1 -0
  112. package/dist/esm/memory/paths.mjs +207 -0
  113. package/dist/esm/memory/paths.mjs.map +1 -0
  114. package/dist/esm/memory/pgvectorStore.mjs +223 -0
  115. package/dist/esm/memory/pgvectorStore.mjs.map +1 -0
  116. package/dist/esm/memory/recallTracking.mjs +94 -0
  117. package/dist/esm/memory/recallTracking.mjs.map +1 -0
  118. package/dist/esm/memory/schema.sql +51 -0
  119. package/dist/esm/memory/temporalDecay.mjs +110 -0
  120. package/dist/esm/memory/temporalDecay.mjs.map +1 -0
  121. package/dist/esm/nodes/ApprovalGateNode.mjs +1 -1
  122. package/dist/esm/nodes/ApprovalGateNode.mjs.map +1 -1
  123. package/dist/esm/prompts/memoryFlushPrompt.mjs +44 -0
  124. package/dist/esm/prompts/memoryFlushPrompt.mjs.map +1 -0
  125. package/dist/esm/run.mjs +16 -3
  126. package/dist/esm/run.mjs.map +1 -1
  127. package/dist/esm/stream.mjs +4 -4
  128. package/dist/esm/stream.mjs.map +1 -1
  129. package/dist/esm/tools/AskUser.mjs +6 -1
  130. package/dist/esm/tools/AskUser.mjs.map +1 -1
  131. package/dist/esm/tools/BrowserTools.mjs +1 -1
  132. package/dist/esm/tools/BrowserTools.mjs.map +1 -1
  133. package/dist/esm/tools/ToolNode.mjs +128 -11
  134. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  135. package/dist/esm/tools/approval/constants.mjs +2 -2
  136. package/dist/esm/tools/approval/constants.mjs.map +1 -1
  137. package/dist/esm/tools/memory/index.mjs +46 -0
  138. package/dist/esm/tools/memory/index.mjs.map +1 -0
  139. package/dist/esm/tools/memory/memoryAppendTool.mjs +67 -0
  140. package/dist/esm/tools/memory/memoryAppendTool.mjs.map +1 -0
  141. package/dist/esm/tools/memory/memoryGetTool.mjs +47 -0
  142. package/dist/esm/tools/memory/memoryGetTool.mjs.map +1 -0
  143. package/dist/esm/tools/memory/memorySearchTool.mjs +63 -0
  144. package/dist/esm/tools/memory/memorySearchTool.mjs.map +1 -0
  145. package/dist/esm/tools/memory/shared.mjs +98 -0
  146. package/dist/esm/tools/memory/shared.mjs.map +1 -0
  147. package/dist/esm/types/graph.mjs.map +1 -1
  148. package/dist/esm/utils/childAgentContext.mjs +237 -0
  149. package/dist/esm/utils/childAgentContext.mjs.map +1 -0
  150. package/dist/esm/utils/events.mjs +36 -5
  151. package/dist/esm/utils/events.mjs.map +1 -1
  152. package/dist/esm/utils/finishReasons.mjs +41 -0
  153. package/dist/esm/utils/finishReasons.mjs.map +1 -0
  154. package/dist/esm/utils/llm.mjs.map +1 -1
  155. package/dist/esm/utils/logging.mjs +31 -0
  156. package/dist/esm/utils/logging.mjs.map +1 -0
  157. package/dist/esm/utils/toolCallNormalization.mjs +247 -0
  158. package/dist/esm/utils/toolCallNormalization.mjs.map +1 -0
  159. package/dist/types/common/index.d.ts +1 -0
  160. package/dist/types/common/spawnPath.d.ts +59 -0
  161. package/dist/types/graphs/HandoffRegistry.d.ts +97 -0
  162. package/dist/types/graphs/MultiAgentGraph.d.ts +58 -18
  163. package/dist/types/graphs/index.d.ts +1 -0
  164. package/dist/types/graphs/phases/flushLoop.d.ts +106 -0
  165. package/dist/types/graphs/phases/memoryFlushPhase.d.ts +100 -0
  166. package/dist/types/index.d.ts +7 -0
  167. package/dist/types/memory/__tests__/mockBackend.d.ts +40 -0
  168. package/dist/types/memory/citations.d.ts +39 -0
  169. package/dist/types/memory/compositeBackend.d.ts +30 -0
  170. package/dist/types/memory/constants.d.ts +121 -0
  171. package/dist/types/memory/embeddings.d.ts +15 -0
  172. package/dist/types/memory/factory.d.ts +23 -0
  173. package/dist/types/memory/index.d.ts +21 -0
  174. package/dist/types/memory/migrate.d.ts +14 -0
  175. package/dist/types/memory/mmr.d.ts +50 -0
  176. package/dist/types/memory/paths.d.ts +107 -0
  177. package/dist/types/memory/pgvectorStore.d.ts +56 -0
  178. package/dist/types/memory/recallTracking.d.ts +30 -0
  179. package/dist/types/memory/temporalDecay.d.ts +53 -0
  180. package/dist/types/memory/types.d.ts +182 -0
  181. package/dist/types/prompts/memoryFlushPrompt.d.ts +54 -0
  182. package/dist/types/run.d.ts +1 -0
  183. package/dist/types/tools/AskUser.d.ts +1 -1
  184. package/dist/types/tools/BrowserTools.d.ts +2 -2
  185. package/dist/types/tools/approval/constants.d.ts +2 -2
  186. package/dist/types/tools/memory/index.d.ts +39 -0
  187. package/dist/types/tools/memory/memoryAppendTool.d.ts +27 -0
  188. package/dist/types/tools/memory/memoryGetTool.d.ts +22 -0
  189. package/dist/types/tools/memory/memorySearchTool.d.ts +22 -0
  190. package/dist/types/tools/memory/shared.d.ts +106 -0
  191. package/dist/types/types/graph.d.ts +16 -3
  192. package/dist/types/utils/childAgentContext.d.ts +99 -0
  193. package/dist/types/utils/events.d.ts +21 -0
  194. package/dist/types/utils/finishReasons.d.ts +32 -0
  195. package/dist/types/utils/logging.d.ts +2 -0
  196. package/dist/types/utils/toolCallNormalization.d.ts +44 -0
  197. package/package.json +6 -4
  198. package/src/agents/AgentContext.ts +26 -3
  199. package/src/common/__tests__/enum.test.ts +4 -2
  200. package/src/common/__tests__/spawnPath.test.ts +110 -0
  201. package/src/common/index.ts +1 -0
  202. package/src/common/spawnPath.ts +101 -0
  203. package/src/graphs/Graph.ts +94 -43
  204. package/src/graphs/HandoffRegistry.ts +199 -0
  205. package/src/graphs/MultiAgentGraph.ts +694 -226
  206. package/src/graphs/__tests__/HandoffRegistry.test.ts +410 -0
  207. package/src/graphs/__tests__/multi-agent-delegate.test.ts +61 -16
  208. package/src/graphs/__tests__/multi-agent-edges.test.ts +4 -2
  209. package/src/graphs/__tests__/multi-agent-nested-subgraph.test.ts +221 -0
  210. package/src/graphs/__tests__/structured-output.integration.test.ts +212 -118
  211. package/src/graphs/contextManagement.e2e.test.ts +1 -1
  212. package/src/graphs/index.ts +1 -0
  213. package/src/graphs/phases/__tests__/flushLoop.test.ts +264 -0
  214. package/src/graphs/phases/__tests__/memoryFlushPhase.test.ts +37 -0
  215. package/src/graphs/phases/__tests__/runMemoryFlush.test.ts +150 -0
  216. package/src/graphs/phases/flushLoop.ts +303 -0
  217. package/src/graphs/phases/memoryFlushPhase.ts +209 -0
  218. package/src/index.ts +30 -1
  219. package/src/llm/bedrock/index.ts +4 -5
  220. package/src/memory/__tests__/citations.test.ts +61 -0
  221. package/src/memory/__tests__/compositeBackend.test.ts +79 -0
  222. package/src/memory/__tests__/isolation.test.ts +206 -0
  223. package/src/memory/__tests__/mmr.test.ts +148 -0
  224. package/src/memory/__tests__/mockBackend.ts +161 -0
  225. package/src/memory/__tests__/paths.test.ts +168 -0
  226. package/src/memory/__tests__/recallTracking.test.ts +96 -0
  227. package/src/memory/__tests__/temporalDecay.test.ts +151 -0
  228. package/src/memory/citations.ts +80 -0
  229. package/src/memory/compositeBackend.ts +99 -0
  230. package/src/memory/constants.ts +229 -0
  231. package/src/memory/embeddings.ts +188 -0
  232. package/src/memory/factory.ts +111 -0
  233. package/src/memory/index.ts +46 -0
  234. package/src/memory/migrate.ts +116 -0
  235. package/src/memory/mmr.ts +161 -0
  236. package/src/memory/paths.ts +258 -0
  237. package/src/memory/pgvectorStore.ts +324 -0
  238. package/src/memory/recallTracking.ts +127 -0
  239. package/src/memory/schema.sql +51 -0
  240. package/src/memory/temporalDecay.ts +134 -0
  241. package/src/memory/types.ts +185 -0
  242. package/src/nodes/ApprovalGateNode.ts +4 -10
  243. package/src/nodes/__tests__/ApprovalGateNode.test.ts +11 -20
  244. package/src/prompts/memoryFlushPrompt.ts +78 -0
  245. package/src/run.ts +17 -6
  246. package/src/scripts/test-bedrock-handoff-autonomous.ts +56 -20
  247. package/src/specs/agent-handoffs-bedrock.integration.test.ts +8 -5
  248. package/src/specs/agent-handoffs.test.ts +8 -2
  249. package/src/stream.ts +4 -6
  250. package/src/tools/AskUser.ts +7 -2
  251. package/src/tools/BrowserTools.ts +3 -5
  252. package/src/tools/ToolNode.ts +150 -13
  253. package/src/tools/__tests__/ToolApproval.test.ts +22 -9
  254. package/src/tools/approval/__tests__/constants.test.ts +4 -4
  255. package/src/tools/approval/constants.ts +2 -2
  256. package/src/tools/memory/__tests__/memoryTools.test.ts +205 -0
  257. package/src/tools/memory/index.ts +96 -0
  258. package/src/tools/memory/memoryAppendTool.ts +101 -0
  259. package/src/tools/memory/memoryGetTool.ts +53 -0
  260. package/src/tools/memory/memorySearchTool.ts +80 -0
  261. package/src/tools/memory/shared.ts +169 -0
  262. package/src/tools/search/search.test.ts +6 -1
  263. package/src/types/graph.ts +16 -3
  264. package/src/utils/__tests__/childAgentContext.test.ts +217 -0
  265. package/src/utils/__tests__/finishReasons.test.ts +55 -0
  266. package/src/utils/__tests__/toolCallNormalization.test.ts +181 -0
  267. package/src/utils/childAgentContext.ts +259 -0
  268. package/src/utils/events.ts +37 -4
  269. package/src/utils/finishReasons.ts +40 -0
  270. package/src/utils/llm.ts +0 -1
  271. package/src/utils/logging.ts +45 -8
  272. package/src/utils/toolCallNormalization.ts +271 -0
@@ -1,23 +1,54 @@
1
1
  import { dispatchCustomEvent } from '@langchain/core/callbacks/dispatch';
2
+ import { AsyncLocalStorageProviderSingleton } from '@langchain/core/singletons';
2
3
 
3
4
  /* eslint-disable no-console */
4
5
  // src/utils/events.ts
6
+ /**
7
+ * Returns the RunnableConfig currently active in LangChain's AsyncLocalStorage,
8
+ * or undefined if none is installed. This is the per-async-branch config that
9
+ * LangGraph installs when entering a node — it carries the correct
10
+ * `metadata.spawnKey` for child subgraph invocations inside `Promise.all`
11
+ * parallel handoffs.
12
+ *
13
+ * Prefer this over any Graph-instance-cached config (e.g. `this.config`)
14
+ * when dispatching events from code that may run concurrently across multiple
15
+ * child subgraphs. An instance-level cache is shared state and races between
16
+ * siblings — the last child to enter wins, so events fire with the wrong
17
+ * child's metadata and the backend routes them to the wrong spawnKey.
18
+ */
19
+ function getCurrentRunnableConfig() {
20
+ try {
21
+ return AsyncLocalStorageProviderSingleton.getInstance().getStore();
22
+ }
23
+ catch {
24
+ return undefined;
25
+ }
26
+ }
5
27
  /**
6
28
  * Safely dispatches a custom event and properly awaits it to avoid
7
29
  * race conditions where events are dispatched after run cleanup.
30
+ *
31
+ * **Parallel-handoff correctness:** callers should prefer passing
32
+ * `undefined` (or the per-node runtime config). When `config` is omitted,
33
+ * LangChain's `ensureConfig` reads the current RunnableConfig from
34
+ * AsyncLocalStorage, which is correctly isolated per async branch under
35
+ * `Promise.all`. Passing a stale instance-cached config overrides that
36
+ * implicit config's metadata and cross-contaminates parallel children.
8
37
  */
9
38
  async function safeDispatchCustomEvent(event, payload, config) {
10
39
  try {
11
- await dispatchCustomEvent(event, payload, config);
40
+ // If the caller did not pass a config, fall back to the current
41
+ // AsyncLocalStorage-resident runnable config so nested Promise.all
42
+ // branches each use their own metadata.
43
+ const effectiveConfig = config ?? getCurrentRunnableConfig();
44
+ await dispatchCustomEvent(event, payload, effectiveConfig);
12
45
  }
13
46
  catch (e) {
14
47
  // Check if this is the known EventStreamCallbackHandler error
15
48
  if (e instanceof Error &&
16
49
  e.message.includes('handleCustomEvent: Run ID') &&
17
50
  e.message.includes('not found in run map')) {
18
- // Suppress this specific error - it's expected during parallel execution
19
- // when EventStreamCallbackHandler loses track of run IDs
20
- // console.debug('Suppressed error dispatching custom event:', e);
51
+ // Suppress expected during parallel/async execution
21
52
  return;
22
53
  }
23
54
  // Log other errors
@@ -25,5 +56,5 @@ async function safeDispatchCustomEvent(event, payload, config) {
25
56
  }
26
57
  }
27
58
 
28
- export { safeDispatchCustomEvent };
59
+ export { getCurrentRunnableConfig, safeDispatchCustomEvent };
29
60
  //# sourceMappingURL=events.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"events.mjs","sources":["../../../src/utils/events.ts"],"sourcesContent":["/* eslint-disable no-console */\n// src/utils/events.ts\nimport { dispatchCustomEvent } from '@langchain/core/callbacks/dispatch';\nimport type { RunnableConfig } from '@langchain/core/runnables';\n\n/**\n * Safely dispatches a custom event and properly awaits it to avoid\n * race conditions where events are dispatched after run cleanup.\n */\nexport async function safeDispatchCustomEvent(\n event: string,\n payload: unknown,\n config?: RunnableConfig\n): Promise<void> {\n try {\n await dispatchCustomEvent(event, payload, config);\n } catch (e) {\n // Check if this is the known EventStreamCallbackHandler error\n if (\n e instanceof Error &&\n e.message.includes('handleCustomEvent: Run ID') &&\n e.message.includes('not found in run map')\n ) {\n // Suppress this specific error - it's expected during parallel execution\n // when EventStreamCallbackHandler loses track of run IDs\n // console.debug('Suppressed error dispatching custom event:', e);\n return;\n }\n // Log other errors\n console.error('Error dispatching custom event:', e);\n }\n}\n"],"names":[],"mappings":";;AAAA;AACA;AAIA;;;AAGG;AACI,eAAe,uBAAuB,CAC3C,KAAa,EACb,OAAgB,EAChB,MAAuB,EAAA;AAEvB,IAAA,IAAI;QACF,MAAM,mBAAmB,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC;IACnD;IAAE,OAAO,CAAC,EAAE;;QAEV,IACE,CAAC,YAAY,KAAK;AAClB,YAAA,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,2BAA2B,CAAC;YAC/C,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAAC,EAC1C;;;;YAIA;QACF;;AAEA,QAAA,OAAO,CAAC,KAAK,CAAC,iCAAiC,EAAE,CAAC,CAAC;IACrD;AACF;;;;"}
1
+ {"version":3,"file":"events.mjs","sources":["../../../src/utils/events.ts"],"sourcesContent":["/* eslint-disable no-console */\n// src/utils/events.ts\nimport { dispatchCustomEvent } from '@langchain/core/callbacks/dispatch';\nimport { AsyncLocalStorageProviderSingleton } from '@langchain/core/singletons';\nimport type { RunnableConfig } from '@langchain/core/runnables';\n\n/**\n * Returns the RunnableConfig currently active in LangChain's AsyncLocalStorage,\n * or undefined if none is installed. This is the per-async-branch config that\n * LangGraph installs when entering a node — it carries the correct\n * `metadata.spawnKey` for child subgraph invocations inside `Promise.all`\n * parallel handoffs.\n *\n * Prefer this over any Graph-instance-cached config (e.g. `this.config`)\n * when dispatching events from code that may run concurrently across multiple\n * child subgraphs. An instance-level cache is shared state and races between\n * siblings — the last child to enter wins, so events fire with the wrong\n * child's metadata and the backend routes them to the wrong spawnKey.\n */\nexport function getCurrentRunnableConfig(): RunnableConfig | undefined {\n try {\n return AsyncLocalStorageProviderSingleton.getInstance().getStore() as\n | RunnableConfig\n | undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Safely dispatches a custom event and properly awaits it to avoid\n * race conditions where events are dispatched after run cleanup.\n *\n * **Parallel-handoff correctness:** callers should prefer passing\n * `undefined` (or the per-node runtime config). When `config` is omitted,\n * LangChain's `ensureConfig` reads the current RunnableConfig from\n * AsyncLocalStorage, which is correctly isolated per async branch under\n * `Promise.all`. Passing a stale instance-cached config overrides that\n * implicit config's metadata and cross-contaminates parallel children.\n */\nexport async function safeDispatchCustomEvent(\n event: string,\n payload: unknown,\n config?: RunnableConfig\n): Promise<void> {\n try {\n // If the caller did not pass a config, fall back to the current\n // AsyncLocalStorage-resident runnable config so nested Promise.all\n // branches each use their own metadata.\n const effectiveConfig = config ?? getCurrentRunnableConfig();\n await dispatchCustomEvent(event, payload, effectiveConfig);\n } catch (e) {\n // Check if this is the known EventStreamCallbackHandler error\n if (\n e instanceof Error &&\n e.message.includes('handleCustomEvent: Run ID') &&\n e.message.includes('not found in run map')\n ) {\n // Suppress expected during parallel/async execution\n return;\n }\n // Log other errors\n console.error('Error dispatching custom event:', e);\n }\n}\n"],"names":[],"mappings":";;;AAAA;AACA;AAKA;;;;;;;;;;;;AAYG;SACa,wBAAwB,GAAA;AACtC,IAAA,IAAI;AACF,QAAA,OAAO,kCAAkC,CAAC,WAAW,EAAE,CAAC,QAAQ,EAEnD;IACf;AAAE,IAAA,MAAM;AACN,QAAA,OAAO,SAAS;IAClB;AACF;AAEA;;;;;;;;;;AAUG;AACI,eAAe,uBAAuB,CAC3C,KAAa,EACb,OAAgB,EAChB,MAAuB,EAAA;AAEvB,IAAA,IAAI;;;;AAIF,QAAA,MAAM,eAAe,GAAG,MAAM,IAAI,wBAAwB,EAAE;QAC5D,MAAM,mBAAmB,CAAC,KAAK,EAAE,OAAO,EAAE,eAAe,CAAC;IAC5D;IAAE,OAAO,CAAC,EAAE;;QAEV,IACE,CAAC,YAAY,KAAK;AAClB,YAAA,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,2BAA2B,CAAC;YAC/C,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAAC,EAC1C;;YAEA;QACF;;AAEA,QAAA,OAAO,CAAC,KAAK,CAAC,iCAAiC,EAAE,CAAC,CAAC;IACrD;AACF;;;;"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Finish-reason constants and helpers.
3
+ *
4
+ * LLM providers emit different keys (`finish_reason`, `stop_reason`,
5
+ * `stopReason`, `finishReason`) and different values for the same concept.
6
+ * This module is the single source of truth for detecting *truncation* —
7
+ * i.e., the model hit its output budget and the response is incomplete.
8
+ *
9
+ * Used by:
10
+ * - `Graph.ts` — sticky `lastFinishReason` across inner subgraph invokes,
11
+ * so host's continuation retry can detect truncation that happens
12
+ * inside a scoped-subgraph child node.
13
+ * - Any future continuation / auto-retry logic added to agents.
14
+ *
15
+ * Kept as a small utils module (instead of inline in Graph.ts) so that
16
+ * additional callers — e.g., structured output recovery, sub-agent
17
+ * orchestrators — can reuse the exact same detection logic without drift.
18
+ */
19
+ /**
20
+ * Canonical set of finish-reason strings that mean "output was truncated".
21
+ *
22
+ * Covers:
23
+ * - `max_tokens` — Anthropic direct API, Bedrock
24
+ * - `length` — OpenAI/Azure
25
+ * - `MAX_TOKENS` — VertexAI/Google (uppercased enum)
26
+ */
27
+ const TRUNCATION_FINISH_REASONS = new Set([
28
+ 'max_tokens',
29
+ 'length',
30
+ 'MAX_TOKENS',
31
+ ]);
32
+ /**
33
+ * @returns true when the given finish/stop reason indicates the response
34
+ * was cut short by the output token budget.
35
+ */
36
+ function isTruncationReason(reason) {
37
+ return reason != null && TRUNCATION_FINISH_REASONS.has(reason);
38
+ }
39
+
40
+ export { TRUNCATION_FINISH_REASONS, isTruncationReason };
41
+ //# sourceMappingURL=finishReasons.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"finishReasons.mjs","sources":["../../../src/utils/finishReasons.ts"],"sourcesContent":["/**\n * Finish-reason constants and helpers.\n *\n * LLM providers emit different keys (`finish_reason`, `stop_reason`,\n * `stopReason`, `finishReason`) and different values for the same concept.\n * This module is the single source of truth for detecting *truncation* —\n * i.e., the model hit its output budget and the response is incomplete.\n *\n * Used by:\n * - `Graph.ts` — sticky `lastFinishReason` across inner subgraph invokes,\n * so host's continuation retry can detect truncation that happens\n * inside a scoped-subgraph child node.\n * - Any future continuation / auto-retry logic added to agents.\n *\n * Kept as a small utils module (instead of inline in Graph.ts) so that\n * additional callers — e.g., structured output recovery, sub-agent\n * orchestrators — can reuse the exact same detection logic without drift.\n */\n\n/**\n * Canonical set of finish-reason strings that mean \"output was truncated\".\n *\n * Covers:\n * - `max_tokens` — Anthropic direct API, Bedrock\n * - `length` — OpenAI/Azure\n * - `MAX_TOKENS` — VertexAI/Google (uppercased enum)\n */\nexport const TRUNCATION_FINISH_REASONS: ReadonlySet<string> = new Set([\n 'max_tokens',\n 'length',\n 'MAX_TOKENS',\n]);\n\n/**\n * @returns true when the given finish/stop reason indicates the response\n * was cut short by the output token budget.\n */\nexport function isTruncationReason(reason: string | undefined | null): boolean {\n return reason != null && TRUNCATION_FINISH_REASONS.has(reason);\n}\n"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;AAiBG;AAEH;;;;;;;AAOG;AACI,MAAM,yBAAyB,GAAwB,IAAI,GAAG,CAAC;IACpE,YAAY;IACZ,QAAQ;IACR,YAAY;AACb,CAAA;AAED;;;AAGG;AACG,SAAU,kBAAkB,CAAC,MAAiC,EAAA;IAClE,OAAO,MAAM,IAAI,IAAI,IAAI,yBAAyB,CAAC,GAAG,CAAC,MAAM,CAAC;AAChE;;;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"llm.mjs","sources":["../../../src/utils/llm.ts"],"sourcesContent":["// src/utils/llm.ts\nimport { Providers } from '@/common';\n\nexport function isOpenAILike(provider?: string | Providers): boolean {\n if (provider == null) {\n return false;\n }\n return (\n [\n Providers.OPENAI,\n Providers.AZURE,\n Providers.OPENROUTER,\n Providers.XAI,\n Providers.DEEPSEEK,\n ] as string[]\n ).includes(provider);\n}\n\nexport function isGoogleLike(provider?: string | Providers): boolean {\n if (provider == null) {\n return false;\n }\n return ([Providers.GOOGLE, Providers.VERTEXAI] as string[]).includes(\n provider\n );\n}\n\n"],"names":[],"mappings":";;;AAAA;AAGM,SAAU,YAAY,CAAC,QAA6B,EAAA;AACxD,IAAA,IAAI,QAAQ,IAAI,IAAI,EAAE;AACpB,QAAA,OAAO,KAAK;IACd;IACA,OACE;AACE,QAAA,SAAS,CAAC,MAAM;AAChB,QAAA,SAAS,CAAC,KAAK;AACf,QAAA,SAAS,CAAC,UAAU;AACpB,QAAA,SAAS,CAAC,GAAG;AACb,QAAA,SAAS,CAAC,QAAQ;AAErB,KAAA,CAAC,QAAQ,CAAC,QAAQ,CAAC;AACtB;AAEM,SAAU,YAAY,CAAC,QAA6B,EAAA;AACxD,IAAA,IAAI,QAAQ,IAAI,IAAI,EAAE;AACpB,QAAA,OAAO,KAAK;IACd;AACA,IAAA,OAAQ,CAAC,SAAS,CAAC,MAAM,EAAE,SAAS,CAAC,QAAQ,CAAc,CAAC,QAAQ,CAClE,QAAQ,CACT;AACH;;;;"}
1
+ {"version":3,"file":"llm.mjs","sources":["../../../src/utils/llm.ts"],"sourcesContent":["// src/utils/llm.ts\nimport { Providers } from '@/common';\n\nexport function isOpenAILike(provider?: string | Providers): boolean {\n if (provider == null) {\n return false;\n }\n return (\n [\n Providers.OPENAI,\n Providers.AZURE,\n Providers.OPENROUTER,\n Providers.XAI,\n Providers.DEEPSEEK,\n ] as string[]\n ).includes(provider);\n}\n\nexport function isGoogleLike(provider?: string | Providers): boolean {\n if (provider == null) {\n return false;\n }\n return ([Providers.GOOGLE, Providers.VERTEXAI] as string[]).includes(\n provider\n );\n}\n"],"names":[],"mappings":";;;AAAA;AAGM,SAAU,YAAY,CAAC,QAA6B,EAAA;AACxD,IAAA,IAAI,QAAQ,IAAI,IAAI,EAAE;AACpB,QAAA,OAAO,KAAK;IACd;IACA,OACE;AACE,QAAA,SAAS,CAAC,MAAM;AAChB,QAAA,SAAS,CAAC,KAAK;AACf,QAAA,SAAS,CAAC,UAAU;AACpB,QAAA,SAAS,CAAC,GAAG;AACb,QAAA,SAAS,CAAC,QAAQ;AAErB,KAAA,CAAC,QAAQ,CAAC,QAAQ,CAAC;AACtB;AAEM,SAAU,YAAY,CAAC,QAA6B,EAAA;AACxD,IAAA,IAAI,QAAQ,IAAI,IAAI,EAAE;AACpB,QAAA,OAAO,KAAK;IACd;AACA,IAAA,OAAQ,CAAC,SAAS,CAAC,MAAM,EAAE,SAAS,CAAC,QAAQ,CAAc,CAAC,QAAQ,CAClE,QAAQ,CACT;AACH;;;;"}
@@ -0,0 +1,31 @@
1
+ import 'fs';
2
+ import 'util';
3
+
4
+ // src/utils/logging.ts
5
+ /**
6
+ * Multi-agent debug gate. Controls chatty graph/handoff/transfer/sequence
7
+ * trace logs emitted from `MultiAgentGraph` and `Graph`. Off by default so the
8
+ * package stays quiet when embedded in host apps. Flip to
9
+ * `DEBUG_MULTIAGENT=true` in the host env to re-enable for testing.
10
+ *
11
+ * Exported as functions so runtime toggling via env reload works; the check
12
+ * is cheap (string equality) and only runs when a log site fires.
13
+ */
14
+ function isMultiAgentDebugEnabled() {
15
+ return process.env.DEBUG_MULTIAGENT === 'true';
16
+ }
17
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
18
+ const mlog = (...args) => {
19
+ if (isMultiAgentDebugEnabled()) {
20
+ console.debug(...args);
21
+ }
22
+ };
23
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
24
+ const mwarn = (...args) => {
25
+ if (isMultiAgentDebugEnabled()) {
26
+ console.warn(...args);
27
+ }
28
+ };
29
+
30
+ export { mlog, mwarn };
31
+ //# sourceMappingURL=logging.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logging.mjs","sources":["../../../src/utils/logging.ts"],"sourcesContent":["// src/utils/logging.ts\nimport fs from 'fs';\nimport util from 'util';\n\n/**\n * Multi-agent debug gate. Controls chatty graph/handoff/transfer/sequence\n * trace logs emitted from `MultiAgentGraph` and `Graph`. Off by default so the\n * package stays quiet when embedded in host apps. Flip to\n * `DEBUG_MULTIAGENT=true` in the host env to re-enable for testing.\n *\n * Exported as functions so runtime toggling via env reload works; the check\n * is cheap (string equality) and only runs when a log site fires.\n */\nfunction isMultiAgentDebugEnabled(): boolean {\n return process.env.DEBUG_MULTIAGENT === 'true';\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport const mlog = (...args: any[]): void => {\n if (isMultiAgentDebugEnabled()) {\n console.debug(...args);\n }\n};\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport const mwarn = (...args: any[]): void => {\n if (isMultiAgentDebugEnabled()) {\n console.warn(...args);\n }\n};\n\nexport function setupLogging(logFileName: string): void {\n const logFile = fs.createWriteStream(logFileName, { flags: 'a' });\n\n const originalConsoleLog = console.log;\n const originalConsoleError = console.error;\n const originalStdoutWrite = process.stdout.write;\n const originalStderrWrite = process.stderr.write;\n\n console.log = function (...args): void {\n logFile.write(util.format.apply(null, args) + ' ');\n originalConsoleLog.apply(console, args);\n };\n\n console.error = function (...args): void {\n logFile.write(util.format.apply(null, args) + ' ');\n originalConsoleError.apply(console, args);\n };\n\n process.stdout.write = function (\n buffer: Uint8Array | string,\n cb?: ((err?: Error) => void) | string,\n fd?: (err?: Error) => void\n ): boolean {\n if (typeof buffer === 'string') {\n logFile.write(buffer);\n } else {\n logFile.write(buffer.toString());\n }\n return originalStdoutWrite.call(\n process.stdout,\n buffer,\n cb as BufferEncoding | undefined,\n fd\n );\n };\n\n process.stderr.write = function (\n buffer: Uint8Array | string,\n cb?: ((err?: Error) => void) | string,\n fd?: (err?: Error) => void\n ): boolean {\n if (typeof buffer === 'string') {\n logFile.write(buffer);\n } else {\n logFile.write(buffer.toString());\n }\n return originalStderrWrite.call(\n process.stderr,\n buffer,\n cb as BufferEncoding | undefined,\n fd\n );\n };\n}\n"],"names":[],"mappings":";;;AAAA;AAIA;;;;;;;;AAQG;AACH,SAAS,wBAAwB,GAAA;AAC/B,IAAA,OAAO,OAAO,CAAC,GAAG,CAAC,gBAAgB,KAAK,MAAM;AAChD;AAEA;MACa,IAAI,GAAG,CAAC,GAAG,IAAW,KAAU;IAC3C,IAAI,wBAAwB,EAAE,EAAE;AAC9B,QAAA,OAAO,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC;IACxB;AACF;AAEA;MACa,KAAK,GAAG,CAAC,GAAG,IAAW,KAAU;IAC5C,IAAI,wBAAwB,EAAE,EAAE;AAC9B,QAAA,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACvB;AACF;;;;"}
@@ -0,0 +1,247 @@
1
+ /**
2
+ * Tool-call name normalization for malformed LLM tool_use outputs.
3
+ *
4
+ * LLMs — especially smaller / faster models — frequently emit tool_use
5
+ * blocks with names that don't exactly match the registered tool name:
6
+ *
7
+ * - Wrong delimiter: `outlook/operations` vs `outlook_operations`
8
+ * - Function-prefix style: `functions.outlook_operations`
9
+ * - Case drift: `Outlook_Operations`
10
+ * - Counter suffix: `outlook_operations_1`
11
+ * - Missing name, only id: `{name: "", id: "tool_outlook_operations_42"}`
12
+ *
13
+ * Before this normalization layer, any of the above caused the LangGraph
14
+ * ToolNode to throw "Tool X not found" and the whole turn to fail. With
15
+ * normalization, we resolve to the correct registered name case-insensitively
16
+ * and transparently fix the tool_call in place.
17
+ *
18
+ * Resolution order:
19
+ * 1. Exact match
20
+ * 2. Normalized delimiter + exact
21
+ * 3. Case-insensitive match
22
+ * 4. Structured candidates (strip prefixes, take suffix segments)
23
+ * 5. Infer from tool_call id (strip trailing digits, function/tool prefix)
24
+ *
25
+ * Any unresolvable tool_call is left as-is — the downstream ToolNode will
26
+ * fail with its usual error and the auto-recovery path kicks in.
27
+ */
28
+ function lowercaseOrEmpty(value) {
29
+ return typeof value === 'string' ? value.toLowerCase() : '';
30
+ }
31
+ /** Collapse common delimiters (space, dot, slash, dash) to `_` and fold case. */
32
+ function normalizeDelimiters(name) {
33
+ return name
34
+ .trim()
35
+ .replace(/[\s./-]+/g, '_')
36
+ .replace(/_{2,}/g, '_')
37
+ .replace(/^_+|_+$/g, '');
38
+ }
39
+ function resolveCaseInsensitive(rawName, allowed) {
40
+ if (allowed.size === 0)
41
+ return null;
42
+ const folded = lowercaseOrEmpty(rawName);
43
+ let match = null;
44
+ for (const name of allowed) {
45
+ if (lowercaseOrEmpty(name) !== folded)
46
+ continue;
47
+ // Ambiguous → fail closed
48
+ if (match && match !== name)
49
+ return null;
50
+ match = name;
51
+ }
52
+ return match;
53
+ }
54
+ function resolveExact(rawName, allowed) {
55
+ if (allowed.size === 0)
56
+ return null;
57
+ if (allowed.has(rawName))
58
+ return rawName;
59
+ const normalized = normalizeDelimiters(rawName);
60
+ if (allowed.has(normalized))
61
+ return normalized;
62
+ return (resolveCaseInsensitive(rawName, allowed) ??
63
+ resolveCaseInsensitive(normalized, allowed));
64
+ }
65
+ function buildStructuredCandidates(rawName) {
66
+ const trimmed = rawName.trim();
67
+ if (!trimmed)
68
+ return [];
69
+ const seen = new Set();
70
+ const out = [];
71
+ const add = (value) => {
72
+ const c = value.trim();
73
+ if (!c || seen.has(c))
74
+ return;
75
+ seen.add(c);
76
+ out.push(c);
77
+ };
78
+ add(trimmed);
79
+ add(normalizeDelimiters(trimmed));
80
+ // Also try dot-normalized, then split
81
+ const dotForm = trimmed.replace(/\//g, '.');
82
+ add(dotForm);
83
+ add(normalizeDelimiters(dotForm));
84
+ const segments = dotForm
85
+ .split('.')
86
+ .map((s) => s.trim())
87
+ .filter(Boolean);
88
+ if (segments.length > 1) {
89
+ for (let i = 1; i < segments.length; i++) {
90
+ const suffix = segments.slice(i).join('.');
91
+ add(suffix);
92
+ add(normalizeDelimiters(suffix));
93
+ }
94
+ }
95
+ return out;
96
+ }
97
+ function resolveStructured(rawName, allowed) {
98
+ if (allowed.size === 0)
99
+ return null;
100
+ const candidates = buildStructuredCandidates(rawName);
101
+ for (const c of candidates) {
102
+ if (allowed.has(c))
103
+ return c;
104
+ }
105
+ for (const c of candidates) {
106
+ const ci = resolveCaseInsensitive(c, allowed);
107
+ if (ci)
108
+ return ci;
109
+ }
110
+ return null;
111
+ }
112
+ function inferFromToolCallId(rawId, allowed) {
113
+ if (!rawId || allowed.size === 0)
114
+ return null;
115
+ const id = rawId.trim();
116
+ if (!id)
117
+ return null;
118
+ const tokens = new Set();
119
+ const addTokens = (value) => {
120
+ const t = value.trim();
121
+ if (!t)
122
+ return;
123
+ tokens.add(t);
124
+ tokens.add(t.replace(/[:._/-]\d+$/, ''));
125
+ tokens.add(t.replace(/\d+$/, ''));
126
+ const dotForm = t.replace(/\//g, '.');
127
+ tokens.add(dotForm);
128
+ tokens.add(dotForm.replace(/[:._-]\d+$/, ''));
129
+ tokens.add(dotForm.replace(/\d+$/, ''));
130
+ for (const prefix of [/^functions?[._-]?/i, /^tools?[._-]?/i]) {
131
+ const stripped = dotForm.replace(prefix, '');
132
+ if (stripped !== dotForm) {
133
+ tokens.add(stripped);
134
+ tokens.add(stripped.replace(/[:._-]\d+$/, ''));
135
+ tokens.add(stripped.replace(/\d+$/, ''));
136
+ }
137
+ }
138
+ };
139
+ const preColon = id.split(':')[0] ?? id;
140
+ addTokens(id);
141
+ addTokens(preColon);
142
+ let singleMatch = null;
143
+ for (const token of tokens) {
144
+ const matched = resolveStructured(token, allowed);
145
+ if (!matched)
146
+ continue;
147
+ // Ambiguous → fail closed
148
+ if (singleMatch && singleMatch !== matched)
149
+ return null;
150
+ singleMatch = matched;
151
+ }
152
+ if (singleMatch)
153
+ return singleMatch;
154
+ // Substring fallback: ids frequently look like `call_<toolname>_<n>` or
155
+ // `toolu_01...<toolname>...`. Scan for any allowed name embedded in the
156
+ // delimiter-normalized id (case-insensitive) and fail closed if more than
157
+ // one distinct allowed name matches.
158
+ const haystack = lowercaseOrEmpty(normalizeDelimiters(id));
159
+ let substrMatch = null;
160
+ for (const allowedName of allowed) {
161
+ const needle = lowercaseOrEmpty(allowedName);
162
+ if (!needle)
163
+ continue;
164
+ if (!haystack.includes(needle))
165
+ continue;
166
+ if (substrMatch && substrMatch !== allowedName)
167
+ return null;
168
+ substrMatch = allowedName;
169
+ }
170
+ return substrMatch;
171
+ }
172
+ /**
173
+ * Produce the best allowed tool name for a raw LLM tool_use name.
174
+ * Returns the original name unchanged if no resolution is possible —
175
+ * the downstream executor will then fail with its normal error path.
176
+ */
177
+ function normalizeToolCallName(rawName, allowedToolNames, rawToolCallId) {
178
+ const trimmed = rawName.trim() ?? '';
179
+ if (!trimmed) {
180
+ return (inferFromToolCallId(rawToolCallId, allowedToolNames) ?? rawName ?? '');
181
+ }
182
+ if (allowedToolNames.size === 0)
183
+ return trimmed;
184
+ const exact = resolveExact(trimmed, allowedToolNames);
185
+ if (exact)
186
+ return exact;
187
+ const structured = resolveStructured(trimmed, allowedToolNames);
188
+ if (structured)
189
+ return structured;
190
+ // Last-resort: try the raw id (if supplied). Substring matching lives
191
+ // inside inferFromToolCallId — only safe to invoke when the caller
192
+ // passed a real provider id, not when the raw name was used above.
193
+ const inferred = inferFromToolCallId(rawToolCallId, allowedToolNames);
194
+ if (inferred)
195
+ return inferred;
196
+ return trimmed;
197
+ }
198
+ /**
199
+ * In-place normalization of all tool_calls on an AIMessage-like object.
200
+ *
201
+ * Applies `normalizeToolCallName` to each tool_call's `name` field using the
202
+ * provided allowed-tool set (derived from the agent's toolMap keys). Also
203
+ * rewrites the `name` field inside any content blocks of type `tool_use` so
204
+ * that downstream providers see the corrected name.
205
+ *
206
+ * Returns `true` if any name was rewritten.
207
+ */
208
+ function normalizeMessageToolCalls(message, allowedToolNames) {
209
+ if (!message || typeof message !== 'object')
210
+ return false;
211
+ let changed = false;
212
+ // LangChain AIMessage.tool_calls (normalized form)
213
+ const msg = message;
214
+ if (Array.isArray(msg.tool_calls)) {
215
+ for (const tc of msg.tool_calls) {
216
+ if (!tc || typeof tc !== 'object')
217
+ continue;
218
+ const rawName = typeof tc.name === 'string' ? tc.name : '';
219
+ const rawId = typeof tc.id === 'string' ? tc.id : undefined;
220
+ const normalized = normalizeToolCallName(rawName, allowedToolNames, rawId);
221
+ if (normalized && normalized !== rawName) {
222
+ tc.name = normalized;
223
+ changed = true;
224
+ }
225
+ }
226
+ }
227
+ // Anthropic-style content blocks with type: 'tool_use'
228
+ if (Array.isArray(msg.content)) {
229
+ for (const block of msg.content) {
230
+ if (!block || typeof block !== 'object')
231
+ continue;
232
+ if (block.type !== 'tool_use')
233
+ continue;
234
+ const rawName = typeof block.name === 'string' ? block.name : '';
235
+ const rawId = typeof block.id === 'string' ? block.id : undefined;
236
+ const normalized = normalizeToolCallName(rawName, allowedToolNames, rawId);
237
+ if (normalized && normalized !== rawName) {
238
+ block.name = normalized;
239
+ changed = true;
240
+ }
241
+ }
242
+ }
243
+ return changed;
244
+ }
245
+
246
+ export { normalizeMessageToolCalls, normalizeToolCallName };
247
+ //# sourceMappingURL=toolCallNormalization.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toolCallNormalization.mjs","sources":["../../../src/utils/toolCallNormalization.ts"],"sourcesContent":["/**\n * Tool-call name normalization for malformed LLM tool_use outputs.\n *\n * LLMs — especially smaller / faster models — frequently emit tool_use\n * blocks with names that don't exactly match the registered tool name:\n *\n * - Wrong delimiter: `outlook/operations` vs `outlook_operations`\n * - Function-prefix style: `functions.outlook_operations`\n * - Case drift: `Outlook_Operations`\n * - Counter suffix: `outlook_operations_1`\n * - Missing name, only id: `{name: \"\", id: \"tool_outlook_operations_42\"}`\n *\n * Before this normalization layer, any of the above caused the LangGraph\n * ToolNode to throw \"Tool X not found\" and the whole turn to fail. With\n * normalization, we resolve to the correct registered name case-insensitively\n * and transparently fix the tool_call in place.\n *\n * Resolution order:\n * 1. Exact match\n * 2. Normalized delimiter + exact\n * 3. Case-insensitive match\n * 4. Structured candidates (strip prefixes, take suffix segments)\n * 5. Infer from tool_call id (strip trailing digits, function/tool prefix)\n *\n * Any unresolvable tool_call is left as-is — the downstream ToolNode will\n * fail with its usual error and the auto-recovery path kicks in.\n */\n\nfunction lowercaseOrEmpty(value: string): string {\n return typeof value === 'string' ? value.toLowerCase() : '';\n}\n\n/** Collapse common delimiters (space, dot, slash, dash) to `_` and fold case. */\nfunction normalizeDelimiters(name: string): string {\n return name\n .trim()\n .replace(/[\\s./-]+/g, '_')\n .replace(/_{2,}/g, '_')\n .replace(/^_+|_+$/g, '');\n}\n\nfunction resolveCaseInsensitive(\n rawName: string,\n allowed: Set<string>\n): string | null {\n if (allowed.size === 0) return null;\n const folded = lowercaseOrEmpty(rawName);\n let match: string | null = null;\n for (const name of allowed) {\n if (lowercaseOrEmpty(name) !== folded) continue;\n // Ambiguous → fail closed\n if (match && match !== name) return null;\n match = name;\n }\n return match;\n}\n\nfunction resolveExact(rawName: string, allowed: Set<string>): string | null {\n if (allowed.size === 0) return null;\n if (allowed.has(rawName)) return rawName;\n const normalized = normalizeDelimiters(rawName);\n if (allowed.has(normalized)) return normalized;\n return (\n resolveCaseInsensitive(rawName, allowed) ??\n resolveCaseInsensitive(normalized, allowed)\n );\n}\n\nfunction buildStructuredCandidates(rawName: string): string[] {\n const trimmed = rawName.trim();\n if (!trimmed) return [];\n const seen = new Set<string>();\n const out: string[] = [];\n const add = (value: string): void => {\n const c = value.trim();\n if (!c || seen.has(c)) return;\n seen.add(c);\n out.push(c);\n };\n add(trimmed);\n add(normalizeDelimiters(trimmed));\n // Also try dot-normalized, then split\n const dotForm = trimmed.replace(/\\//g, '.');\n add(dotForm);\n add(normalizeDelimiters(dotForm));\n const segments = dotForm\n .split('.')\n .map((s) => s.trim())\n .filter(Boolean);\n if (segments.length > 1) {\n for (let i = 1; i < segments.length; i++) {\n const suffix = segments.slice(i).join('.');\n add(suffix);\n add(normalizeDelimiters(suffix));\n }\n }\n return out;\n}\n\nfunction resolveStructured(\n rawName: string,\n allowed: Set<string>\n): string | null {\n if (allowed.size === 0) return null;\n const candidates = buildStructuredCandidates(rawName);\n for (const c of candidates) {\n if (allowed.has(c)) return c;\n }\n for (const c of candidates) {\n const ci = resolveCaseInsensitive(c, allowed);\n if (ci) return ci;\n }\n return null;\n}\n\nfunction inferFromToolCallId(\n rawId: string | undefined,\n allowed: Set<string>\n): string | null {\n if (!rawId || allowed.size === 0) return null;\n const id = rawId.trim();\n if (!id) return null;\n\n const tokens = new Set<string>();\n const addTokens = (value: string): void => {\n const t = value.trim();\n if (!t) return;\n tokens.add(t);\n tokens.add(t.replace(/[:._/-]\\d+$/, ''));\n tokens.add(t.replace(/\\d+$/, ''));\n const dotForm = t.replace(/\\//g, '.');\n tokens.add(dotForm);\n tokens.add(dotForm.replace(/[:._-]\\d+$/, ''));\n tokens.add(dotForm.replace(/\\d+$/, ''));\n for (const prefix of [/^functions?[._-]?/i, /^tools?[._-]?/i]) {\n const stripped = dotForm.replace(prefix, '');\n if (stripped !== dotForm) {\n tokens.add(stripped);\n tokens.add(stripped.replace(/[:._-]\\d+$/, ''));\n tokens.add(stripped.replace(/\\d+$/, ''));\n }\n }\n };\n\n const preColon = id.split(':')[0] ?? id;\n addTokens(id);\n addTokens(preColon);\n\n let singleMatch: string | null = null;\n for (const token of tokens) {\n const matched = resolveStructured(token, allowed);\n if (!matched) continue;\n // Ambiguous → fail closed\n if (singleMatch && singleMatch !== matched) return null;\n singleMatch = matched;\n }\n if (singleMatch) return singleMatch;\n\n // Substring fallback: ids frequently look like `call_<toolname>_<n>` or\n // `toolu_01...<toolname>...`. Scan for any allowed name embedded in the\n // delimiter-normalized id (case-insensitive) and fail closed if more than\n // one distinct allowed name matches.\n const haystack = lowercaseOrEmpty(normalizeDelimiters(id));\n let substrMatch: string | null = null;\n for (const allowedName of allowed) {\n const needle = lowercaseOrEmpty(allowedName);\n if (!needle) continue;\n if (!haystack.includes(needle)) continue;\n if (substrMatch && substrMatch !== allowedName) return null;\n substrMatch = allowedName;\n }\n return substrMatch;\n}\n\n/**\n * Produce the best allowed tool name for a raw LLM tool_use name.\n * Returns the original name unchanged if no resolution is possible —\n * the downstream executor will then fail with its normal error path.\n */\nexport function normalizeToolCallName(\n rawName: string,\n allowedToolNames: Set<string>,\n rawToolCallId?: string\n): string {\n const trimmed = rawName.trim() ?? '';\n if (!trimmed) {\n return (\n inferFromToolCallId(rawToolCallId, allowedToolNames) ?? rawName ?? ''\n );\n }\n if (allowedToolNames.size === 0) return trimmed;\n\n const exact = resolveExact(trimmed, allowedToolNames);\n if (exact) return exact;\n\n const structured = resolveStructured(trimmed, allowedToolNames);\n if (structured) return structured;\n\n // Last-resort: try the raw id (if supplied). Substring matching lives\n // inside inferFromToolCallId — only safe to invoke when the caller\n // passed a real provider id, not when the raw name was used above.\n const inferred = inferFromToolCallId(rawToolCallId, allowedToolNames);\n if (inferred) return inferred;\n\n return trimmed;\n}\n\n/**\n * In-place normalization of all tool_calls on an AIMessage-like object.\n *\n * Applies `normalizeToolCallName` to each tool_call's `name` field using the\n * provided allowed-tool set (derived from the agent's toolMap keys). Also\n * rewrites the `name` field inside any content blocks of type `tool_use` so\n * that downstream providers see the corrected name.\n *\n * Returns `true` if any name was rewritten.\n */\nexport function normalizeMessageToolCalls(\n message: unknown,\n allowedToolNames: Set<string>\n): boolean {\n if (!message || typeof message !== 'object') return false;\n let changed = false;\n\n // LangChain AIMessage.tool_calls (normalized form)\n const msg = message as {\n tool_calls?: Array<{ name?: string; id?: string }>;\n content?: unknown;\n };\n if (Array.isArray(msg.tool_calls)) {\n for (const tc of msg.tool_calls) {\n if (!tc || typeof tc !== 'object') continue;\n const rawName = typeof tc.name === 'string' ? tc.name : '';\n const rawId = typeof tc.id === 'string' ? tc.id : undefined;\n const normalized = normalizeToolCallName(\n rawName,\n allowedToolNames,\n rawId\n );\n if (normalized && normalized !== rawName) {\n tc.name = normalized;\n changed = true;\n }\n }\n }\n\n // Anthropic-style content blocks with type: 'tool_use'\n if (Array.isArray(msg.content)) {\n for (const block of msg.content as Array<{\n type?: unknown;\n name?: unknown;\n id?: unknown;\n }>) {\n if (!block || typeof block !== 'object') continue;\n if (block.type !== 'tool_use') continue;\n const rawName = typeof block.name === 'string' ? block.name : '';\n const rawId = typeof block.id === 'string' ? block.id : undefined;\n const normalized = normalizeToolCallName(\n rawName,\n allowedToolNames,\n rawId\n );\n if (normalized && normalized !== rawName) {\n block.name = normalized;\n changed = true;\n }\n }\n }\n\n return changed;\n}\n"],"names":[],"mappings":"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"}
@@ -1,3 +1,4 @@
1
1
  export * from './enum';
2
2
  export * from './constants';
3
+ export * from './spawnPath';
3
4
  export * from '../tools/approval/constants';
@@ -0,0 +1,59 @@
1
+ /**
2
+ * spawnPath — hierarchical invocation identity for nested multi-agent orchestration.
3
+ *
4
+ * A spawnPath is a slash-separated chain of spawnKeys from the root of the current
5
+ * agent invocation to the current spawn. The root agent has an empty spawnPath;
6
+ * each handoff/transfer/sequence that spawns a new subgraph appends a new spawnKey.
7
+ *
8
+ * Examples:
9
+ * "" → primary agent (no spawn)
10
+ * "call_abc" → first-level handoff child
11
+ * "call_abc/call_def" → grandchild (depth 2)
12
+ * "call_abc/call_def/call_ghi" → depth 3
13
+ *
14
+ * These utilities are the single source of truth for path manipulation across:
15
+ * - @illuma-ai/agents (MultiAgentGraph, HandoffRegistry, callbacks)
16
+ * - host api (initialize.js, callbacks.js, ExecutionTrace writes)
17
+ * - host client (subagent store, sidebar rendering)
18
+ *
19
+ * See docs/multi-agent-nesting-architecture.md for the full design.
20
+ */
21
+ /** Separator between spawnKeys in a spawnPath. Chosen so that the path looks
22
+ * like a filesystem/URL path, which makes it easy to read in logs and traces. */
23
+ export declare const SPAWN_PATH_SEP = "/";
24
+ /** Hard cap on nested multi-agent invocations. Prevents runaway recursion.
25
+ * Can be overridden at the host api layer via MULTI_AGENT_MAX_NESTING_DEPTH. */
26
+ export declare const MAX_NESTING_DEPTH = 5;
27
+ /**
28
+ * Append a spawnKey to a parent spawnPath.
29
+ *
30
+ * @param parent - Parent spawnPath (may be undefined/null/empty for root)
31
+ * @param key - spawnKey to append
32
+ * @returns New spawnPath string
33
+ */
34
+ export declare function buildSpawnPath(parent: string | undefined | null, key: string): string;
35
+ /**
36
+ * Compute the depth of a spawnPath.
37
+ * Root (empty) → 0; single-segment → 1; etc.
38
+ */
39
+ export declare function spawnPathDepth(path: string | undefined | null): number;
40
+ /**
41
+ * Return the parent spawnPath, or null if the input is already root.
42
+ *
43
+ * - parentSpawnPath("a/b/c") === "a/b"
44
+ * - parentSpawnPath("a") === ""
45
+ * - parentSpawnPath("") === null
46
+ */
47
+ export declare function parentSpawnPath(path: string | undefined | null): string | null;
48
+ /** Split a spawnPath into its constituent spawnKey segments. */
49
+ export declare function spawnPathParts(path: string | undefined | null): string[];
50
+ /**
51
+ * Return the last spawnKey in a spawnPath (the "current" spawn).
52
+ * Returns null for root.
53
+ */
54
+ export declare function leafSpawnKey(path: string | undefined | null): string | null;
55
+ /**
56
+ * True if `ancestor` is a strict ancestor of `descendant`. Root ("") is
57
+ * ancestor of everything except itself.
58
+ */
59
+ export declare function isAncestorSpawnPath(ancestor: string | undefined | null, descendant: string | undefined | null): boolean;
@@ -0,0 +1,97 @@
1
+ import type { BaseMessage } from '@langchain/core/messages';
2
+ import type * as t from '@/types';
3
+ /**
4
+ * Tracks the lifecycle of a spawned handoff child agent.
5
+ */
6
+ export type HandoffRecord = {
7
+ /** Agent identity (destination agentId) — stable across multiple spawns of the same agent */
8
+ id: string;
9
+ /** Unique internal key for this specific spawn (agentId + monotonic counter) */
10
+ spawnKey: string;
11
+ /** Display name of the child agent */
12
+ name: string;
13
+ /** Task description / instructions passed to child */
14
+ task: string;
15
+ /** When the handoff was spawned */
16
+ spawnedAt: number;
17
+ /** Current status */
18
+ status: 'pending' | 'running' | 'completed' | 'failed';
19
+ /** The background promise executing the child subgraph */
20
+ promise: Promise<t.BaseGraphState>;
21
+ /** Resolved result text (populated on completion) */
22
+ resultText?: string;
23
+ /** Error message (populated on failure) */
24
+ error?: string;
25
+ /** Duration in ms (populated on completion/failure) */
26
+ durationMs?: number;
27
+ /** Number of messages in child's output */
28
+ resultMessageCount?: number;
29
+ };
30
+ /**
31
+ * Registry for async handoff execution.
32
+ *
33
+ * Enables the autonomous orchestration pattern:
34
+ * 1. Orchestrator spawns children (non-blocking)
35
+ * 2. Orchestrator stays alive to reason, spawn more, or check status
36
+ * 3. Orchestrator collects results when ready
37
+ *
38
+ * Scoped per MultiAgentGraph instance — each orchestrator graph gets its own registry.
39
+ */
40
+ export declare class HandoffRegistry {
41
+ private records;
42
+ /** Monotonically increasing counter for unique spawn IDs */
43
+ private spawnCounter;
44
+ /**
45
+ * Register a spawned handoff child.
46
+ * The promise runs in the background — not awaited here.
47
+ * Uses a unique key per spawn so the same agent can be spawned multiple times
48
+ * across rounds without overwriting prior records.
49
+ */
50
+ spawn(params: {
51
+ id: string;
52
+ name: string;
53
+ task: string;
54
+ promise: Promise<t.BaseGraphState>;
55
+ extractResult: (messages: BaseMessage[], agentId: string) => string;
56
+ truncateResult: (text: string, maxChars: number) => string;
57
+ maxResultChars: number;
58
+ /** Callback when child completes (for SSE events) */
59
+ onComplete?: (record: HandoffRecord) => void;
60
+ }): void;
61
+ /** List all pending (running) handoffs */
62
+ listPending(): HandoffRecord[];
63
+ /** List all completed handoffs (not yet collected) */
64
+ listCompleted(): HandoffRecord[];
65
+ /** List all handoffs regardless of status */
66
+ listAll(): HandoffRecord[];
67
+ /**
68
+ * Get a handoff record by either its unique spawnKey or by agentId.
69
+ * - Exact spawnKey match wins (O(1)).
70
+ * - Falls back to the most recently spawned record for that agentId — matching
71
+ * lookups by callers that only know the agent identity, not the spawn round.
72
+ */
73
+ get(idOrSpawnKey: string): HandoffRecord | undefined;
74
+ /** Check if any handoffs are still running */
75
+ hasPending(): boolean;
76
+ /**
77
+ * Wait for ALL pending handoffs to complete and return all records.
78
+ * Records are NOT auto-cleared — caller removes collected records via remove().
79
+ */
80
+ waitForAll(): Promise<HandoffRecord[]>;
81
+ /**
82
+ * Wait for ANY pending handoff to complete.
83
+ * Returns the newly completed record(s).
84
+ */
85
+ waitForAny(): Promise<HandoffRecord[]>;
86
+ /**
87
+ * Remove record(s) by spawnKey or agentId.
88
+ * - Exact spawnKey match removes only that record.
89
+ * - agentId match removes ALL records for that agent (covers callers that
90
+ * want to forget everything tied to a given agent).
91
+ */
92
+ remove(idOrSpawnKey: string): void;
93
+ /** Clear all records (for cleanup between graph invocations) */
94
+ clear(): void;
95
+ /** Number of total tracked handoffs */
96
+ get size(): number;
97
+ }