illuma-agents 1.0.7 → 1.0.9

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 (237) hide show
  1. package/LICENSE +1 -5
  2. package/dist/cjs/common/enum.cjs +1 -2
  3. package/dist/cjs/common/enum.cjs.map +1 -1
  4. package/dist/cjs/instrumentation.cjs.map +1 -1
  5. package/dist/cjs/llm/anthropic/types.cjs.map +1 -1
  6. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +79 -2
  7. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
  8. package/dist/cjs/llm/anthropic/utils/tools.cjs.map +1 -1
  9. package/dist/cjs/llm/bedrock/index.cjs +99 -0
  10. package/dist/cjs/llm/bedrock/index.cjs.map +1 -0
  11. package/dist/cjs/llm/fake.cjs.map +1 -1
  12. package/dist/cjs/llm/google/index.cjs +78 -9
  13. package/dist/cjs/llm/google/index.cjs.map +1 -1
  14. package/dist/cjs/llm/google/utils/common.cjs +185 -28
  15. package/dist/cjs/llm/google/utils/common.cjs.map +1 -1
  16. package/dist/cjs/llm/providers.cjs +13 -16
  17. package/dist/cjs/llm/providers.cjs.map +1 -1
  18. package/dist/cjs/llm/text.cjs.map +1 -1
  19. package/dist/cjs/messages/core.cjs +14 -14
  20. package/dist/cjs/messages/core.cjs.map +1 -1
  21. package/dist/cjs/messages/ids.cjs.map +1 -1
  22. package/dist/cjs/messages/prune.cjs.map +1 -1
  23. package/dist/cjs/run.cjs +10 -1
  24. package/dist/cjs/run.cjs.map +1 -1
  25. package/dist/cjs/splitStream.cjs.map +1 -1
  26. package/dist/cjs/stream.cjs +4 -1
  27. package/dist/cjs/stream.cjs.map +1 -1
  28. package/dist/cjs/tools/ToolNode.cjs +163 -55
  29. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  30. package/dist/cjs/tools/handlers.cjs +29 -25
  31. package/dist/cjs/tools/handlers.cjs.map +1 -1
  32. package/dist/cjs/tools/search/anthropic.cjs.map +1 -1
  33. package/dist/cjs/tools/search/content.cjs.map +1 -1
  34. package/dist/cjs/tools/search/firecrawl.cjs.map +1 -1
  35. package/dist/cjs/tools/search/format.cjs.map +1 -1
  36. package/dist/cjs/tools/search/highlights.cjs.map +1 -1
  37. package/dist/cjs/tools/search/rerankers.cjs.map +1 -1
  38. package/dist/cjs/tools/search/schema.cjs +25 -25
  39. package/dist/cjs/tools/search/schema.cjs.map +1 -1
  40. package/dist/cjs/tools/search/search.cjs +6 -1
  41. package/dist/cjs/tools/search/search.cjs.map +1 -1
  42. package/dist/cjs/tools/search/serper-scraper.cjs.map +1 -1
  43. package/dist/cjs/tools/search/tool.cjs +162 -35
  44. package/dist/cjs/tools/search/tool.cjs.map +1 -1
  45. package/dist/cjs/tools/search/utils.cjs.map +1 -1
  46. package/dist/cjs/utils/graph.cjs.map +1 -1
  47. package/dist/cjs/utils/llm.cjs +0 -1
  48. package/dist/cjs/utils/llm.cjs.map +1 -1
  49. package/dist/cjs/utils/misc.cjs.map +1 -1
  50. package/dist/cjs/utils/run.cjs.map +1 -1
  51. package/dist/cjs/utils/title.cjs +7 -7
  52. package/dist/cjs/utils/title.cjs.map +1 -1
  53. package/dist/esm/common/enum.mjs +1 -2
  54. package/dist/esm/common/enum.mjs.map +1 -1
  55. package/dist/esm/instrumentation.mjs.map +1 -1
  56. package/dist/esm/llm/anthropic/types.mjs.map +1 -1
  57. package/dist/esm/llm/anthropic/utils/message_inputs.mjs +79 -2
  58. package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
  59. package/dist/esm/llm/anthropic/utils/tools.mjs.map +1 -1
  60. package/dist/esm/llm/bedrock/index.mjs +97 -0
  61. package/dist/esm/llm/bedrock/index.mjs.map +1 -0
  62. package/dist/esm/llm/fake.mjs.map +1 -1
  63. package/dist/esm/llm/google/index.mjs +79 -10
  64. package/dist/esm/llm/google/index.mjs.map +1 -1
  65. package/dist/esm/llm/google/utils/common.mjs +184 -30
  66. package/dist/esm/llm/google/utils/common.mjs.map +1 -1
  67. package/dist/esm/llm/providers.mjs +2 -5
  68. package/dist/esm/llm/providers.mjs.map +1 -1
  69. package/dist/esm/llm/text.mjs.map +1 -1
  70. package/dist/esm/messages/core.mjs +14 -14
  71. package/dist/esm/messages/core.mjs.map +1 -1
  72. package/dist/esm/messages/ids.mjs.map +1 -1
  73. package/dist/esm/messages/prune.mjs.map +1 -1
  74. package/dist/esm/run.mjs +10 -1
  75. package/dist/esm/run.mjs.map +1 -1
  76. package/dist/esm/splitStream.mjs.map +1 -1
  77. package/dist/esm/stream.mjs +4 -1
  78. package/dist/esm/stream.mjs.map +1 -1
  79. package/dist/esm/tools/ToolNode.mjs +164 -56
  80. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  81. package/dist/esm/tools/handlers.mjs +30 -26
  82. package/dist/esm/tools/handlers.mjs.map +1 -1
  83. package/dist/esm/tools/search/anthropic.mjs.map +1 -1
  84. package/dist/esm/tools/search/content.mjs.map +1 -1
  85. package/dist/esm/tools/search/firecrawl.mjs.map +1 -1
  86. package/dist/esm/tools/search/format.mjs.map +1 -1
  87. package/dist/esm/tools/search/highlights.mjs.map +1 -1
  88. package/dist/esm/tools/search/rerankers.mjs.map +1 -1
  89. package/dist/esm/tools/search/schema.mjs +25 -25
  90. package/dist/esm/tools/search/schema.mjs.map +1 -1
  91. package/dist/esm/tools/search/search.mjs +6 -1
  92. package/dist/esm/tools/search/search.mjs.map +1 -1
  93. package/dist/esm/tools/search/serper-scraper.mjs.map +1 -1
  94. package/dist/esm/tools/search/tool.mjs +162 -35
  95. package/dist/esm/tools/search/tool.mjs.map +1 -1
  96. package/dist/esm/tools/search/utils.mjs.map +1 -1
  97. package/dist/esm/utils/graph.mjs.map +1 -1
  98. package/dist/esm/utils/llm.mjs +0 -1
  99. package/dist/esm/utils/llm.mjs.map +1 -1
  100. package/dist/esm/utils/misc.mjs.map +1 -1
  101. package/dist/esm/utils/run.mjs.map +1 -1
  102. package/dist/esm/utils/title.mjs +7 -7
  103. package/dist/esm/utils/title.mjs.map +1 -1
  104. package/dist/types/common/enum.d.ts +1 -2
  105. package/dist/types/llm/bedrock/index.d.ts +36 -0
  106. package/dist/types/llm/google/index.d.ts +10 -0
  107. package/dist/types/llm/google/types.d.ts +11 -1
  108. package/dist/types/llm/google/utils/common.d.ts +17 -2
  109. package/dist/types/tools/ToolNode.d.ts +9 -1
  110. package/dist/types/tools/search/types.d.ts +2 -0
  111. package/dist/types/types/llm.d.ts +3 -8
  112. package/dist/types/types/tools.d.ts +1 -1
  113. package/package.json +15 -11
  114. package/src/common/enum.ts +1 -2
  115. package/src/common/index.ts +1 -1
  116. package/src/instrumentation.ts +22 -22
  117. package/src/llm/anthropic/llm.spec.ts +1442 -1442
  118. package/src/llm/anthropic/types.ts +140 -140
  119. package/src/llm/anthropic/utils/message_inputs.ts +757 -660
  120. package/src/llm/anthropic/utils/output_parsers.ts +133 -133
  121. package/src/llm/anthropic/utils/tools.ts +29 -29
  122. package/src/llm/bedrock/index.ts +128 -0
  123. package/src/llm/fake.ts +133 -133
  124. package/src/llm/google/data/gettysburg10.wav +0 -0
  125. package/src/llm/google/data/hotdog.jpg +0 -0
  126. package/src/llm/google/index.ts +129 -14
  127. package/src/llm/google/llm.spec.ts +932 -0
  128. package/src/llm/google/types.ts +56 -43
  129. package/src/llm/google/utils/common.ts +873 -660
  130. package/src/llm/google/utils/tools.ts +160 -160
  131. package/src/llm/openai/types.ts +24 -24
  132. package/src/llm/openai/utils/isReasoningModel.test.ts +90 -90
  133. package/src/llm/providers.ts +2 -7
  134. package/src/llm/text.ts +94 -94
  135. package/src/messages/core.ts +463 -463
  136. package/src/messages/formatAgentMessages.tools.test.ts +400 -400
  137. package/src/messages/formatMessage.test.ts +693 -693
  138. package/src/messages/ids.ts +26 -26
  139. package/src/messages/prune.ts +567 -567
  140. package/src/messages/shiftIndexTokenCountMap.test.ts +81 -81
  141. package/src/mockStream.ts +98 -98
  142. package/src/prompts/collab.ts +5 -5
  143. package/src/prompts/index.ts +1 -1
  144. package/src/prompts/taskmanager.ts +61 -61
  145. package/src/run.ts +13 -4
  146. package/src/scripts/ant_web_search_edge_case.ts +162 -0
  147. package/src/scripts/ant_web_search_error_edge_case.ts +148 -0
  148. package/src/scripts/args.ts +48 -48
  149. package/src/scripts/caching.ts +123 -123
  150. package/src/scripts/code_exec_files.ts +193 -193
  151. package/src/scripts/empty_input.ts +137 -137
  152. package/src/scripts/image.ts +178 -178
  153. package/src/scripts/memory.ts +97 -97
  154. package/src/scripts/thinking.ts +149 -149
  155. package/src/specs/anthropic.simple.test.ts +67 -0
  156. package/src/specs/spec.utils.ts +3 -3
  157. package/src/specs/token-distribution-edge-case.test.ts +316 -316
  158. package/src/specs/tool-error.test.ts +193 -193
  159. package/src/splitStream.test.ts +691 -691
  160. package/src/splitStream.ts +234 -234
  161. package/src/stream.test.ts +94 -94
  162. package/src/stream.ts +4 -1
  163. package/src/tools/ToolNode.ts +206 -64
  164. package/src/tools/handlers.ts +32 -28
  165. package/src/tools/search/anthropic.ts +51 -51
  166. package/src/tools/search/content.test.ts +173 -173
  167. package/src/tools/search/content.ts +147 -147
  168. package/src/tools/search/direct-url.test.ts +530 -0
  169. package/src/tools/search/firecrawl.ts +210 -210
  170. package/src/tools/search/format.ts +250 -250
  171. package/src/tools/search/highlights.ts +320 -320
  172. package/src/tools/search/index.ts +2 -2
  173. package/src/tools/search/jina-reranker.test.ts +126 -126
  174. package/src/tools/search/output.md +2775 -2775
  175. package/src/tools/search/rerankers.ts +242 -242
  176. package/src/tools/search/schema.ts +63 -63
  177. package/src/tools/search/search.ts +766 -759
  178. package/src/tools/search/serper-scraper.ts +155 -155
  179. package/src/tools/search/test.html +883 -883
  180. package/src/tools/search/test.md +642 -642
  181. package/src/tools/search/test.ts +159 -159
  182. package/src/tools/search/tool.ts +619 -471
  183. package/src/tools/search/types.ts +689 -687
  184. package/src/tools/search/utils.ts +79 -79
  185. package/src/types/index.ts +6 -6
  186. package/src/types/llm.ts +2 -8
  187. package/src/types/tools.ts +80 -80
  188. package/src/utils/graph.ts +10 -10
  189. package/src/utils/llm.ts +26 -27
  190. package/src/utils/llmConfig.ts +5 -3
  191. package/src/utils/logging.ts +48 -48
  192. package/src/utils/misc.ts +57 -57
  193. package/src/utils/run.ts +100 -100
  194. package/src/utils/title.ts +165 -165
  195. package/dist/cjs/llm/ollama/index.cjs +0 -70
  196. package/dist/cjs/llm/ollama/index.cjs.map +0 -1
  197. package/dist/cjs/llm/ollama/utils.cjs +0 -158
  198. package/dist/cjs/llm/ollama/utils.cjs.map +0 -1
  199. package/dist/esm/llm/ollama/index.mjs +0 -68
  200. package/dist/esm/llm/ollama/index.mjs.map +0 -1
  201. package/dist/esm/llm/ollama/utils.mjs +0 -155
  202. package/dist/esm/llm/ollama/utils.mjs.map +0 -1
  203. package/dist/types/llm/ollama/index.d.ts +0 -8
  204. package/dist/types/llm/ollama/utils.d.ts +0 -7
  205. package/src/llm/ollama/index.ts +0 -92
  206. package/src/llm/ollama/utils.ts +0 -193
  207. package/src/proto/CollabGraph.ts +0 -269
  208. package/src/proto/TaskManager.ts +0 -243
  209. package/src/proto/collab.ts +0 -200
  210. package/src/proto/collab_design.ts +0 -184
  211. package/src/proto/collab_design_v2.ts +0 -224
  212. package/src/proto/collab_design_v3.ts +0 -255
  213. package/src/proto/collab_design_v4.ts +0 -220
  214. package/src/proto/collab_design_v5.ts +0 -251
  215. package/src/proto/collab_graph.ts +0 -181
  216. package/src/proto/collab_original.ts +0 -123
  217. package/src/proto/example.ts +0 -93
  218. package/src/proto/example_new.ts +0 -68
  219. package/src/proto/example_old.ts +0 -201
  220. package/src/proto/example_test.ts +0 -152
  221. package/src/proto/example_test_anthropic.ts +0 -100
  222. package/src/proto/log_stream.ts +0 -202
  223. package/src/proto/main_collab_community_event.ts +0 -133
  224. package/src/proto/main_collab_design_v2.ts +0 -96
  225. package/src/proto/main_collab_design_v4.ts +0 -100
  226. package/src/proto/main_collab_design_v5.ts +0 -135
  227. package/src/proto/main_collab_global_analysis.ts +0 -122
  228. package/src/proto/main_collab_hackathon_event.ts +0 -153
  229. package/src/proto/main_collab_space_mission.ts +0 -153
  230. package/src/proto/main_philosophy.ts +0 -210
  231. package/src/proto/original_script.ts +0 -126
  232. package/src/proto/standard.ts +0 -100
  233. package/src/proto/stream.ts +0 -56
  234. package/src/proto/tasks.ts +0 -118
  235. package/src/proto/tools/global_analysis_tools.ts +0 -86
  236. package/src/proto/tools/space_mission_tools.ts +0 -60
  237. package/src/proto/vertexai.ts +0 -54
