@illuma-ai/agents 1.1.21 → 1.1.23

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 (244) hide show
  1. package/dist/cjs/graphs/Graph.cjs +12 -1
  2. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  3. package/dist/cjs/graphs/MultiAgentGraph.cjs +105 -1
  4. package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
  5. package/dist/cjs/run.cjs +20 -9
  6. package/dist/cjs/run.cjs.map +1 -1
  7. package/dist/cjs/utils/llm.cjs.map +1 -1
  8. package/dist/esm/graphs/Graph.mjs +12 -1
  9. package/dist/esm/graphs/Graph.mjs.map +1 -1
  10. package/dist/esm/graphs/MultiAgentGraph.mjs +105 -1
  11. package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
  12. package/dist/esm/run.mjs +20 -9
  13. package/dist/esm/run.mjs.map +1 -1
  14. package/dist/esm/utils/llm.mjs.map +1 -1
  15. package/dist/types/graphs/MultiAgentGraph.d.ts +17 -0
  16. package/package.json +1 -1
  17. package/src/graphs/Graph.ts +13 -1
  18. package/src/graphs/MultiAgentGraph.ts +128 -1
  19. package/src/graphs/__tests__/multi-agent-delegate.test.ts +205 -0
  20. package/src/run.ts +20 -11
  21. package/src/scripts/test-bedrock-handoff-autonomous.ts +231 -0
  22. package/src/utils/llm.ts +1 -0
  23. package/src/agents/AgentContext.js +0 -782
  24. package/src/agents/AgentContext.test.js +0 -421
  25. package/src/agents/__tests__/AgentContext.test.js +0 -678
  26. package/src/agents/__tests__/resolveStructuredOutputMode.test.js +0 -117
  27. package/src/common/enum.js +0 -192
  28. package/src/common/index.js +0 -3
  29. package/src/events.js +0 -166
  30. package/src/graphs/Graph.js +0 -1857
  31. package/src/graphs/MultiAgentGraph.js +0 -1092
  32. package/src/graphs/__tests__/structured-output.integration.test.js +0 -624
  33. package/src/graphs/__tests__/structured-output.test.js +0 -144
  34. package/src/graphs/contextManagement.e2e.test.js +0 -718
  35. package/src/graphs/contextManagement.test.js +0 -485
  36. package/src/graphs/handoffValidation.test.js +0 -276
  37. package/src/graphs/index.js +0 -3
  38. package/src/index.js +0 -28
  39. package/src/instrumentation.js +0 -21
  40. package/src/llm/anthropic/index.js +0 -319
  41. package/src/llm/anthropic/types.js +0 -46
  42. package/src/llm/anthropic/utils/message_inputs.js +0 -627
  43. package/src/llm/anthropic/utils/message_outputs.js +0 -290
  44. package/src/llm/anthropic/utils/output_parsers.js +0 -89
  45. package/src/llm/anthropic/utils/tools.js +0 -25
  46. package/src/llm/bedrock/__tests__/bedrock-caching.test.js +0 -392
  47. package/src/llm/bedrock/index.js +0 -303
  48. package/src/llm/bedrock/types.js +0 -2
  49. package/src/llm/bedrock/utils/index.js +0 -6
  50. package/src/llm/bedrock/utils/message_inputs.js +0 -463
  51. package/src/llm/bedrock/utils/message_outputs.js +0 -269
  52. package/src/llm/fake.js +0 -92
  53. package/src/llm/google/index.js +0 -215
  54. package/src/llm/google/types.js +0 -12
  55. package/src/llm/google/utils/common.js +0 -670
  56. package/src/llm/google/utils/tools.js +0 -111
  57. package/src/llm/google/utils/zod_to_genai_parameters.js +0 -47
  58. package/src/llm/openai/index.js +0 -1033
  59. package/src/llm/openai/types.js +0 -2
  60. package/src/llm/openai/utils/index.js +0 -756
  61. package/src/llm/openai/utils/isReasoningModel.test.js +0 -79
  62. package/src/llm/openrouter/index.js +0 -261
  63. package/src/llm/openrouter/reasoning.test.js +0 -181
  64. package/src/llm/providers.js +0 -36
  65. package/src/llm/text.js +0 -65
  66. package/src/llm/vertexai/index.js +0 -402
  67. package/src/messages/__tests__/tools.test.js +0 -392
  68. package/src/messages/cache.js +0 -404
  69. package/src/messages/cache.test.js +0 -1167
  70. package/src/messages/content.js +0 -48
  71. package/src/messages/content.test.js +0 -314
  72. package/src/messages/core.js +0 -359
  73. package/src/messages/ensureThinkingBlock.test.js +0 -997
  74. package/src/messages/format.js +0 -973
  75. package/src/messages/formatAgentMessages.test.js +0 -2278
  76. package/src/messages/formatAgentMessages.tools.test.js +0 -362
  77. package/src/messages/formatMessage.test.js +0 -608
  78. package/src/messages/ids.js +0 -18
  79. package/src/messages/index.js +0 -9
  80. package/src/messages/labelContentByAgent.test.js +0 -725
  81. package/src/messages/prune.js +0 -438
  82. package/src/messages/reducer.js +0 -60
  83. package/src/messages/shiftIndexTokenCountMap.test.js +0 -63
  84. package/src/messages/summarize.js +0 -146
  85. package/src/messages/summarize.test.js +0 -332
  86. package/src/messages/tools.js +0 -90
  87. package/src/mockStream.js +0 -81
  88. package/src/prompts/collab.js +0 -7
  89. package/src/prompts/index.js +0 -3
  90. package/src/prompts/taskmanager.js +0 -58
  91. package/src/run.js +0 -427
  92. package/src/schemas/index.js +0 -3
  93. package/src/schemas/schema-preparation.test.js +0 -370
  94. package/src/schemas/validate.js +0 -314
  95. package/src/schemas/validate.test.js +0 -264
  96. package/src/scripts/abort.js +0 -127
  97. package/src/scripts/ant_web_search.js +0 -130
  98. package/src/scripts/ant_web_search_edge_case.js +0 -133
  99. package/src/scripts/ant_web_search_error_edge_case.js +0 -119
  100. package/src/scripts/args.js +0 -41
  101. package/src/scripts/bedrock-cache-debug.js +0 -186
  102. package/src/scripts/bedrock-content-aggregation-test.js +0 -195
  103. package/src/scripts/bedrock-merge-test.js +0 -80
  104. package/src/scripts/bedrock-parallel-tools-test.js +0 -150
  105. package/src/scripts/caching.js +0 -106
  106. package/src/scripts/cli.js +0 -152
  107. package/src/scripts/cli2.js +0 -119
  108. package/src/scripts/cli3.js +0 -163
  109. package/src/scripts/cli4.js +0 -165
  110. package/src/scripts/cli5.js +0 -165
  111. package/src/scripts/code_exec.js +0 -171
  112. package/src/scripts/code_exec_files.js +0 -180
  113. package/src/scripts/code_exec_multi_session.js +0 -185
  114. package/src/scripts/code_exec_ptc.js +0 -265
  115. package/src/scripts/code_exec_session.js +0 -217
  116. package/src/scripts/code_exec_simple.js +0 -120
  117. package/src/scripts/content.js +0 -111
  118. package/src/scripts/empty_input.js +0 -125
  119. package/src/scripts/handoff-test.js +0 -96
  120. package/src/scripts/image.js +0 -138
  121. package/src/scripts/memory.js +0 -83
  122. package/src/scripts/multi-agent-chain.js +0 -271
  123. package/src/scripts/multi-agent-conditional.js +0 -185
  124. package/src/scripts/multi-agent-document-review-chain.js +0 -171
  125. package/src/scripts/multi-agent-hybrid-flow.js +0 -264
  126. package/src/scripts/multi-agent-parallel-start.js +0 -214
  127. package/src/scripts/multi-agent-parallel.js +0 -346
  128. package/src/scripts/multi-agent-sequence.js +0 -184
  129. package/src/scripts/multi-agent-supervisor.js +0 -324
  130. package/src/scripts/multi-agent-test.js +0 -147
  131. package/src/scripts/parallel-asymmetric-tools-test.js +0 -202
  132. package/src/scripts/parallel-full-metadata-test.js +0 -176
  133. package/src/scripts/parallel-tools-test.js +0 -256
  134. package/src/scripts/programmatic_exec.js +0 -277
  135. package/src/scripts/programmatic_exec_agent.js +0 -168
  136. package/src/scripts/search.js +0 -118
  137. package/src/scripts/sequential-full-metadata-test.js +0 -143
  138. package/src/scripts/simple.js +0 -174
  139. package/src/scripts/single-agent-metadata-test.js +0 -152
  140. package/src/scripts/stream.js +0 -113
  141. package/src/scripts/test-custom-prompt-key.js +0 -132
  142. package/src/scripts/test-handoff-input.js +0 -143
  143. package/src/scripts/test-handoff-preamble.js +0 -227
  144. package/src/scripts/test-handoff-steering.js +0 -353
  145. package/src/scripts/test-multi-agent-list-handoff.js +0 -318
  146. package/src/scripts/test-parallel-agent-labeling.js +0 -253
  147. package/src/scripts/test-parallel-handoffs.js +0 -229
  148. package/src/scripts/test-thinking-handoff-bedrock.js +0 -132
  149. package/src/scripts/test-thinking-handoff.js +0 -132
  150. package/src/scripts/test-thinking-to-thinking-handoff-bedrock.js +0 -140
  151. package/src/scripts/test-tool-before-handoff-role-order.js +0 -223
  152. package/src/scripts/test-tools-before-handoff.js +0 -187
  153. package/src/scripts/test_code_api.js +0 -263
  154. package/src/scripts/thinking-bedrock.js +0 -128
  155. package/src/scripts/thinking-vertexai.js +0 -130
  156. package/src/scripts/thinking.js +0 -134
  157. package/src/scripts/tool_search.js +0 -114
  158. package/src/scripts/tools.js +0 -125
  159. package/src/specs/agent-handoffs-bedrock.integration.test.js +0 -280
  160. package/src/specs/agent-handoffs.test.js +0 -924
  161. package/src/specs/anthropic.simple.test.js +0 -287
  162. package/src/specs/azure.simple.test.js +0 -381
  163. package/src/specs/cache.simple.test.js +0 -282
  164. package/src/specs/custom-event-await.test.js +0 -148
  165. package/src/specs/deepseek.simple.test.js +0 -189
  166. package/src/specs/emergency-prune.test.js +0 -308
  167. package/src/specs/moonshot.simple.test.js +0 -237
  168. package/src/specs/observability.integration.test.js +0 -1337
  169. package/src/specs/openai.simple.test.js +0 -233
  170. package/src/specs/openrouter.simple.test.js +0 -202
  171. package/src/specs/prune.test.js +0 -733
  172. package/src/specs/reasoning.test.js +0 -144
  173. package/src/specs/spec.utils.js +0 -4
  174. package/src/specs/thinking-handoff.test.js +0 -486
  175. package/src/specs/thinking-prune.test.js +0 -600
  176. package/src/specs/token-distribution-edge-case.test.js +0 -246
  177. package/src/specs/token-memoization.test.js +0 -32
  178. package/src/specs/tokens.test.js +0 -49
  179. package/src/specs/tool-error.test.js +0 -139
  180. package/src/splitStream.js +0 -204
  181. package/src/splitStream.test.js +0 -504
  182. package/src/stream.js +0 -650
  183. package/src/stream.test.js +0 -225
  184. package/src/test/mockTools.js +0 -340
  185. package/src/tools/BrowserTools.js +0 -245
  186. package/src/tools/Calculator.js +0 -38
  187. package/src/tools/Calculator.test.js +0 -225
  188. package/src/tools/CodeExecutor.js +0 -233
  189. package/src/tools/ProgrammaticToolCalling.js +0 -602
  190. package/src/tools/StreamingToolCallBuffer.js +0 -179
  191. package/src/tools/ToolNode.js +0 -930
  192. package/src/tools/ToolSearch.js +0 -904
  193. package/src/tools/__tests__/BrowserTools.test.js +0 -306
  194. package/src/tools/__tests__/ProgrammaticToolCalling.integration.test.js +0 -276
  195. package/src/tools/__tests__/ProgrammaticToolCalling.test.js +0 -807
  196. package/src/tools/__tests__/StreamingToolCallBuffer.test.js +0 -175
  197. package/src/tools/__tests__/ToolApproval.test.js +0 -675
  198. package/src/tools/__tests__/ToolNode.recovery.test.js +0 -200
  199. package/src/tools/__tests__/ToolNode.session.test.js +0 -319
  200. package/src/tools/__tests__/ToolSearch.integration.test.js +0 -125
  201. package/src/tools/__tests__/ToolSearch.test.js +0 -812
  202. package/src/tools/__tests__/handlers.test.js +0 -799
  203. package/src/tools/__tests__/truncation-recovery.integration.test.js +0 -362
  204. package/src/tools/handlers.js +0 -306
  205. package/src/tools/schema.js +0 -25
  206. package/src/tools/search/anthropic.js +0 -34
  207. package/src/tools/search/content.js +0 -116
  208. package/src/tools/search/content.test.js +0 -133
  209. package/src/tools/search/firecrawl.js +0 -173
  210. package/src/tools/search/format.js +0 -198
  211. package/src/tools/search/highlights.js +0 -241
  212. package/src/tools/search/index.js +0 -3
  213. package/src/tools/search/jina-reranker.test.js +0 -106
  214. package/src/tools/search/rerankers.js +0 -165
  215. package/src/tools/search/schema.js +0 -102
  216. package/src/tools/search/search.js +0 -561
  217. package/src/tools/search/serper-scraper.js +0 -126
  218. package/src/tools/search/test.js +0 -129
  219. package/src/tools/search/tool.js +0 -453
  220. package/src/tools/search/types.js +0 -2
  221. package/src/tools/search/utils.js +0 -59
  222. package/src/types/graph.js +0 -24
  223. package/src/types/graph.test.js +0 -192
  224. package/src/types/index.js +0 -7
  225. package/src/types/llm.js +0 -2
  226. package/src/types/messages.js +0 -2
  227. package/src/types/run.js +0 -2
  228. package/src/types/stream.js +0 -2
  229. package/src/types/tools.js +0 -2
  230. package/src/utils/contextAnalytics.js +0 -79
  231. package/src/utils/contextAnalytics.test.js +0 -166
  232. package/src/utils/events.js +0 -26
  233. package/src/utils/graph.js +0 -11
  234. package/src/utils/handlers.js +0 -65
  235. package/src/utils/index.js +0 -10
  236. package/src/utils/llm.js +0 -21
  237. package/src/utils/llmConfig.js +0 -205
  238. package/src/utils/logging.js +0 -37
  239. package/src/utils/misc.js +0 -51
  240. package/src/utils/run.js +0 -69
  241. package/src/utils/schema.js +0 -21
  242. package/src/utils/title.js +0 -119
  243. package/src/utils/tokens.js +0 -92
  244. package/src/utils/toonFormat.js +0 -379