@@ -1,10 +1,17 @@
1
+ import { ToolCall } from '@langchain/core/messages/tool';
2
+ import {
3
+ ToolMessage,
4
+ isAIMessage,
5
+ isBaseMessage,
6
+ } from '@langchain/core/messages';
1
7
  import {
2
8
  END,
3
- MessagesAnnotation,
9
+ Send,
10
+ Command,
4
11
  isCommand,
5
12
  isGraphInterrupt,
13
+ MessagesAnnotation,
6
14
  } from '@langchain/langgraph';
7
- import { ToolMessage, isBaseMessage } from '@langchain/core/messages';
8
15
  import type {
9
16
  RunnableConfig,
10
17
  RunnableToolLike,
@@ -14,12 +21,20 @@ import type { StructuredToolInterface } from '@langchain/core/tools';
14
21
  import type * as t from '@/types';
15
22
  import { RunnableCallable } from '@/utils';
16
23
 
24
+ /**
25
+ * Helper to check if a value is a Send object
26
+ */
27
+ function isSend(value: unknown): value is Send {
28
+ return value instanceof Send;
29
+ }
30
+
17
31
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
18
32
  export class ToolNode<T = any> extends RunnableCallable<T, T> {
19
33
  tools: t.GenericTool[];
20
34
  private toolMap: Map<string, StructuredToolInterface | RunnableToolLike>;
21
35
  private loadRuntimeTools?: t.ToolRefGenerator;
22
36
  handleToolErrors = true;
37
+ trace = false;
23
38
  toolCallStepIds?: Map<string, string>;
24
39
  errorHandler?: t.ToolNodeConstructorParams['errorHandler'];
25
40
  private toolUsageCount: Map<string, number>;
@@ -52,60 +67,50 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
52
67
  return new Map(this.toolUsageCount); // Return a copy
53
68
  }
54
69
 
55
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
56
- protected async run(input: any, config: RunnableConfig): Promise<T> {
57
- const message = Array.isArray(input)
58
- ? input[input.length - 1]
59
- : input.messages[input.messages.length - 1];
60
-
61
- if (message._getType() !== 'ai') {
62
- throw new Error('ToolNode only accepts AIMessages as input.');
63
- }
64
-
65
- if (this.loadRuntimeTools) {
66
- const { tools, toolMap } = this.loadRuntimeTools(
67
- (message as AIMessage).tool_calls ?? []
70
+ /**
71
+ * Runs a single tool call with error handling
72
+ */
73
+ protected async runTool(
74
+ call: ToolCall,
75
+ config: RunnableConfig
76
+ ): Promise<BaseMessage | Command> {
77
+ const tool = this.toolMap.get(call.name);
78
+ try {
79
+ if (tool === undefined) {
80
+ throw new Error(`Tool "${call.name}" not found.`);
81
+ }
82
+ const turn = this.toolUsageCount.get(call.name) ?? 0;
83
+ this.toolUsageCount.set(call.name, turn + 1);
84
+ const args = call.args;
85
+ const stepId = this.toolCallStepIds?.get(call.id!);
86
+ const output = await tool.invoke(
87
+ { ...call, args, type: 'tool_call', stepId, turn },
88
+ config
68
89
  );
69
- this.tools = tools;
70
- this.toolMap = toolMap ?? new Map(tools.map((tool) => [tool.name, tool]));
71
- }
72
- const outputs = await Promise.all(
73
- (message as AIMessage).tool_calls?.map(async (call) => {
74
- const tool = this.toolMap.get(call.name);
90
+ if (
91
+ (isBaseMessage(output) && output._getType() === 'tool') ||
92
+ isCommand(output)
93
+ ) {
94
+ return output;
95
+ } else {
96
+ return new ToolMessage({
97
+ status: 'success',
98
+ name: tool.name,
99
+ content: typeof output === 'string' ? output : JSON.stringify(output),
100
+ tool_call_id: call.id!,
101
+ });
102
+ }
103
+ } catch (_e: unknown) {
104
+ const e = _e as Error;
105
+ if (!this.handleToolErrors) {
106
+ throw e;
107
+ }
108
+ if (isGraphInterrupt(e)) {
109
+ throw e;
110
+ }
111
+ if (this.errorHandler) {
75
112
  try {
76
- if (tool === undefined) {
77
- throw new Error(`Tool "${call.name}" not found.`);
78
- }
79
- const turn = this.toolUsageCount.get(call.name) ?? 0;
80
- this.toolUsageCount.set(call.name, turn + 1);
81
- const args = call.args;
82
- const stepId = this.toolCallStepIds?.get(call.id!);
83
- const output = await tool.invoke(
84
- { ...call, args, type: 'tool_call', stepId, turn },
85
- config
86
- );
87
- if (
88
- (isBaseMessage(output) && output._getType() === 'tool') ||
89
- isCommand(output)
90
- ) {
91
- return output;
92
- } else {
93
- return new ToolMessage({
94
- name: tool.name,
95
- content:
96
- typeof output === 'string' ? output : JSON.stringify(output),
97
- tool_call_id: call.id!,
98
- });
99
- }
100
- } catch (_e: unknown) {
101
- const e = _e as Error;
102
- if (!this.handleToolErrors) {
103
- throw e;
104
- }
105
- if (isGraphInterrupt(e)) {
106
- throw e;
107
- }
108
- this.errorHandler?.(
113
+ await this.errorHandler(
109
114
  {
110
115
  error: e,
111
116
  id: call.id!,
@@ -114,27 +119,164 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
114
119
  },
115
120
  config.metadata
116
121
  );
117
- return new ToolMessage({
118
- content: `Error: ${e.message}\n Please fix your mistakes.`,
119
- name: call.name,
120
- tool_call_id: call.id ?? '',
122
+ } catch (handlerError) {
123
+ // eslint-disable-next-line no-console
124
+ console.error('Error in errorHandler:', {
125
+ toolName: call.name,
126
+ toolCallId: call.id,
127
+ toolArgs: call.args,
128
+ stepId: this.toolCallStepIds?.get(call.id!),
129
+ turn: this.toolUsageCount.get(call.name),
130
+ originalError: {
131
+ message: e.message,
132
+ stack: e.stack ?? undefined,
133
+ },
134
+ handlerError:
135
+ handlerError instanceof Error
136
+ ? {
137
+ message: handlerError.message,
138
+ stack: handlerError.stack ?? undefined,
139
+ }
140
+ : {
141
+ message: String(handlerError),
142
+ stack: undefined,
143
+ },
121
144
  });
122
145
  }
123
- }) ?? []
124
- );
146
+ }
147
+ return new ToolMessage({
148
+ status: 'error',
149
+ content: `Error: ${e.message}\n Please fix your mistakes.`,
150
+ name: call.name,
151
+ tool_call_id: call.id ?? '',
152
+ });
153
+ }
154
+ }
155
+
156
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
157
+ protected async run(input: any, config: RunnableConfig): Promise<T> {
158
+ let outputs: (BaseMessage | Command)[];
159
+
160
+ if (this.isSendInput(input)) {
161
+ outputs = [await this.runTool(input.lg_tool_call, config)];
162
+ } else {
163
+ let messages: BaseMessage[];
164
+ if (Array.isArray(input)) {
165
+ messages = input;
166
+ } else if (this.isMessagesState(input)) {
167
+ messages = input.messages;
168
+ } else {
169
+ throw new Error(
170
+ 'ToolNode only accepts BaseMessage[] or { messages: BaseMessage[] } as input.'
171
+ );
172
+ }
173
+
174
+ const toolMessageIds: Set<string> = new Set(
175
+ messages
176
+ .filter((msg) => msg._getType() === 'tool')
177
+ .map((msg) => (msg as ToolMessage).tool_call_id)
178
+ );
179
+
180
+ let aiMessage: AIMessage | undefined;
181
+ for (let i = messages.length - 1; i >= 0; i--) {
182
+ const message = messages[i];
183
+ if (isAIMessage(message)) {
184
+ aiMessage = message;
185
+ break;
186
+ }
187
+ }
188
+
189
+ if (aiMessage == null || !isAIMessage(aiMessage)) {
190
+ throw new Error('ToolNode only accepts AIMessages as input.');
191
+ }
192
+
193
+ if (this.loadRuntimeTools) {
194
+ const { tools, toolMap } = this.loadRuntimeTools(
195
+ aiMessage.tool_calls ?? []
196
+ );
197
+ this.tools = tools;
198
+ this.toolMap =
199
+ toolMap ?? new Map(tools.map((tool) => [tool.name, tool]));
200
+ }
201
+
202
+ outputs = await Promise.all(
203
+ aiMessage.tool_calls
204
+ ?.filter((call) => {
205
+ /**
206
+ * Filter out:
207
+ * 1. Already processed tool calls (present in toolMessageIds)
208
+ * 2. Server tool calls (e.g., web_search with IDs starting with 'srvtoolu_')
209
+ * which are executed by the provider's API and don't require invocation
210
+ */
211
+ return (
212
+ (call.id == null || !toolMessageIds.has(call.id)) &&
213
+ !(call.id?.startsWith('srvtoolu_') ?? false)
214
+ );
215
+ })
216
+ .map((call) => this.runTool(call, config)) ?? []
217
+ );
218
+ }
125
219
 
126
220
  if (!outputs.some(isCommand)) {
127
221
  return (Array.isArray(input) ? outputs : { messages: outputs }) as T;
128
222
  }
129
223
 
130
- const combinedOutputs = outputs.map((output) => {
224
+ const combinedOutputs: (
225
+ | { messages: BaseMessage[] }
226
+ | BaseMessage[]
227
+ | Command
228
+ )[] = [];
229
+ let parentCommand: Command | null = null;
230
+
231
+ for (const output of outputs) {
131
232
  if (isCommand(output)) {
132
- return output;
233
+ if (
234
+ output.graph === Command.PARENT &&
235
+ Array.isArray(output.goto) &&
236
+ output.goto.every((send): send is Send => isSend(send))
237
+ ) {
238
+ if (parentCommand) {
239
+ (parentCommand.goto as Send[]).push(...(output.goto as Send[]));
240
+ } else {
241
+ parentCommand = new Command({
242
+ graph: Command.PARENT,
243
+ goto: output.goto,
244
+ });
245
+ }
246
+ } else {
247
+ combinedOutputs.push(output);
248
+ }
249
+ } else {
250
+ combinedOutputs.push(
251
+ Array.isArray(input) ? [output] : { messages: [output] }
252
+ );
133
253
  }
134
- return Array.isArray(input) ? [output] : { messages: [output] };
135
- });
254
+ }
255
+
256
+ if (parentCommand) {
257
+ combinedOutputs.push(parentCommand);
258
+ }
259
+
136
260
  return combinedOutputs as T;
137
261
  }
262
+
263
+ private isSendInput(input: unknown): input is { lg_tool_call: ToolCall } {
264
+ return (
265
+ typeof input === 'object' && input != null && 'lg_tool_call' in input
266
+ );
267
+ }
268
+
269
+ private isMessagesState(
270
+ input: unknown
271
+ ): input is { messages: BaseMessage[] } {
272
+ return (
273
+ typeof input === 'object' &&
274
+ input != null &&
275
+ 'messages' in input &&
276
+ Array.isArray((input as { messages: unknown }).messages) &&
277
+ (input as { messages: unknown[] }).messages.every(isBaseMessage)
278
+ );
279
+ }
138
280
  }
139
281
 
140
282
  function areToolCallsInvoked(
@@ -9,7 +9,6 @@ import type { AgentContext } from '@/agents/AgentContext';
9
9
  import type * as t from '@/types';
10
10
  import {
11
11
  ToolCallTypes,
12
- ContentTypes,
13
12
  GraphEvents,
14
13
  StepTypes,
15
14
  Providers,
@@ -87,22 +86,33 @@ export async function handleToolCallChunks({
87
86
  const alreadyDispatched =
88
87
  prevRunStep?.type === StepTypes.MESSAGE_CREATION &&
89
88
  graph.messageStepHasToolCalls.has(prevStepId);
90
- if (!alreadyDispatched && tool_calls?.length === toolCallChunks.length) {
91
- await graph.dispatchMessageDelta(prevStepId, {
92
- content: [
93
- {
94
- type: ContentTypes.TEXT,
95
- text: '',
96
- tool_call_ids: tool_calls.map((tc) => tc.id ?? ''),
97
- },
98
- ],
99
- });
89
+
90
+ if (prevRunStep?.type === StepTypes.TOOL_CALLS) {
91
+ /**
92
+ * If previous step is already a tool_calls step, use that step ID
93
+ * This ensures tool call deltas are dispatched to the correct step
94
+ */
95
+ stepId = prevStepId;
96
+ } else if (
97
+ !alreadyDispatched &&
98
+ prevRunStep?.type === StepTypes.MESSAGE_CREATION
99
+ ) {
100
+ /**
101
+ * Create tool_calls step as soon as we receive the first tool call chunk
102
+ * This ensures deltas are always associated with the correct step
103
+ *
104
+ * NOTE: We do NOT dispatch an empty text block here because:
105
+ * - Empty text blocks cause providers (Anthropic, Bedrock) to reject messages
106
+ * - The tool_calls themselves are sufficient for the step
107
+ * - Empty content with tool_call_ids gets stored in conversation history
108
+ * and causes "messages must have non-empty content" errors on replay
109
+ */
100
110
  graph.messageStepHasToolCalls.set(prevStepId, true);
101
111
  stepId = await graph.dispatchRunStep(
102
112
  stepKey,
103
113
  {
104
114
  type: StepTypes.TOOL_CALLS,
105
- tool_calls,
115
+ tool_calls: tool_calls ?? [],
106
116
  },
107
117
  metadata
108
118
  );
@@ -149,26 +159,21 @@ export const handleToolCalls = async (
149
159
  // no previous step
150
160
  }
151
161
 
152
- const dispatchToolCallIds = async (
153
- lastMessageStepId: string
154
- ): Promise<void> => {
155
- await graph.dispatchMessageDelta(lastMessageStepId, {
156
- content: [
157
- {
158
- type: 'text',
159
- text: '',
160
- tool_call_ids: [toolCallId],
161
- },
162
- ],
163
- });
164
- };
162
+ /**
163
+ * NOTE: We do NOT dispatch empty text blocks with tool_call_ids because:
164
+ * - Empty text blocks cause providers (Anthropic, Bedrock) to reject messages
165
+ * - They get stored in conversation history and cause errors on replay:
166
+ * "messages must have non-empty content" (Anthropic)
167
+ * "The content field in the Message object is empty" (Bedrock)
168
+ * - The tool_calls themselves are sufficient
169
+ */
170
+
165
171
  /* If the previous step exists and is a message creation */
166
172
  if (
167
173
  prevStepId &&
168
174
  prevRunStep &&
169
175
  prevRunStep.type === StepTypes.MESSAGE_CREATION
170
176
  ) {
171
- await dispatchToolCallIds(prevStepId);
172
177
  graph.messageStepHasToolCalls.set(prevStepId, true);
173
178
  /* If the previous step doesn't exist or is not a message creation */
174
179
  } else if (
@@ -186,8 +191,7 @@ export const handleToolCalls = async (
186
191
  },
187
192
  metadata
188
193
  );
189
- await dispatchToolCallIds(stepId);
190
- graph.messageStepHasToolCalls.set(prevStepId, true);
194
+ graph.messageStepHasToolCalls.set(stepId, true);
191
195
  }
192
196
 
193
197
  await graph.dispatchRunStep(
@@ -1,51 +1,51 @@
1
- import type {
2
- AnthropicTextBlockParam,
3
- AnthropicWebSearchResultBlockParam,
4
- } from '@/llm/anthropic/types';
5
- import type { SearchResultData, ProcessedOrganic } from './types';
6
- import { getAttribution } from './utils';
7
-
8
- /**
9
- * Coerces Anthropic web search results to the SearchResultData format
10
- * @param results - Array of Anthropic web search results
11
- * @param turn - The turn number to associate with these results
12
- * @returns SearchResultData with minimal ProcessedOrganic items
13
- */
14
- export function coerceAnthropicSearchResults({
15
- results,
16
- turn = 0,
17
- }: {
18
- results: (AnthropicTextBlockParam | AnthropicWebSearchResultBlockParam)[];
19
- turn?: number;
20
- }): SearchResultData {
21
- const organic: ProcessedOrganic[] = results
22
- .filter((result) => result.type === 'web_search_result')
23
- .map((result, index) => ({
24
- link: result.url,
25
- position: index + 1,
26
- title: result.title,
27
- date: result.page_age ?? undefined,
28
- attribution: getAttribution(result.url),
29
- }));
30
-
31
- return {
32
- turn,
33
- organic,
34
- };
35
- }
36
-
37
- /**
38
- * Helper function to check if an object is an Anthropic web search result
39
- */
40
- export function isAnthropicWebSearchResult(
41
- obj: unknown
42
- ): obj is AnthropicWebSearchResultBlockParam {
43
- return (
44
- typeof obj === 'object' &&
45
- obj !== null &&
46
- 'type' in obj &&
47
- obj.type === 'web_search_result' &&
48
- 'url' in obj &&
49
- typeof (obj as Record<string, unknown>).url === 'string'
50
- );
51
- }
1
+ import type {
2
+ AnthropicTextBlockParam,
3
+ AnthropicWebSearchResultBlockParam,
4
+ } from '@/llm/anthropic/types';
5
+ import type { SearchResultData, ProcessedOrganic } from './types';
6
+ import { getAttribution } from './utils';
7
+
8
+ /**
9
+ * Coerces Anthropic web search results to the SearchResultData format
10
+ * @param results - Array of Anthropic web search results
11
+ * @param turn - The turn number to associate with these results
12
+ * @returns SearchResultData with minimal ProcessedOrganic items
13
+ */
14
+ export function coerceAnthropicSearchResults({
15
+ results,
16
+ turn = 0,
17
+ }: {
18
+ results: (AnthropicTextBlockParam | AnthropicWebSearchResultBlockParam)[];
19
+ turn?: number;
20
+ }): SearchResultData {
21
+ const organic: ProcessedOrganic[] = results
22
+ .filter((result) => result.type === 'web_search_result')
23
+ .map((result, index) => ({
24
+ link: result.url,
25
+ position: index + 1,
26
+ title: result.title,
27
+ date: result.page_age ?? undefined,
28
+ attribution: getAttribution(result.url),
29
+ }));
30
+
31
+ return {
32
+ turn,
33
+ organic,
34
+ };
35
+ }
36
+
37
+ /**
38
+ * Helper function to check if an object is an Anthropic web search result
39
+ */
40
+ export function isAnthropicWebSearchResult(
41
+ obj: unknown
42
+ ): obj is AnthropicWebSearchResultBlockParam {
43
+ return (
44
+ typeof obj === 'object' &&
45
+ obj !== null &&
46
+ 'type' in obj &&
47
+ obj.type === 'web_search_result' &&
48
+ 'url' in obj &&
49
+ typeof (obj as Record<string, unknown>).url === 'string'
50
+ );
51
+ }