@@ -1,362 +0,0 @@
1
- /**
2
- * Integration test: Full Truncation Recovery Pipeline
3
- *
4
- * Tests the end-to-end flow that would catch the Bedrock buffer bug:
5
- * Streaming chunks → handleToolCallChunks → StreamingToolCallBuffer → handleToolCalls → ToolNode.recoverTruncatedArgs → tool receives recovered args
6
- *
7
- * Uses real StreamingToolCallBuffer (not mocked) and real ToolNode with real tool definitions.
8
- * Only the graph dispatch layer is mocked (since we're not testing SSE dispatch).
9
- *
10
- * Verified against live Bedrock API output (see bedrock-live-poc.ts):
11
- * START chunk: { id, name, index, args: "" }
12
- * DELTA chunks: { args, index } — NO id, NO name
13
- */
14
- import { z } from 'zod';
15
- import { tool } from '@langchain/core/tools';
16
- import { AIMessage } from '@langchain/core/messages';
17
- import { describe, it, expect, beforeEach, jest } from '@jest/globals';
18
- import { StepTypes } from '@/common';
19
- import { StreamingToolCallBuffer } from '../StreamingToolCallBuffer';
20
- import { handleToolCallChunks } from '../handlers';
21
- import { ToolNode } from '../ToolNode';
22
- // ---------------------------------------------------------------------------
23
- // Helpers
24
- // ---------------------------------------------------------------------------
25
- /** Creates a mock graph that delegates buffer operations to a REAL StreamingToolCallBuffer */
26
- function createIntegrationGraph(buffer) {
27
- let stepCounter = 0;
28
- return {
29
- getStepKey: jest.fn().mockReturnValue('step-key'),
30
- getStepIdByKey: jest.fn().mockReturnValue('prev-step-id'),
31
- getRunStep: jest.fn().mockReturnValue({
32
- type: StepTypes.MESSAGE_CREATION,
33
- id: 'prev-step-id',
34
- index: 0,
35
- stepDetails: { type: StepTypes.MESSAGE_CREATION, message_creation: { message_id: 'msg-1' } },
36
- usage: null,
37
- }),
38
- dispatchRunStep: jest.fn()
39
- .mockImplementation(async () => `step-${++stepCounter}`),
40
- dispatchRunStepDelta: jest.fn().mockResolvedValue(undefined),
41
- toolCallStepIds: new Map(),
42
- messageStepHasToolCalls: new Map(),
43
- messageIdsByStepKey: new Map(),
44
- prelimMessageIdsByStepKey: new Map(),
45
- streamingToolCallBuffer: buffer, // REAL buffer, not mocked
46
- };
47
- }
48
- /** Creates a tool that captures received args for assertion */
49
- function createCaptureTool(name, schema, capturedArgs) {
50
- return tool(async (input) => {
51
- capturedArgs.push({ ...input });
52
- return `OK`;
53
- }, { name, description: `Test tool: ${name}`, schema });
54
- }
55
- /** Simulates Bedrock streaming: START chunk with {id, name, index, args:""}, then DELTA chunks with {args, index} */
56
- function simulateBedrockChunks(toolCallId, toolName, index, argsJson, chunkSize = 30) {
57
- const chunks = [];
58
- // START chunk: id + name + index, args="" (verified from live Bedrock API)
59
- chunks.push({
60
- id: toolCallId,
61
- name: toolName,
62
- index,
63
- args: '',
64
- type: 'tool_call_chunk',
65
- });
66
- // DELTA chunks: args + index, NO id, NO name (verified from live Bedrock API)
67
- for (let i = 0; i < argsJson.length; i += chunkSize) {
68
- chunks.push({
69
- id: undefined,
70
- name: undefined,
71
- index,
72
- args: argsJson.substring(i, i + chunkSize),
73
- type: 'tool_call_chunk',
74
- });
75
- }
76
- return chunks;
77
- }
78
- /** Simulates OpenAI/Anthropic streaming: every chunk has {id, args} */
79
- function simulateOpenAIChunks(toolCallId, toolName, index, argsJson, chunkSize = 30) {
80
- const chunks = [];
81
- // First chunk: id + name + first args fragment
82
- const firstArgs = argsJson.substring(0, chunkSize);
83
- chunks.push({
84
- id: toolCallId,
85
- name: toolName,
86
- index,
87
- args: firstArgs,
88
- type: 'tool_call_chunk',
89
- });
90
- // Subsequent chunks: id + args (OpenAI sends id on every chunk)
91
- for (let i = chunkSize; i < argsJson.length; i += chunkSize) {
92
- chunks.push({
93
- id: toolCallId,
94
- name: undefined,
95
- index,
96
- args: argsJson.substring(i, i + chunkSize),
97
- type: 'tool_call_chunk',
98
- });
99
- }
100
- return chunks;
101
- }
102
- const contentToolSchema = z.object({
103
- action: z.string(),
104
- filename: z.string().optional(),
105
- content: z.string().optional(),
106
- });
107
- const codeExecutorSchema = z.object({
108
- code: z.string(),
109
- language: z.string().optional(),
110
- });
111
- // ---------------------------------------------------------------------------
112
- // Tests
113
- // ---------------------------------------------------------------------------
114
- describe('Truncation Recovery Pipeline (Integration)', () => {
115
- let buffer;
116
- beforeEach(() => {
117
- buffer = new StreamingToolCallBuffer();
118
- });
119
- it('Bedrock truncated write — full pipeline recovery', async () => {
120
- const capturedArgs = [];
121
- const graph = createIntegrationGraph(buffer);
122
- // Full args JSON that Bedrock would stream (including content)
123
- const fullArgsJson = '{"action":"write","filename":"app.tsx","content":"import React from \\"react\\";\\nexport default function App() {\\n return <div>Hello World</div>;\\n}"}';
124
- // Simulate Bedrock streaming: START + DELTA chunks
125
- const chunks = simulateBedrockChunks('tooluse_abc', 'content_tool', 0, fullArgsJson, 20);
126
- // Feed all chunks through handleToolCallChunks (real buffer accumulation)
127
- for (const chunk of chunks) {
128
- await handleToolCallChunks({
129
- graph: graph,
130
- stepKey: 'step-key',
131
- toolCallChunks: [chunk],
132
- metadata: { run_id: 'test' },
133
- });
134
- }
135
- // Verify buffer was populated
136
- expect(buffer.has('tooluse_abc')).toBe(true);
137
- expect(buffer.getRawArgs('tooluse_abc')).toBe(fullArgsJson);
138
- // Simulate truncation: parsePartialJson lost the content field
139
- const truncatedArgs = { action: 'write', filename: 'app.tsx' }; // content MISSING
140
- // Create ToolNode with real buffer
141
- const contentTool = createCaptureTool('content_tool', contentToolSchema, capturedArgs);
142
- const toolNode = new ToolNode({
143
- tools: [contentTool],
144
- streamingToolCallBuffer: buffer,
145
- });
146
- // Invoke ToolNode with truncated args
147
- const aiMsg = new AIMessage({
148
- content: '',
149
- tool_calls: [{ id: 'tooluse_abc', name: 'content_tool', args: truncatedArgs }],
150
- });
151
- await toolNode.invoke({ messages: [aiMsg] });
152
- // Assert: tool received RECOVERED content, not truncated
153
- expect(capturedArgs).toHaveLength(1);
154
- expect(capturedArgs[0].action).toBe('write');
155
- expect(capturedArgs[0].filename).toBe('app.tsx');
156
- expect(capturedArgs[0].content).toBe('import React from "react";\nexport default function App() {\n return <div>Hello World</div>;\n}');
157
- });
158
- it('OpenAI/Anthropic chunked write — full pipeline recovery', async () => {
159
- const capturedArgs = [];
160
- const graph = createIntegrationGraph(buffer);
161
- const fullArgsJson = '{"action":"write","filename":"test.tsx","content":"const x = 42;\\nconst y = 100;"}';
162
- const chunks = simulateOpenAIChunks('tc_openai', 'content_tool', 0, fullArgsJson, 15);
163
- for (const chunk of chunks) {
164
- await handleToolCallChunks({
165
- graph: graph,
166
- stepKey: 'step-key',
167
- toolCallChunks: [chunk],
168
- metadata: { run_id: 'test' },
169
- });
170
- }
171
- expect(buffer.has('tc_openai')).toBe(true);
172
- expect(buffer.getRawArgs('tc_openai')).toBe(fullArgsJson);
173
- // Simulate truncation: content missing
174
- const contentTool = createCaptureTool('content_tool', contentToolSchema, capturedArgs);
175
- const toolNode = new ToolNode({
176
- tools: [contentTool],
177
- streamingToolCallBuffer: buffer,
178
- });
179
- const aiMsg = new AIMessage({
180
- content: '',
181
- tool_calls: [{ id: 'tc_openai', name: 'content_tool', args: { action: 'write', filename: 'test.tsx' } }],
182
- });
183
- await toolNode.invoke({ messages: [aiMsg] });
184
- expect(capturedArgs[0].content).toBe('const x = 42;\nconst y = 100;');
185
- });
186
- it('multiple parallel tool calls — no cross-contamination', async () => {
187
- const capturedArgs = [];
188
- const graph = createIntegrationGraph(buffer);
189
- // Two parallel tool calls with different indexes
190
- const args1 = '{"action":"write","filename":"a.tsx","content":"file A content"}';
191
- const args2 = '{"action":"write","filename":"b.tsx","content":"file B content"}';
192
- const chunks1 = simulateBedrockChunks('tc_1', 'content_tool', 0, args1, 15);
193
- const chunks2 = simulateBedrockChunks('tc_2', 'content_tool', 1, args2, 15);
194
- // Interleave chunks (realistic concurrent streaming)
195
- const interleaved = [];
196
- const maxLen = Math.max(chunks1.length, chunks2.length);
197
- for (let i = 0; i < maxLen; i++) {
198
- if (i < chunks1.length)
199
- interleaved.push(chunks1[i]);
200
- if (i < chunks2.length)
201
- interleaved.push(chunks2[i]);
202
- }
203
- for (const chunk of interleaved) {
204
- await handleToolCallChunks({
205
- graph: graph,
206
- stepKey: 'step-key',
207
- toolCallChunks: [chunk],
208
- metadata: { run_id: 'test' },
209
- });
210
- }
211
- expect(buffer.getRawArgs('tc_1')).toBe(args1);
212
- expect(buffer.getRawArgs('tc_2')).toBe(args2);
213
- const contentTool = createCaptureTool('content_tool', contentToolSchema, capturedArgs);
214
- const toolNode = new ToolNode({
215
- tools: [contentTool],
216
- streamingToolCallBuffer: buffer,
217
- });
218
- // Only tc_1 is truncated (missing content), tc_2 has all fields
219
- const aiMsg = new AIMessage({
220
- content: '',
221
- tool_calls: [
222
- { id: 'tc_1', name: 'content_tool', args: { action: 'write', filename: 'a.tsx' } }, // truncated
223
- { id: 'tc_2', name: 'content_tool', args: { action: 'write', filename: 'b.tsx', content: 'file B content' } }, // complete
224
- ],
225
- });
226
- await toolNode.invoke({ messages: [aiMsg] });
227
- expect(capturedArgs).toHaveLength(2);
228
- // tc_1: content recovered from buffer
229
- expect(capturedArgs[0].content).toBe('file A content');
230
- // tc_2: content unchanged (already present in parsed args)
231
- expect(capturedArgs[1].content).toBe('file B content');
232
- });
233
- it('buffer cleanup after recovery — no stale data', async () => {
234
- const capturedArgs = [];
235
- const graph = createIntegrationGraph(buffer);
236
- const fullArgs = '{"action":"write","filename":"x.tsx","content":"test"}';
237
- const chunks = simulateBedrockChunks('tc_cleanup', 'content_tool', 0, fullArgs);
238
- for (const chunk of chunks) {
239
- await handleToolCallChunks({
240
- graph: graph,
241
- stepKey: 'step-key',
242
- toolCallChunks: [chunk],
243
- metadata: { run_id: 'test' },
244
- });
245
- }
246
- expect(buffer.has('tc_cleanup')).toBe(true);
247
- const contentTool = createCaptureTool('content_tool', contentToolSchema, capturedArgs);
248
- const toolNode = new ToolNode({
249
- tools: [contentTool],
250
- streamingToolCallBuffer: buffer,
251
- });
252
- const aiMsg = new AIMessage({
253
- content: '',
254
- tool_calls: [{ id: 'tc_cleanup', name: 'content_tool', args: { action: 'write' } }],
255
- });
256
- await toolNode.invoke({ messages: [aiMsg] });
257
- // Buffer should be cleared after processing
258
- expect(buffer.has('tc_cleanup')).toBe(false);
259
- expect(buffer.getRawArgs('tc_cleanup')).toBeUndefined();
260
- });
261
- it('no truncation — buffer populated but not used for overwrite', async () => {
262
- const capturedArgs = [];
263
- const graph = createIntegrationGraph(buffer);
264
- const fullArgs = '{"action":"read","filename":"existing.tsx"}';
265
- const chunks = simulateBedrockChunks('tc_complete', 'content_tool', 0, fullArgs);
266
- for (const chunk of chunks) {
267
- await handleToolCallChunks({
268
- graph: graph,
269
- stepKey: 'step-key',
270
- toolCallChunks: [chunk],
271
- metadata: { run_id: 'test' },
272
- });
273
- }
274
- const contentTool = createCaptureTool('content_tool', contentToolSchema, capturedArgs);
275
- const toolNode = new ToolNode({
276
- tools: [contentTool],
277
- streamingToolCallBuffer: buffer,
278
- });
279
- // Complete args — nothing missing
280
- const aiMsg = new AIMessage({
281
- content: '',
282
- tool_calls: [{ id: 'tc_complete', name: 'content_tool', args: { action: 'read', filename: 'existing.tsx' } }],
283
- });
284
- await toolNode.invoke({ messages: [aiMsg] });
285
- expect(capturedArgs[0].action).toBe('read');
286
- expect(capturedArgs[0].filename).toBe('existing.tsx');
287
- // No content field in args or buffer — should stay absent
288
- expect(capturedArgs[0]).not.toHaveProperty('content');
289
- });
290
- it('generic tool recovery — non-content_tool (code_executor)', async () => {
291
- const capturedArgs = [];
292
- const graph = createIntegrationGraph(buffer);
293
- const largeCode = 'function fibonacci(n) {\\n if (n <= 1) return n;\\n return fibonacci(n-1) + fibonacci(n-2);\\n}\\nconsole.log(fibonacci(10));';
294
- const fullArgs = `{"code":"${largeCode}","language":"javascript"}`;
295
- const chunks = simulateBedrockChunks('tc_code', 'execute_code', 0, fullArgs, 20);
296
- for (const chunk of chunks) {
297
- await handleToolCallChunks({
298
- graph: graph,
299
- stepKey: 'step-key',
300
- toolCallChunks: [chunk],
301
- metadata: { run_id: 'test' },
302
- });
303
- }
304
- const codeTool = createCaptureTool('execute_code', codeExecutorSchema, capturedArgs);
305
- const toolNode = new ToolNode({
306
- tools: [codeTool],
307
- streamingToolCallBuffer: buffer,
308
- });
309
- // Truncated: only has language, code is missing
310
- const aiMsg = new AIMessage({
311
- content: '',
312
- tool_calls: [{ id: 'tc_code', name: 'execute_code', args: { language: 'javascript' } }],
313
- });
314
- await toolNode.invoke({ messages: [aiMsg] });
315
- expect(capturedArgs[0].code).toContain('fibonacci');
316
- expect(capturedArgs[0].language).toBe('javascript');
317
- });
318
- it('Bedrock large content (5000+ chars) — recovery preserves full content', async () => {
319
- const capturedArgs = [];
320
- const graph = createIntegrationGraph(buffer);
321
- // Generate large content (simulating a real dashboard component)
322
- const lines = [];
323
- lines.push('import React from \\"react\\";');
324
- lines.push('import { useState, useEffect } from \\"react\\";');
325
- for (let i = 0; i < 100; i++) {
326
- lines.push(`// Component section ${i}`);
327
- lines.push(`const Section${i} = () => <div>Section ${i}</div>;`);
328
- }
329
- lines.push('export default function Dashboard() {');
330
- lines.push(' return <div>Dashboard</div>;');
331
- lines.push('}');
332
- const content = lines.join('\\n');
333
- const fullArgs = `{"action":"write","filename":"dashboard.tsx","content":"${content}"}`;
334
- const chunks = simulateBedrockChunks('tc_large', 'content_tool', 0, fullArgs, 50);
335
- for (const chunk of chunks) {
336
- await handleToolCallChunks({
337
- graph: graph,
338
- stepKey: 'step-key',
339
- toolCallChunks: [chunk],
340
- metadata: { run_id: 'test' },
341
- });
342
- }
343
- const contentTool = createCaptureTool('content_tool', contentToolSchema, capturedArgs);
344
- const toolNode = new ToolNode({
345
- tools: [contentTool],
346
- streamingToolCallBuffer: buffer,
347
- });
348
- // Completely empty args (truncation wiped everything)
349
- const aiMsg = new AIMessage({
350
- content: '',
351
- tool_calls: [{ id: 'tc_large', name: 'content_tool', args: {} }],
352
- });
353
- await toolNode.invoke({ messages: [aiMsg] });
354
- expect(capturedArgs[0].action).toBe('write');
355
- expect(capturedArgs[0].filename).toBe('dashboard.tsx');
356
- const recoveredContent = capturedArgs[0].content;
357
- expect(recoveredContent).toContain('import React from "react"');
358
- expect(recoveredContent).toContain('Section 99');
359
- expect(recoveredContent).toContain('export default function Dashboard()');
360
- });
361
- });
362
- //# sourceMappingURL=truncation-recovery.integration.test.js.map
@@ -1,306 +0,0 @@
1
- /* eslint-disable no-console */
2
- // src/tools/handlers.ts
3
- import { nanoid } from 'nanoid';
4
- import { ToolMessage } from '@langchain/core/messages';
5
- import { ToolCallTypes, GraphEvents, StepTypes, Providers, Constants, } from '@/common';
6
- import { coerceAnthropicSearchResults, isAnthropicWebSearchResult, } from '@/tools/search/anthropic';
7
- import { formatResultsForLLM } from '@/tools/search/format';
8
- import { getMessageId } from '@/messages';
9
- export async function handleToolCallChunks({ graph, stepKey, toolCallChunks, metadata, }) {
10
- let prevStepId;
11
- let prevRunStep;
12
- try {
13
- prevStepId = graph.getStepIdByKey(stepKey);
14
- prevRunStep = graph.getRunStep(prevStepId);
15
- }
16
- catch {
17
- /** Edge Case: If no previous step exists, create a new message creation step */
18
- const message_id = getMessageId(stepKey, graph, true) ?? '';
19
- prevStepId = await graph.dispatchRunStep(stepKey, {
20
- type: StepTypes.MESSAGE_CREATION,
21
- message_creation: {
22
- message_id,
23
- },
24
- }, metadata);
25
- prevRunStep = graph.getRunStep(prevStepId);
26
- }
27
- const _stepId = graph.getStepIdByKey(stepKey);
28
- /** Edge Case: Tool Call Run Step or `tool_call_ids` never dispatched */
29
- const tool_calls = prevStepId && prevRunStep && prevRunStep.type === StepTypes.MESSAGE_CREATION
30
- ? []
31
- : undefined;
32
- /**
33
- * Feed streaming tool call buffer — accumulate raw arg strings for truncation recovery.
34
- *
35
- * Provider chunk patterns:
36
- * - OpenAI/Anthropic: every chunk has {id, args} → direct append
37
- * - Bedrock: START chunk has {id, name, index}, DELTA chunks have {args, index} (no id)
38
- * → use index-to-id mapping to resolve the target buffer entry
39
- */
40
- for (const toolCallChunk of toolCallChunks) {
41
- const chunkIndex = toolCallChunk.index;
42
- // START chunk: has id (and usually name). Store index→id mapping for future DELTA chunks.
43
- if (toolCallChunk.id) {
44
- if (typeof chunkIndex === 'number') {
45
- graph.streamingToolCallBuffer.setIndexMapping(chunkIndex, toolCallChunk.id);
46
- }
47
- if (toolCallChunk.name) {
48
- graph.streamingToolCallBuffer.setToolName(toolCallChunk.id, toolCallChunk.name);
49
- }
50
- // Append args if present on the same chunk (OpenAI/Anthropic pattern)
51
- if (toolCallChunk.args) {
52
- graph.streamingToolCallBuffer.append(toolCallChunk.id, toolCallChunk.args);
53
- }
54
- }
55
- else if (toolCallChunk.args && typeof chunkIndex === 'number') {
56
- // DELTA chunk: no id, but has args + index. Resolve id via index mapping (Bedrock pattern).
57
- const resolvedId = graph.streamingToolCallBuffer.getIdByIndex(chunkIndex);
58
- if (resolvedId) {
59
- graph.streamingToolCallBuffer.append(resolvedId, toolCallChunk.args);
60
- }
61
- }
62
- }
63
- /** Edge Case: `id` and `name` fields cannot be empty strings */
64
- for (const toolCallChunk of toolCallChunks) {
65
- if (toolCallChunk.name === '') {
66
- toolCallChunk.name = undefined;
67
- }
68
- if (toolCallChunk.id === '') {
69
- toolCallChunk.id = undefined;
70
- }
71
- else if (tool_calls != null &&
72
- toolCallChunk.id != null &&
73
- toolCallChunk.name != null) {
74
- tool_calls.push({
75
- args: {},
76
- id: toolCallChunk.id,
77
- name: toolCallChunk.name,
78
- type: ToolCallTypes.TOOL_CALL,
79
- });
80
- }
81
- }
82
- let stepId = _stepId;
83
- const alreadyDispatched = prevRunStep?.type === StepTypes.MESSAGE_CREATION &&
84
- graph.messageStepHasToolCalls.has(prevStepId);
85
- if (prevRunStep?.type === StepTypes.TOOL_CALLS) {
86
- /**
87
- * If previous step is already a tool_calls step, use that step ID
88
- * This ensures tool call deltas are dispatched to the correct step
89
- */
90
- stepId = prevStepId;
91
- }
92
- else if (!alreadyDispatched &&
93
- prevRunStep?.type === StepTypes.MESSAGE_CREATION) {
94
- /**
95
- * Create tool_calls step as soon as we receive the first tool call chunk
96
- * This ensures deltas are always associated with the correct step
97
- *
98
- * NOTE: We do NOT dispatch an empty text block here because:
99
- * - Empty text blocks cause providers (Anthropic, Bedrock) to reject messages
100
- * - The tool_calls themselves are sufficient for the step
101
- * - Empty content with tool_call_ids gets stored in conversation history
102
- * and causes "messages must have non-empty content" errors on replay
103
- */
104
- graph.messageStepHasToolCalls.set(prevStepId, true);
105
- stepId = await graph.dispatchRunStep(stepKey, {
106
- type: StepTypes.TOOL_CALLS,
107
- tool_calls: tool_calls ?? [],
108
- }, metadata);
109
- }
110
- await graph.dispatchRunStepDelta(stepId, {
111
- type: StepTypes.TOOL_CALLS,
112
- tool_calls: toolCallChunks,
113
- });
114
- }
115
- export const handleToolCalls = async (toolCalls, metadata, graph) => {
116
- if (!graph || !metadata) {
117
- console.warn('Graph or metadata not found in `handleToolCalls`');
118
- return;
119
- }
120
- if (!toolCalls) {
121
- return;
122
- }
123
- if (toolCalls.length === 0) {
124
- return;
125
- }
126
- const stepKey = graph.getStepKey(metadata);
127
- /**
128
- * Track whether we've already reused an empty TOOL_CALLS step created by
129
- * handleToolCallChunks during streaming. Only reuse it once (for the first
130
- * tool call); subsequent parallel tool calls must create their own steps.
131
- */
132
- let reusedChunkStepId;
133
- for (const tool_call of toolCalls) {
134
- const toolCallId = tool_call.id ?? `toolu_${nanoid()}`;
135
- tool_call.id = toolCallId;
136
- // If this tool call ID was already tracked via handleToolCallChunks,
137
- // the step exists but may lack the name (Bedrock sends name only at model end).
138
- // Dispatch a delta with the complete data so the client can fill in the name.
139
- if (toolCallId && graph.toolCallStepIds.has(toolCallId)) {
140
- const existingStepId = graph.toolCallStepIds.get(toolCallId);
141
- if (existingStepId != null && existingStepId !== '') {
142
- const argsStr = typeof tool_call.args === 'string'
143
- ? tool_call.args
144
- : JSON.stringify(tool_call.args);
145
- await graph.dispatchRunStepDelta(existingStepId, {
146
- type: StepTypes.TOOL_CALLS,
147
- tool_calls: [{ name: tool_call.name, args: argsStr, id: toolCallId }],
148
- });
149
- }
150
- continue;
151
- }
152
- let prevStepId = '';
153
- let prevRunStep;
154
- try {
155
- prevStepId = graph.getStepIdByKey(stepKey);
156
- prevRunStep = graph.getRunStep(prevStepId);
157
- }
158
- catch {
159
- // no previous step
160
- }
161
- // If the previous step is TOOL_CALLS (created by handleToolCallChunks),
162
- // either reuse it (if empty) or dispatch a new TOOL_CALLS step directly —
163
- // skip the intermediate MESSAGE_CREATION to avoid orphaned gaps.
164
- if (prevRunStep?.type === StepTypes.TOOL_CALLS) {
165
- const details = prevRunStep.stepDetails;
166
- const isEmpty = !details.tool_calls || details.tool_calls.length === 0;
167
- if (isEmpty && prevStepId !== reusedChunkStepId) {
168
- graph.toolCallStepIds.set(toolCallId, prevStepId);
169
- reusedChunkStepId = prevStepId;
170
- continue;
171
- }
172
- await graph.dispatchRunStep(stepKey, { type: StepTypes.TOOL_CALLS, tool_calls: [tool_call] }, metadata);
173
- continue;
174
- }
175
- /**
176
- * NOTE: We do NOT dispatch empty text blocks with tool_call_ids because:
177
- * - Empty text blocks cause providers (Anthropic, Bedrock) to reject messages
178
- * - They get stored in conversation history and cause errors on replay:
179
- * "messages must have non-empty content" (Anthropic)
180
- * "The content field in the Message object is empty" (Bedrock)
181
- * - The tool_calls themselves are sufficient
182
- */
183
- /* If the previous step exists and is a message creation */
184
- if (prevStepId && prevRunStep) {
185
- graph.messageStepHasToolCalls.set(prevStepId, true);
186
- /* If the previous step doesn't exist */
187
- }
188
- else if (!prevRunStep) {
189
- const messageId = getMessageId(stepKey, graph, true) ?? '';
190
- const stepId = await graph.dispatchRunStep(stepKey, {
191
- type: StepTypes.MESSAGE_CREATION,
192
- message_creation: {
193
- message_id: messageId,
194
- },
195
- }, metadata);
196
- graph.messageStepHasToolCalls.set(stepId, true);
197
- }
198
- await graph.dispatchRunStep(stepKey, {
199
- type: StepTypes.TOOL_CALLS,
200
- tool_calls: [tool_call],
201
- }, metadata);
202
- }
203
- };
204
- export const toolResultTypes = new Set([
205
- // 'tool_use',
206
- // 'server_tool_use',
207
- // 'input_json_delta',
208
- 'tool_result',
209
- 'web_search_result',
210
- 'web_search_tool_result',
211
- ]);
212
- /**
213
- * Handles the result of a server tool call; in other words, a provider's built-in tool.
214
- * As of 2025-07-06, only Anthropic handles server tool calls with this pattern.
215
- */
216
- export async function handleServerToolResult({ graph, content, metadata, agentContext, }) {
217
- let skipHandling = false;
218
- if (agentContext?.provider !== Providers.ANTHROPIC) {
219
- return skipHandling;
220
- }
221
- if (typeof content === 'string' ||
222
- content == null ||
223
- content.length === 0 ||
224
- (content.length === 1 &&
225
- content[0].tool_use_id == null)) {
226
- return skipHandling;
227
- }
228
- for (const contentPart of content) {
229
- const toolUseId = contentPart.tool_use_id;
230
- if (toolUseId == null || toolUseId === '') {
231
- continue;
232
- }
233
- const stepId = graph.toolCallStepIds.get(toolUseId);
234
- if (stepId == null || stepId === '') {
235
- console.warn(`Tool use ID ${toolUseId} not found in graph, cannot dispatch tool result.`);
236
- continue;
237
- }
238
- const runStep = graph.getRunStep(stepId);
239
- if (!runStep) {
240
- console.warn(`Run step for ${stepId} does not exist, cannot dispatch tool result.`);
241
- continue;
242
- }
243
- else if (runStep.type !== StepTypes.TOOL_CALLS) {
244
- console.warn(`Run step for ${stepId} is not a tool call step, cannot dispatch tool result.`);
245
- continue;
246
- }
247
- const toolCall = runStep.stepDetails.type === StepTypes.TOOL_CALLS
248
- ? runStep.stepDetails.tool_calls?.find((toolCall) => toolCall.id === toolUseId)
249
- : undefined;
250
- if (!toolCall) {
251
- continue;
252
- }
253
- if (contentPart.type === 'web_search_result' ||
254
- contentPart.type === 'web_search_tool_result') {
255
- await handleAnthropicSearchResults({
256
- contentPart: contentPart,
257
- toolCall,
258
- metadata,
259
- graph,
260
- });
261
- }
262
- if (!skipHandling) {
263
- skipHandling = true;
264
- }
265
- }
266
- return skipHandling;
267
- }
268
- async function handleAnthropicSearchResults({ contentPart, toolCall, metadata, graph, }) {
269
- if (!Array.isArray(contentPart.content)) {
270
- console.warn(`Expected content to be an array, got ${typeof contentPart.content}`);
271
- return;
272
- }
273
- if (!isAnthropicWebSearchResult(contentPart.content[0])) {
274
- console.warn(`Expected content to be an Anthropic web search result, got ${JSON.stringify(contentPart.content)}`);
275
- return;
276
- }
277
- const turn = graph.invokedToolIds?.size ?? 0;
278
- const searchResultData = coerceAnthropicSearchResults({
279
- turn,
280
- results: contentPart.content,
281
- });
282
- const name = toolCall.name;
283
- const input = toolCall.args ?? {};
284
- const artifact = {
285
- [Constants.WEB_SEARCH]: searchResultData,
286
- };
287
- const { output: formattedOutput } = formatResultsForLLM(turn, searchResultData);
288
- const output = new ToolMessage({
289
- name,
290
- artifact,
291
- content: formattedOutput,
292
- tool_call_id: toolCall.id,
293
- });
294
- const toolEndData = {
295
- input,
296
- output,
297
- };
298
- await graph.handlerRegistry
299
- ?.getHandler(GraphEvents.TOOL_END)
300
- ?.handle(GraphEvents.TOOL_END, toolEndData, metadata, graph);
301
- if (graph.invokedToolIds == null) {
302
- graph.invokedToolIds = new Set();
303
- }
304
- graph.invokedToolIds.add(toolCall.id);
305
- }
306
- //# sourceMappingURL=handlers.js.map