@illuma-ai/agents 1.1.21 → 1.1.22

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 (241) 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 +85 -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/esm/graphs/Graph.mjs +12 -1
  8. package/dist/esm/graphs/Graph.mjs.map +1 -1
  9. package/dist/esm/graphs/MultiAgentGraph.mjs +85 -1
  10. package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
  11. package/dist/esm/run.mjs +20 -9
  12. package/dist/esm/run.mjs.map +1 -1
  13. package/dist/types/graphs/MultiAgentGraph.d.ts +17 -0
  14. package/package.json +1 -1
  15. package/src/graphs/Graph.ts +12 -1
  16. package/src/graphs/MultiAgentGraph.ts +105 -1
  17. package/src/graphs/__tests__/multi-agent-delegate.test.ts +191 -0
  18. package/src/run.ts +20 -11
  19. package/src/scripts/test-bedrock-handoff-autonomous.ts +231 -0
  20. package/src/agents/AgentContext.js +0 -782
  21. package/src/agents/AgentContext.test.js +0 -421
  22. package/src/agents/__tests__/AgentContext.test.js +0 -678
  23. package/src/agents/__tests__/resolveStructuredOutputMode.test.js +0 -117
  24. package/src/common/enum.js +0 -192
  25. package/src/common/index.js +0 -3
  26. package/src/events.js +0 -166
  27. package/src/graphs/Graph.js +0 -1857
  28. package/src/graphs/MultiAgentGraph.js +0 -1092
  29. package/src/graphs/__tests__/structured-output.integration.test.js +0 -624
  30. package/src/graphs/__tests__/structured-output.test.js +0 -144
  31. package/src/graphs/contextManagement.e2e.test.js +0 -718
  32. package/src/graphs/contextManagement.test.js +0 -485
  33. package/src/graphs/handoffValidation.test.js +0 -276
  34. package/src/graphs/index.js +0 -3
  35. package/src/index.js +0 -28
  36. package/src/instrumentation.js +0 -21
  37. package/src/llm/anthropic/index.js +0 -319
  38. package/src/llm/anthropic/types.js +0 -46
  39. package/src/llm/anthropic/utils/message_inputs.js +0 -627
  40. package/src/llm/anthropic/utils/message_outputs.js +0 -290
  41. package/src/llm/anthropic/utils/output_parsers.js +0 -89
  42. package/src/llm/anthropic/utils/tools.js +0 -25
  43. package/src/llm/bedrock/__tests__/bedrock-caching.test.js +0 -392
  44. package/src/llm/bedrock/index.js +0 -303
  45. package/src/llm/bedrock/types.js +0 -2
  46. package/src/llm/bedrock/utils/index.js +0 -6
  47. package/src/llm/bedrock/utils/message_inputs.js +0 -463
  48. package/src/llm/bedrock/utils/message_outputs.js +0 -269
  49. package/src/llm/fake.js +0 -92
  50. package/src/llm/google/index.js +0 -215
  51. package/src/llm/google/types.js +0 -12
  52. package/src/llm/google/utils/common.js +0 -670
  53. package/src/llm/google/utils/tools.js +0 -111
  54. package/src/llm/google/utils/zod_to_genai_parameters.js +0 -47
  55. package/src/llm/openai/index.js +0 -1033
  56. package/src/llm/openai/types.js +0 -2
  57. package/src/llm/openai/utils/index.js +0 -756
  58. package/src/llm/openai/utils/isReasoningModel.test.js +0 -79
  59. package/src/llm/openrouter/index.js +0 -261
  60. package/src/llm/openrouter/reasoning.test.js +0 -181
  61. package/src/llm/providers.js +0 -36
  62. package/src/llm/text.js +0 -65
  63. package/src/llm/vertexai/index.js +0 -402
  64. package/src/messages/__tests__/tools.test.js +0 -392
  65. package/src/messages/cache.js +0 -404
  66. package/src/messages/cache.test.js +0 -1167
  67. package/src/messages/content.js +0 -48
  68. package/src/messages/content.test.js +0 -314
  69. package/src/messages/core.js +0 -359
  70. package/src/messages/ensureThinkingBlock.test.js +0 -997
  71. package/src/messages/format.js +0 -973
  72. package/src/messages/formatAgentMessages.test.js +0 -2278
  73. package/src/messages/formatAgentMessages.tools.test.js +0 -362
  74. package/src/messages/formatMessage.test.js +0 -608
  75. package/src/messages/ids.js +0 -18
  76. package/src/messages/index.js +0 -9
  77. package/src/messages/labelContentByAgent.test.js +0 -725
  78. package/src/messages/prune.js +0 -438
  79. package/src/messages/reducer.js +0 -60
  80. package/src/messages/shiftIndexTokenCountMap.test.js +0 -63
  81. package/src/messages/summarize.js +0 -146
  82. package/src/messages/summarize.test.js +0 -332
  83. package/src/messages/tools.js +0 -90
  84. package/src/mockStream.js +0 -81
  85. package/src/prompts/collab.js +0 -7
  86. package/src/prompts/index.js +0 -3
  87. package/src/prompts/taskmanager.js +0 -58
  88. package/src/run.js +0 -427
  89. package/src/schemas/index.js +0 -3
  90. package/src/schemas/schema-preparation.test.js +0 -370
  91. package/src/schemas/validate.js +0 -314
  92. package/src/schemas/validate.test.js +0 -264
  93. package/src/scripts/abort.js +0 -127
  94. package/src/scripts/ant_web_search.js +0 -130
  95. package/src/scripts/ant_web_search_edge_case.js +0 -133
  96. package/src/scripts/ant_web_search_error_edge_case.js +0 -119
  97. package/src/scripts/args.js +0 -41
  98. package/src/scripts/bedrock-cache-debug.js +0 -186
  99. package/src/scripts/bedrock-content-aggregation-test.js +0 -195
  100. package/src/scripts/bedrock-merge-test.js +0 -80
  101. package/src/scripts/bedrock-parallel-tools-test.js +0 -150
  102. package/src/scripts/caching.js +0 -106
  103. package/src/scripts/cli.js +0 -152
  104. package/src/scripts/cli2.js +0 -119
  105. package/src/scripts/cli3.js +0 -163
  106. package/src/scripts/cli4.js +0 -165
  107. package/src/scripts/cli5.js +0 -165
  108. package/src/scripts/code_exec.js +0 -171
  109. package/src/scripts/code_exec_files.js +0 -180
  110. package/src/scripts/code_exec_multi_session.js +0 -185
  111. package/src/scripts/code_exec_ptc.js +0 -265
  112. package/src/scripts/code_exec_session.js +0 -217
  113. package/src/scripts/code_exec_simple.js +0 -120
  114. package/src/scripts/content.js +0 -111
  115. package/src/scripts/empty_input.js +0 -125
  116. package/src/scripts/handoff-test.js +0 -96
  117. package/src/scripts/image.js +0 -138
  118. package/src/scripts/memory.js +0 -83
  119. package/src/scripts/multi-agent-chain.js +0 -271
  120. package/src/scripts/multi-agent-conditional.js +0 -185
  121. package/src/scripts/multi-agent-document-review-chain.js +0 -171
  122. package/src/scripts/multi-agent-hybrid-flow.js +0 -264
  123. package/src/scripts/multi-agent-parallel-start.js +0 -214
  124. package/src/scripts/multi-agent-parallel.js +0 -346
  125. package/src/scripts/multi-agent-sequence.js +0 -184
  126. package/src/scripts/multi-agent-supervisor.js +0 -324
  127. package/src/scripts/multi-agent-test.js +0 -147
  128. package/src/scripts/parallel-asymmetric-tools-test.js +0 -202
  129. package/src/scripts/parallel-full-metadata-test.js +0 -176
  130. package/src/scripts/parallel-tools-test.js +0 -256
  131. package/src/scripts/programmatic_exec.js +0 -277
  132. package/src/scripts/programmatic_exec_agent.js +0 -168
  133. package/src/scripts/search.js +0 -118
  134. package/src/scripts/sequential-full-metadata-test.js +0 -143
  135. package/src/scripts/simple.js +0 -174
  136. package/src/scripts/single-agent-metadata-test.js +0 -152
  137. package/src/scripts/stream.js +0 -113
  138. package/src/scripts/test-custom-prompt-key.js +0 -132
  139. package/src/scripts/test-handoff-input.js +0 -143
  140. package/src/scripts/test-handoff-preamble.js +0 -227
  141. package/src/scripts/test-handoff-steering.js +0 -353
  142. package/src/scripts/test-multi-agent-list-handoff.js +0 -318
  143. package/src/scripts/test-parallel-agent-labeling.js +0 -253
  144. package/src/scripts/test-parallel-handoffs.js +0 -229
  145. package/src/scripts/test-thinking-handoff-bedrock.js +0 -132
  146. package/src/scripts/test-thinking-handoff.js +0 -132
  147. package/src/scripts/test-thinking-to-thinking-handoff-bedrock.js +0 -140
  148. package/src/scripts/test-tool-before-handoff-role-order.js +0 -223
  149. package/src/scripts/test-tools-before-handoff.js +0 -187
  150. package/src/scripts/test_code_api.js +0 -263
  151. package/src/scripts/thinking-bedrock.js +0 -128
  152. package/src/scripts/thinking-vertexai.js +0 -130
  153. package/src/scripts/thinking.js +0 -134
  154. package/src/scripts/tool_search.js +0 -114
  155. package/src/scripts/tools.js +0 -125
  156. package/src/specs/agent-handoffs-bedrock.integration.test.js +0 -280
  157. package/src/specs/agent-handoffs.test.js +0 -924
  158. package/src/specs/anthropic.simple.test.js +0 -287
  159. package/src/specs/azure.simple.test.js +0 -381
  160. package/src/specs/cache.simple.test.js +0 -282
  161. package/src/specs/custom-event-await.test.js +0 -148
  162. package/src/specs/deepseek.simple.test.js +0 -189
  163. package/src/specs/emergency-prune.test.js +0 -308
  164. package/src/specs/moonshot.simple.test.js +0 -237
  165. package/src/specs/observability.integration.test.js +0 -1337
  166. package/src/specs/openai.simple.test.js +0 -233
  167. package/src/specs/openrouter.simple.test.js +0 -202
  168. package/src/specs/prune.test.js +0 -733
  169. package/src/specs/reasoning.test.js +0 -144
  170. package/src/specs/spec.utils.js +0 -4
  171. package/src/specs/thinking-handoff.test.js +0 -486
  172. package/src/specs/thinking-prune.test.js +0 -600
  173. package/src/specs/token-distribution-edge-case.test.js +0 -246
  174. package/src/specs/token-memoization.test.js +0 -32
  175. package/src/specs/tokens.test.js +0 -49
  176. package/src/specs/tool-error.test.js +0 -139
  177. package/src/splitStream.js +0 -204
  178. package/src/splitStream.test.js +0 -504
  179. package/src/stream.js +0 -650
  180. package/src/stream.test.js +0 -225
  181. package/src/test/mockTools.js +0 -340
  182. package/src/tools/BrowserTools.js +0 -245
  183. package/src/tools/Calculator.js +0 -38
  184. package/src/tools/Calculator.test.js +0 -225
  185. package/src/tools/CodeExecutor.js +0 -233
  186. package/src/tools/ProgrammaticToolCalling.js +0 -602
  187. package/src/tools/StreamingToolCallBuffer.js +0 -179
  188. package/src/tools/ToolNode.js +0 -930
  189. package/src/tools/ToolSearch.js +0 -904
  190. package/src/tools/__tests__/BrowserTools.test.js +0 -306
  191. package/src/tools/__tests__/ProgrammaticToolCalling.integration.test.js +0 -276
  192. package/src/tools/__tests__/ProgrammaticToolCalling.test.js +0 -807
  193. package/src/tools/__tests__/StreamingToolCallBuffer.test.js +0 -175
  194. package/src/tools/__tests__/ToolApproval.test.js +0 -675
  195. package/src/tools/__tests__/ToolNode.recovery.test.js +0 -200
  196. package/src/tools/__tests__/ToolNode.session.test.js +0 -319
  197. package/src/tools/__tests__/ToolSearch.integration.test.js +0 -125
  198. package/src/tools/__tests__/ToolSearch.test.js +0 -812
  199. package/src/tools/__tests__/handlers.test.js +0 -799
  200. package/src/tools/__tests__/truncation-recovery.integration.test.js +0 -362
  201. package/src/tools/handlers.js +0 -306
  202. package/src/tools/schema.js +0 -25
  203. package/src/tools/search/anthropic.js +0 -34
  204. package/src/tools/search/content.js +0 -116
  205. package/src/tools/search/content.test.js +0 -133
  206. package/src/tools/search/firecrawl.js +0 -173
  207. package/src/tools/search/format.js +0 -198
  208. package/src/tools/search/highlights.js +0 -241
  209. package/src/tools/search/index.js +0 -3
  210. package/src/tools/search/jina-reranker.test.js +0 -106
  211. package/src/tools/search/rerankers.js +0 -165
  212. package/src/tools/search/schema.js +0 -102
  213. package/src/tools/search/search.js +0 -561
  214. package/src/tools/search/serper-scraper.js +0 -126
  215. package/src/tools/search/test.js +0 -129
  216. package/src/tools/search/tool.js +0 -453
  217. package/src/tools/search/types.js +0 -2
  218. package/src/tools/search/utils.js +0 -59
  219. package/src/types/graph.js +0 -24
  220. package/src/types/graph.test.js +0 -192
  221. package/src/types/index.js +0 -7
  222. package/src/types/llm.js +0 -2
  223. package/src/types/messages.js +0 -2
  224. package/src/types/run.js +0 -2
  225. package/src/types/stream.js +0 -2
  226. package/src/types/tools.js +0 -2
  227. package/src/utils/contextAnalytics.js +0 -79
  228. package/src/utils/contextAnalytics.test.js +0 -166
  229. package/src/utils/events.js +0 -26
  230. package/src/utils/graph.js +0 -11
  231. package/src/utils/handlers.js +0 -65
  232. package/src/utils/index.js +0 -10
  233. package/src/utils/llm.js +0 -21
  234. package/src/utils/llmConfig.js +0 -205
  235. package/src/utils/logging.js +0 -37
  236. package/src/utils/misc.js +0 -51
  237. package/src/utils/run.js +0 -69
  238. package/src/utils/schema.js +0 -21
  239. package/src/utils/title.js +0 -119
  240. package/src/utils/tokens.js +0 -92
  241. package/src/utils/toonFormat.js +0 -379
@@ -1,1857 +0,0 @@
1
- /* eslint-disable no-console */
2
- // src/graphs/Graph.ts
3
- import { nanoid } from 'nanoid';
4
- import { concat } from '@langchain/core/utils/stream';
5
- import { ChatVertexAI } from '@langchain/google-vertexai';
6
- import { START, END, StateGraph, Annotation, messagesStateReducer, } from '@langchain/langgraph';
7
- import { RunnableLambda, } from '@langchain/core/runnables';
8
- import { SystemMessage, AIMessageChunk, HumanMessage, } from '@langchain/core/messages';
9
- import { formatAnthropicArtifactContent, ensureThinkingBlockInMessages, convertMessagesToContent, addBedrockCacheControl, extractToolDiscoveries, modifyDeltaProperties, formatArtifactPayload, formatContentStrings, createPruneMessages, addCacheControl, getMessageId, } from '@/messages';
10
- import { GraphNodeKeys, ContentTypes, GraphEvents, Providers, StepTypes, MessageTypes, Constants, } from '@/common';
11
- import { resetIfNotEmpty, isOpenAILike, isGoogleLike, joinKeys, sleep, } from '@/utils';
12
- import { buildContextAnalytics, } from '@/utils/contextAnalytics';
13
- import { getChatModelClass, manualToolStreamProviders } from '@/llm/providers';
14
- import { ToolNode as CustomToolNode, toolsCondition } from '@/tools/ToolNode';
15
- import { ChatOpenAI, AzureChatOpenAI } from '@/llm/openai';
16
- import { safeDispatchCustomEvent } from '@/utils/events';
17
- import { createSchemaOnlyTools } from '@/tools/schema';
18
- import { prepareSchemaForProvider } from '@/schemas/validate';
19
- import { AgentContext } from '@/agents/AgentContext';
20
- import { StructuredOutputRefusalError, StructuredOutputTruncatedError, } from '@/types/graph';
21
- import { createFakeStreamingLLM } from '@/llm/fake';
22
- import { handleToolCalls } from '@/tools/handlers';
23
- import { ChatModelStreamHandler } from '@/stream';
24
- import { StreamingToolCallBuffer } from '@/tools/StreamingToolCallBuffer';
25
- const { AGENT, TOOLS } = GraphNodeKeys;
26
- export class Graph {
27
- messageStepHasToolCalls = new Map();
28
- messageIdsByStepKey = new Map();
29
- prelimMessageIdsByStepKey = new Map();
30
- config;
31
- contentData = [];
32
- stepKeyIds = new Map();
33
- contentIndexMap = new Map();
34
- toolCallStepIds = new Map();
35
- signal;
36
- /** Set of invoked tool call IDs from non-message run steps completed mid-run, if any */
37
- invokedToolIds;
38
- handlerRegistry;
39
- /**
40
- * Tool session contexts for automatic state persistence across tool invocations.
41
- * Keyed by tool name (e.g., Constants.EXECUTE_CODE).
42
- * Currently supports code execution session tracking (session_id, files).
43
- */
44
- sessions = new Map();
45
- /**
46
- * Streaming tool call buffer — accumulates raw arg strings during streaming
47
- * so that truncated tool call content can be recovered by the ToolNode.
48
- * Fed by handleToolCallChunks, consumed by ToolNode.run when args are incomplete.
49
- */
50
- streamingToolCallBuffer = new StreamingToolCallBuffer();
51
- /**
52
- * Clears heavy references to allow GC to reclaim memory held by
53
- * LangGraph's internal config / AsyncLocalStorage RunTree chain.
54
- * Call after a run completes and content has been extracted.
55
- */
56
- clearHeavyState() {
57
- this.config = undefined;
58
- this.signal = undefined;
59
- this.contentData = [];
60
- this.contentIndexMap = new Map();
61
- this.stepKeyIds = new Map();
62
- this.toolCallStepIds.clear();
63
- this.messageIdsByStepKey = new Map();
64
- this.messageStepHasToolCalls = new Map();
65
- this.prelimMessageIdsByStepKey = new Map();
66
- this.invokedToolIds = undefined;
67
- this.handlerRegistry = undefined;
68
- this.sessions.clear();
69
- this.streamingToolCallBuffer.clearAll();
70
- }
71
- }
72
- export class StandardGraph extends Graph {
73
- overrideModel;
74
- /** Optional compile options passed into workflow.compile() */
75
- compileOptions;
76
- messages = [];
77
- runId;
78
- startIndex = 0;
79
- signal;
80
- /** Map of agent contexts by agent ID */
81
- agentContexts = new Map();
82
- /** Default agent ID to use */
83
- defaultAgentId;
84
- /** Normalized finish/stop reason from the last LLM invocation */
85
- lastFinishReason;
86
- constructor({
87
- // parent-level graph inputs
88
- runId, signal, agents, tokenCounter, indexTokenCountMap, }) {
89
- super();
90
- this.runId = runId;
91
- this.signal = signal;
92
- if (agents.length === 0) {
93
- throw new Error('At least one agent configuration is required');
94
- }
95
- for (const agentConfig of agents) {
96
- const agentContext = AgentContext.fromConfig(agentConfig, tokenCounter, indexTokenCountMap);
97
- this.agentContexts.set(agentConfig.agentId, agentContext);
98
- }
99
- this.defaultAgentId = agents[0].agentId;
100
- }
101
- /* Init */
102
- resetValues(keepContent) {
103
- this.messages = [];
104
- this.lastFinishReason = undefined;
105
- this.config = resetIfNotEmpty(this.config, undefined);
106
- if (keepContent !== true) {
107
- this.contentData = resetIfNotEmpty(this.contentData, []);
108
- this.contentIndexMap = resetIfNotEmpty(this.contentIndexMap, new Map());
109
- }
110
- this.stepKeyIds = resetIfNotEmpty(this.stepKeyIds, new Map());
111
- /**
112
- * Clear in-place instead of replacing with a new Map to preserve the
113
- * shared reference held by ToolNode (passed at construction time).
114
- * Using resetIfNotEmpty would create a new Map, leaving ToolNode with
115
- * a stale reference on 2nd+ processStream calls.
116
- */
117
- this.toolCallStepIds.clear();
118
- this.messageIdsByStepKey = resetIfNotEmpty(this.messageIdsByStepKey, new Map());
119
- this.messageStepHasToolCalls = resetIfNotEmpty(this.messageStepHasToolCalls, new Map());
120
- this.prelimMessageIdsByStepKey = resetIfNotEmpty(this.prelimMessageIdsByStepKey, new Map());
121
- this.invokedToolIds = resetIfNotEmpty(this.invokedToolIds, undefined);
122
- for (const context of this.agentContexts.values()) {
123
- context.reset();
124
- }
125
- }
126
- clearHeavyState() {
127
- super.clearHeavyState();
128
- this.messages = [];
129
- this.overrideModel = undefined;
130
- for (const context of this.agentContexts.values()) {
131
- context.reset();
132
- }
133
- }
134
- /**
135
- * Returns the normalized finish/stop reason from the last LLM invocation.
136
- * Used by callers to detect when the response was truncated due to max_tokens.
137
- */
138
- getLastFinishReason() {
139
- return this.lastFinishReason;
140
- }
141
- /**
142
- * Estimates a human-friendly description of the conversation timeframe based on message count.
143
- * Uses rough heuristics to provide context about how much history is available.
144
- *
145
- * @param messageCount - Number of messages in the remaining context
146
- * @returns A friendly description like "the last few minutes", "the past hour", etc.
147
- */
148
- getContextTimeframeDescription(messageCount) {
149
- // Rough heuristics based on typical conversation patterns:
150
- // - Very active chat: ~20-30 messages per hour
151
- // - Normal chat: ~10-15 messages per hour
152
- // - Slow/thoughtful chat: ~5-8 messages per hour
153
- // We use a middle estimate of ~12 messages per hour
154
- if (messageCount <= 5) {
155
- return 'just the last few exchanges';
156
- }
157
- else if (messageCount <= 15) {
158
- return 'the last several minutes';
159
- }
160
- else if (messageCount <= 30) {
161
- return 'roughly the past hour';
162
- }
163
- else if (messageCount <= 60) {
164
- return 'the past couple of hours';
165
- }
166
- else if (messageCount <= 150) {
167
- return 'the past few hours';
168
- }
169
- else if (messageCount <= 300) {
170
- return 'roughly a day\'s worth';
171
- }
172
- else if (messageCount <= 700) {
173
- return 'the past few days';
174
- }
175
- else {
176
- return 'about a week or more';
177
- }
178
- }
179
- /* Run Step Processing */
180
- getRunStep(stepId) {
181
- const index = this.contentIndexMap.get(stepId);
182
- if (index !== undefined) {
183
- return this.contentData[index];
184
- }
185
- return undefined;
186
- }
187
- getAgentContext(metadata) {
188
- if (!metadata) {
189
- throw new Error('No metadata provided to retrieve agent context');
190
- }
191
- const currentNode = metadata.langgraph_node;
192
- if (!currentNode) {
193
- throw new Error('No langgraph_node in metadata to retrieve agent context');
194
- }
195
- let agentId;
196
- if (currentNode.startsWith(AGENT)) {
197
- agentId = currentNode.substring(AGENT.length);
198
- }
199
- else if (currentNode.startsWith(TOOLS)) {
200
- agentId = currentNode.substring(TOOLS.length);
201
- }
202
- const agentContext = this.agentContexts.get(agentId ?? '');
203
- if (!agentContext) {
204
- throw new Error(`No agent context found for agent ID ${agentId}`);
205
- }
206
- return agentContext;
207
- }
208
- getStepKey(metadata) {
209
- if (!metadata)
210
- return '';
211
- const keyList = this.getKeyList(metadata);
212
- if (this.checkKeyList(keyList)) {
213
- throw new Error('Missing metadata');
214
- }
215
- return joinKeys(keyList);
216
- }
217
- getStepIdByKey(stepKey, index) {
218
- const stepIds = this.stepKeyIds.get(stepKey);
219
- if (!stepIds) {
220
- throw new Error(`No step IDs found for stepKey ${stepKey}`);
221
- }
222
- if (index === undefined) {
223
- return stepIds[stepIds.length - 1];
224
- }
225
- return stepIds[index];
226
- }
227
- generateStepId(stepKey) {
228
- const stepIds = this.stepKeyIds.get(stepKey);
229
- let newStepId;
230
- let stepIndex = 0;
231
- if (stepIds) {
232
- stepIndex = stepIds.length;
233
- newStepId = `step_${nanoid()}`;
234
- stepIds.push(newStepId);
235
- this.stepKeyIds.set(stepKey, stepIds);
236
- }
237
- else {
238
- newStepId = `step_${nanoid()}`;
239
- this.stepKeyIds.set(stepKey, [newStepId]);
240
- }
241
- return [newStepId, stepIndex];
242
- }
243
- getKeyList(metadata) {
244
- if (!metadata)
245
- return [];
246
- const keyList = [
247
- metadata.run_id,
248
- metadata.thread_id,
249
- metadata.langgraph_node,
250
- metadata.langgraph_step,
251
- metadata.checkpoint_ns,
252
- ];
253
- const agentContext = this.getAgentContext(metadata);
254
- if (agentContext.currentTokenType === ContentTypes.THINK ||
255
- agentContext.currentTokenType === 'think_and_text') {
256
- keyList.push('reasoning');
257
- }
258
- else if (agentContext.tokenTypeSwitch === 'content') {
259
- keyList.push(`post-reasoning-${agentContext.reasoningTransitionCount}`);
260
- }
261
- if (this.invokedToolIds != null && this.invokedToolIds.size > 0) {
262
- keyList.push(this.invokedToolIds.size + '');
263
- }
264
- return keyList;
265
- }
266
- checkKeyList(keyList) {
267
- return keyList.some((key) => key === undefined);
268
- }
269
- /* Misc.*/
270
- getRunMessages() {
271
- const result = this.messages.slice(this.startIndex);
272
- console.debug(`[Graph] getRunMessages() | totalMessages=${this.messages.length} | startIndex=${this.startIndex} | runMessages=${result.length}`);
273
- return result;
274
- }
275
- getContentParts() {
276
- return convertMessagesToContent(this.messages.slice(this.startIndex));
277
- }
278
- /**
279
- * Get all run steps, optionally filtered by agent ID
280
- */
281
- getRunSteps(agentId) {
282
- if (agentId == null || agentId === '') {
283
- return [...this.contentData];
284
- }
285
- return this.contentData.filter((step) => step.agentId === agentId);
286
- }
287
- /**
288
- * Get run steps grouped by agent ID
289
- */
290
- getRunStepsByAgent() {
291
- const stepsByAgent = new Map();
292
- for (const step of this.contentData) {
293
- if (step.agentId == null || step.agentId === '')
294
- continue;
295
- const steps = stepsByAgent.get(step.agentId) ?? [];
296
- steps.push(step);
297
- stepsByAgent.set(step.agentId, steps);
298
- }
299
- return stepsByAgent;
300
- }
301
- /**
302
- * Get agent IDs that participated in this run
303
- */
304
- getActiveAgentIds() {
305
- const agentIds = new Set();
306
- for (const step of this.contentData) {
307
- if (step.agentId != null && step.agentId !== '') {
308
- agentIds.add(step.agentId);
309
- }
310
- }
311
- return Array.from(agentIds);
312
- }
313
- /**
314
- * Maps contentPart indices to agent IDs for post-run analysis
315
- * Returns a map where key is the contentPart index and value is the agentId
316
- */
317
- getContentPartAgentMap() {
318
- const contentPartAgentMap = new Map();
319
- for (const step of this.contentData) {
320
- if (step.agentId != null &&
321
- step.agentId !== '' &&
322
- Number.isFinite(step.index)) {
323
- contentPartAgentMap.set(step.index, step.agentId);
324
- }
325
- }
326
- return contentPartAgentMap;
327
- }
328
- /**
329
- * Get the context breakdown from the primary agent for admin token tracking.
330
- * Returns detailed token counts for instructions, tools, etc.
331
- */
332
- getContextBreakdown() {
333
- const primaryContext = this.agentContexts.get(this.defaultAgentId);
334
- if (!primaryContext) {
335
- return null;
336
- }
337
- return primaryContext.getContextBreakdown();
338
- }
339
- /**
340
- * Get the latest context analytics from the graph.
341
- * Returns metrics like utilization %, TOON stats, message breakdown.
342
- */
343
- getContextAnalytics() {
344
- return this.lastContextAnalytics ?? null;
345
- }
346
- /** Store the latest context analytics for retrieval after run */
347
- lastContextAnalytics = null;
348
- /* Graph */
349
- createSystemRunnable({ provider, clientOptions, instructions, additional_instructions, }) {
350
- let finalInstructions = instructions;
351
- if (additional_instructions != null && additional_instructions !== '') {
352
- finalInstructions =
353
- finalInstructions != null && finalInstructions
354
- ? `${finalInstructions}\n\n${additional_instructions}`
355
- : additional_instructions;
356
- }
357
- if (finalInstructions != null &&
358
- finalInstructions &&
359
- provider === Providers.ANTHROPIC &&
360
- clientOptions.promptCache === true) {
361
- finalInstructions = {
362
- content: [
363
- {
364
- type: 'text',
365
- text: instructions,
366
- cache_control: { type: 'ephemeral' },
367
- },
368
- ],
369
- };
370
- }
371
- if (finalInstructions != null && finalInstructions !== '') {
372
- const systemMessage = new SystemMessage(finalInstructions);
373
- return RunnableLambda.from((messages) => {
374
- return [systemMessage, ...messages];
375
- }).withConfig({ runName: 'prompt' });
376
- }
377
- }
378
- initializeTools({ currentTools, currentToolMap, agentContext, }) {
379
- const toolDefinitions = agentContext?.toolDefinitions;
380
- const eventDrivenMode = toolDefinitions != null && toolDefinitions.length > 0;
381
- // Extract HITL tool approval config from compile options (if configured)
382
- const toolApprovalConfig = this.compileOptions?.toolApprovalConfig;
383
- if (eventDrivenMode) {
384
- const schemaTools = createSchemaOnlyTools(toolDefinitions);
385
- const toolDefMap = new Map(toolDefinitions.map((def) => [def.name, def]));
386
- const graphTools = agentContext?.graphTools;
387
- const directToolNames = new Set();
388
- const allTools = [...schemaTools];
389
- const allToolMap = new Map(schemaTools.map((tool) => [tool.name, tool]));
390
- if (graphTools && graphTools.length > 0) {
391
- for (const tool of graphTools) {
392
- if ('name' in tool) {
393
- allTools.push(tool);
394
- allToolMap.set(tool.name, tool);
395
- directToolNames.add(tool.name);
396
- }
397
- }
398
- }
399
- return new CustomToolNode({
400
- tools: allTools,
401
- toolMap: allToolMap,
402
- eventDrivenMode: true,
403
- sessions: this.sessions,
404
- toolDefinitions: toolDefMap,
405
- agentId: agentContext?.agentId,
406
- toolCallStepIds: this.toolCallStepIds,
407
- toolRegistry: agentContext?.toolRegistry,
408
- directToolNames: directToolNames.size > 0 ? directToolNames : undefined,
409
- streamingToolCallBuffer: this.streamingToolCallBuffer,
410
- errorHandler: (data, metadata) => StandardGraph.handleToolCallErrorStatic(this, data, metadata),
411
- toolApprovalConfig,
412
- });
413
- }
414
- const graphTools = agentContext?.graphTools;
415
- const baseTools = currentTools ?? [];
416
- const allTraditionalTools = graphTools && graphTools.length > 0
417
- ? [...baseTools, ...graphTools]
418
- : baseTools;
419
- const traditionalToolMap = graphTools && graphTools.length > 0
420
- ? new Map([
421
- ...(currentToolMap ?? new Map()),
422
- ...graphTools
423
- .filter((t) => 'name' in t)
424
- .map((t) => [t.name, t]),
425
- ])
426
- : currentToolMap;
427
- return new CustomToolNode({
428
- tools: allTraditionalTools,
429
- toolMap: traditionalToolMap,
430
- toolCallStepIds: this.toolCallStepIds,
431
- streamingToolCallBuffer: this.streamingToolCallBuffer,
432
- errorHandler: (data, metadata) => StandardGraph.handleToolCallErrorStatic(this, data, metadata),
433
- toolRegistry: agentContext?.toolRegistry,
434
- sessions: this.sessions,
435
- toolApprovalConfig,
436
- });
437
- }
438
- initializeModel({ provider, tools, clientOptions, }) {
439
- const ChatModelClass = getChatModelClass(provider);
440
- const model = new ChatModelClass(clientOptions ?? {});
441
- if (isOpenAILike(provider) &&
442
- (model instanceof ChatOpenAI || model instanceof AzureChatOpenAI)) {
443
- model.temperature = clientOptions
444
- .temperature;
445
- model.topP = clientOptions.topP;
446
- model.frequencyPenalty = clientOptions
447
- .frequencyPenalty;
448
- model.presencePenalty = clientOptions
449
- .presencePenalty;
450
- model.n = clientOptions.n;
451
- }
452
- else if (provider === Providers.VERTEXAI &&
453
- model instanceof ChatVertexAI) {
454
- model.temperature = clientOptions
455
- .temperature;
456
- model.topP = clientOptions.topP;
457
- model.topK = clientOptions.topK;
458
- model.topLogprobs = clientOptions
459
- .topLogprobs;
460
- model.frequencyPenalty = clientOptions
461
- .frequencyPenalty;
462
- model.presencePenalty = clientOptions
463
- .presencePenalty;
464
- model.maxOutputTokens = clientOptions
465
- .maxOutputTokens;
466
- }
467
- if (!tools || tools.length === 0) {
468
- return model;
469
- }
470
- return model.bindTools(tools);
471
- }
472
- overrideTestModel(responses, sleep, toolCalls) {
473
- this.overrideModel = createFakeStreamingLLM({
474
- responses,
475
- sleep,
476
- toolCalls,
477
- });
478
- }
479
- getNewModel({ provider, clientOptions, }) {
480
- const ChatModelClass = getChatModelClass(provider);
481
- return new ChatModelClass(clientOptions ?? {});
482
- }
483
- getUsageMetadata(finalMessage) {
484
- if (finalMessage &&
485
- 'usage_metadata' in finalMessage &&
486
- finalMessage.usage_metadata != null) {
487
- return finalMessage.usage_metadata;
488
- }
489
- }
490
- /** Execute model invocation with streaming support */
491
- async attemptInvoke({ currentModel, finalMessages, provider, tools: _tools, }, config) {
492
- const model = this.overrideModel ?? currentModel;
493
- if (!model) {
494
- throw new Error('No model found');
495
- }
496
- if (model.stream) {
497
- /**
498
- * Process all model output through a local ChatModelStreamHandler in the
499
- * graph execution context. Each chunk is awaited before the next one is
500
- * consumed, so by the time the stream is exhausted every run step
501
- * (MESSAGE_CREATION, TOOL_CALLS) has been created and toolCallStepIds is
502
- * fully populated — the graph will not transition to ToolNode until this
503
- * is done.
504
- *
505
- * This replaces the previous pattern where ChatModelStreamHandler lived
506
- * in the for-await stream consumer (handler registry). That consumer
507
- * runs concurrently with graph execution, so the graph could advance to
508
- * ToolNode before the consumer had processed all events. By handling
509
- * chunks here, inside the agent node, the race is eliminated.
510
- *
511
- * The for-await consumer no longer needs a ChatModelStreamHandler; its
512
- * on_chat_model_stream events are simply ignored (no handler registered).
513
- * The dispatched custom events (ON_RUN_STEP, ON_MESSAGE_DELTA, etc.)
514
- * still reach the content aggregator and SSE handlers through the custom
515
- * event callback in Run.createCustomEventCallback.
516
- */
517
- const metadata = config?.metadata;
518
- const streamHandler = new ChatModelStreamHandler();
519
- const _streamSetupStart = Date.now();
520
- const stream = await model.stream(finalMessages, config);
521
- console.info(`[TIMING] model.stream() setup: ${Date.now() - _streamSetupStart}ms`);
522
- let finalChunk;
523
- let _ttfc = 0;
524
- const _streamStart = Date.now();
525
- for await (const chunk of stream) {
526
- if (!_ttfc) {
527
- _ttfc = Date.now() - _streamStart;
528
- console.info(`[TIMING] time-to-first-chunk: ${_ttfc}ms`);
529
- }
530
- await streamHandler.handle(GraphEvents.CHAT_MODEL_STREAM, { chunk }, metadata, this);
531
- finalChunk = finalChunk ? concat(finalChunk, chunk) : chunk;
532
- }
533
- console.info(`[TIMING] stream consumption: ${Date.now() - _streamStart}ms`);
534
- if (manualToolStreamProviders.has(provider)) {
535
- finalChunk = modifyDeltaProperties(provider, finalChunk);
536
- }
537
- if ((finalChunk?.tool_calls?.length ?? 0) > 0) {
538
- finalChunk.tool_calls = finalChunk.tool_calls?.filter((tool_call) => !!tool_call.name);
539
- }
540
- return { messages: [finalChunk] };
541
- }
542
- else {
543
- /** Fallback for models without stream support. */
544
- const finalMessage = await model.invoke(finalMessages, config);
545
- if ((finalMessage.tool_calls?.length ?? 0) > 0) {
546
- finalMessage.tool_calls = finalMessage.tool_calls?.filter((tool_call) => !!tool_call.name);
547
- }
548
- return { messages: [finalMessage] };
549
- }
550
- }
551
- /**
552
- * Execute model invocation with structured output.
553
- * Uses native constrained decoding (jsonSchema method) for supported providers,
554
- * or falls back to withStructuredOutput with functionCalling/jsonMode.
555
- *
556
- * Native mode uses provider APIs directly:
557
- * - Anthropic: output_config.format via LangChain's method: 'json_schema'
558
- * - OpenAI/Azure: response_format.json_schema via LangChain's method: 'jsonSchema'
559
- * - Bedrock: falls back to functionCalling (LangChain doesn't support native yet)
560
- */
561
- async attemptStructuredInvoke({ currentModel, finalMessages, schema, structuredOutputConfig, provider, agentContext, }, config) {
562
- const model = this.overrideModel ?? currentModel;
563
- // Check if model supports withStructuredOutput
564
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
565
- if (typeof model.withStructuredOutput !== 'function') {
566
- throw new Error('The selected model does not support structured output. ' +
567
- 'Please use a model that supports JSON schema output (e.g., OpenAI GPT-4, Anthropic Claude, Google Gemini) ' +
568
- 'or disable structured output for this agent.');
569
- }
570
- const { name = 'StructuredResponse', includeRaw: _includeRaw = false, handleErrors = true, maxRetries = 2, } = structuredOutputConfig;
571
- // Resolve the structured output method using AgentContext's provider-aware logic
572
- let method;
573
- if (agentContext) {
574
- const resolved = agentContext.resolveStructuredOutputMode();
575
- method = resolved.method;
576
- if (resolved.warnings.length > 0) {
577
- console.warn('[Graph] Structured output mode warnings:', resolved.warnings);
578
- }
579
- }
580
- else {
581
- // Legacy fallback: use the old mode-based resolution
582
- const mode = structuredOutputConfig.mode ?? 'auto';
583
- if (mode === 'tool') {
584
- method = 'functionCalling';
585
- }
586
- else if (mode === 'provider') {
587
- method =
588
- provider === Providers.BEDROCK ? 'functionCalling' : 'jsonMode';
589
- }
590
- else {
591
- method = undefined;
592
- }
593
- }
594
- // Prepare schema for provider-specific constraints when using native/jsonSchema mode
595
- let preparedSchema = schema;
596
- if (method === 'jsonSchema' && provider != null) {
597
- const { schema: prepared, warnings } = prepareSchemaForProvider(schema, provider, structuredOutputConfig.strict !== false);
598
- preparedSchema = prepared;
599
- if (warnings.length > 0) {
600
- console.warn('[Graph] Schema preparation warnings:', warnings);
601
- }
602
- }
603
- // Use withStructuredOutput to bind the schema
604
- // Always use includeRaw: true internally so we can debug what's returned
605
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
606
- const structuredModel = model.withStructuredOutput(preparedSchema, {
607
- name,
608
- method: method === 'native' ? undefined : method,
609
- includeRaw: true, // Always true internally for debugging
610
- strict: structuredOutputConfig.strict !== false,
611
- });
612
- console.debug('[Graph] Structured output config:', {
613
- name,
614
- method,
615
- provider,
616
- schemaKeys: Object.keys(preparedSchema),
617
- });
618
- let lastError;
619
- let attempts = 0;
620
- while (attempts <= maxRetries) {
621
- try {
622
- // Note: We pass the original config here. The stream aggregator will filter out
623
- // the synthetic "response" tool call events from withStructuredOutput()
624
- const result = await structuredModel.invoke(finalMessages, config);
625
- // Check for refusal or truncation in the raw message
626
- if (result?.raw != null) {
627
- const rawMsg = result.raw;
628
- // Check stop reason for refusal or truncation
629
- const responseMetadata = rawMsg.response_metadata;
630
- const stopReason = responseMetadata.stop_reason ?? // Anthropic
631
- responseMetadata.finish_reason ?? // OpenAI
632
- responseMetadata.stopReason; // Bedrock
633
- if (stopReason === 'max_tokens' || stopReason === 'length') {
634
- throw new StructuredOutputTruncatedError(stopReason);
635
- }
636
- // Check for Anthropic refusal (stop_reason won't be 'refusal' but content may indicate it)
637
- // OpenAI uses message.refusal field
638
- const refusal = rawMsg
639
- .refusal;
640
- if (refusal != null && refusal !== '') {
641
- throw new StructuredOutputRefusalError(refusal);
642
- }
643
- }
644
- // Handle response - we always use includeRaw internally
645
- if (result?.raw != null && result?.parsed !== undefined) {
646
- return {
647
- structuredResponse: result.parsed,
648
- rawMessage: result.raw,
649
- };
650
- }
651
- // Fallback for models that don't support includeRaw
652
- return {
653
- structuredResponse: result,
654
- };
655
- }
656
- catch (error) {
657
- // Don't retry on refusal or truncation errors — they need user action
658
- if (error instanceof StructuredOutputRefusalError ||
659
- error instanceof StructuredOutputTruncatedError) {
660
- throw error;
661
- }
662
- lastError = error;
663
- attempts++;
664
- // If error handling is disabled, throw immediately
665
- if (handleErrors === false) {
666
- throw error;
667
- }
668
- // If we've exhausted retries, throw
669
- if (attempts > maxRetries) {
670
- throw new Error(`Structured output failed after ${maxRetries + 1} attempts: ${lastError.message}`);
671
- }
672
- // Add error message to conversation for retry
673
- const errorMessage = typeof handleErrors === 'string'
674
- ? handleErrors
675
- : `The response did not match the expected schema. Error: ${lastError.message}. Please try again with a valid response.`;
676
- console.warn(`[Graph] Structured output attempt ${attempts} failed: ${lastError.message}. Retrying...`);
677
- // Add the error as a human message for context
678
- finalMessages = [
679
- ...finalMessages,
680
- new HumanMessage({
681
- content: `[VALIDATION ERROR]\n${errorMessage}`,
682
- }),
683
- ];
684
- }
685
- }
686
- throw lastError ?? new Error('Structured output failed');
687
- }
688
- cleanupSignalListener(currentModel) {
689
- if (!this.signal) {
690
- return;
691
- }
692
- const model = this.overrideModel ?? currentModel;
693
- if (!model) {
694
- return;
695
- }
696
- const client = model?.exposedClient;
697
- if (!client?.abortHandler) {
698
- return;
699
- }
700
- this.signal.removeEventListener('abort', client.abortHandler);
701
- client.abortHandler = undefined;
702
- }
703
- /**
704
- * Perform structured output invocation: creates a fresh model without tools bound,
705
- * removes thinking configuration, invokes with the schema, emits the event,
706
- * and returns a clean AIMessageChunk without tool_calls.
707
- *
708
- * Used by both the immediate path (no tools) and the deferred path (after tool use).
709
- */
710
- async performStructuredOutput({ agentContext, finalMessages, config, }) {
711
- const schema = agentContext.getStructuredOutputSchema();
712
- if (!schema) {
713
- throw new Error('Structured output schema is not configured');
714
- }
715
- // Get a fresh model WITHOUT tools bound
716
- // bindTools() returns RunnableBinding which lacks withStructuredOutput
717
- // Also disable thinking mode - Anthropic/Bedrock doesn't allow tool_choice with thinking enabled
718
- const structuredClientOptions = {
719
- ...agentContext.clientOptions,
720
- };
721
- // Determine if streaming is possible for this structured output mode
722
- // Native/jsonSchema modes can stream; tool/functionCalling modes cannot (synthetic tool calls break UX)
723
- const resolved = agentContext.resolveStructuredOutputMode();
724
- const canStream = resolved.method === 'jsonSchema' || resolved.method === 'jsonMode';
725
- if (!canStream) {
726
- // Disable streaming for function calling mode (synthetic tool calls break streaming UX)
727
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
728
- structuredClientOptions.streaming = false;
729
- }
730
- // For native/jsonSchema mode, Anthropic's constrained decoding works with thinking enabled
731
- // (grammar only applies to final output, not thinking blocks). For function calling mode,
732
- // thinking must be disabled because forced tool_choice is incompatible with thinking.
733
- const needsThinkingDisabled = resolved.method !== 'jsonSchema';
734
- if (needsThinkingDisabled) {
735
- // Remove thinking configuration for Bedrock
736
- if (agentContext.provider === Providers.BEDROCK) {
737
- const bedrockOpts = structuredClientOptions;
738
- if (bedrockOpts.additionalModelRequestFields != null) {
739
- const additionalFields = Object.assign({}, bedrockOpts.additionalModelRequestFields);
740
- delete additionalFields.thinking;
741
- delete additionalFields.budgetTokens;
742
- bedrockOpts.additionalModelRequestFields =
743
- additionalFields;
744
- }
745
- }
746
- // Remove thinking configuration for Anthropic direct API
747
- if (agentContext.provider === Providers.ANTHROPIC) {
748
- const anthropicOpts = structuredClientOptions;
749
- if (anthropicOpts.thinking) {
750
- delete anthropicOpts.thinking;
751
- }
752
- }
753
- }
754
- const structuredModel = this.getNewModel({
755
- provider: agentContext.provider,
756
- clientOptions: structuredClientOptions,
757
- });
758
- const { structuredResponse, rawMessage } = await this.attemptStructuredInvoke({
759
- currentModel: structuredModel,
760
- finalMessages,
761
- schema,
762
- structuredOutputConfig: agentContext.structuredOutput,
763
- provider: agentContext.provider,
764
- agentContext,
765
- }, config);
766
- // Emit structured output event
767
- await safeDispatchCustomEvent(GraphEvents.ON_STRUCTURED_OUTPUT, {
768
- structuredResponse,
769
- schema,
770
- raw: rawMessage,
771
- }, config);
772
- // Create a clean message WITHOUT tool_calls for structured output.
773
- // The rawMessage contains a tool_call for the structured output schema (e.g., "response"),
774
- // which would cause the graph router to send it to the tool node.
775
- // We return a clean AI message that ends the graph.
776
- let cleanMessage;
777
- if (rawMessage) {
778
- cleanMessage = new AIMessageChunk({
779
- content: JSON.stringify(structuredResponse, null, 2),
780
- id: rawMessage.id,
781
- response_metadata: rawMessage.response_metadata,
782
- usage_metadata: rawMessage.usage_metadata,
783
- });
784
- }
785
- return {
786
- messages: cleanMessage ? [cleanMessage] : [],
787
- structuredResponse,
788
- };
789
- }
790
- createCallModel(agentId = 'default') {
791
- return async (state, config) => {
792
- const _agentNodeStart = Date.now();
793
- /**
794
- * Get agent context - it must exist by this point
795
- */
796
- const agentContext = this.agentContexts.get(agentId);
797
- if (!agentContext) {
798
- throw new Error(`Agent context not found for agentId: ${agentId}`);
799
- }
800
- if (!config) {
801
- throw new Error('No config provided');
802
- }
803
- let { messages } = state;
804
- // CACHE OPTIMIZATION: Inject dynamicContext as a HumanMessage at the start of conversation
805
- // This keeps the system message static (cacheable) while providing dynamic context
806
- // (timestamps, user info, tool context) as conversation content instead.
807
- // Only inject on the first turn when messages don't already have the context marker.
808
- if (agentContext.dynamicContext != null &&
809
- agentContext.dynamicContext !== '' &&
810
- messages.length > 0 &&
811
- !messages.some((m) => m instanceof HumanMessage &&
812
- typeof m.content === 'string' &&
813
- m.content.startsWith('[SESSION_CONTEXT]'))) {
814
- const dynamicContextMessage = new HumanMessage({
815
- content: `[SESSION_CONTEXT]\n${agentContext.dynamicContext}`,
816
- });
817
- const ackMessage = new AIMessageChunk({
818
- content: 'Understood. I have noted the session context including the current date/time (CST) and will apply it appropriately.',
819
- });
820
- messages = [dynamicContextMessage, ackMessage, ...messages];
821
- }
822
- // Extract tool discoveries from current turn only (similar to formatArtifactPayload pattern)
823
- const discoveredNames = extractToolDiscoveries(messages);
824
- if (discoveredNames.length > 0) {
825
- agentContext.markToolsAsDiscovered(discoveredNames);
826
- }
827
- const toolsForBinding = agentContext.getToolsForBinding();
828
- const _t1 = Date.now();
829
- let model = this.overrideModel ??
830
- this.initializeModel({
831
- tools: toolsForBinding,
832
- provider: agentContext.provider,
833
- clientOptions: agentContext.clientOptions,
834
- });
835
- console.info(`[TIMING] initializeModel: ${Date.now() - _t1}ms`);
836
- if (agentContext.systemRunnable) {
837
- const _t2 = Date.now();
838
- model = agentContext.systemRunnable.pipe(model);
839
- console.info(`[TIMING] systemRunnable.pipe: ${Date.now() - _t2}ms`);
840
- }
841
- if (agentContext.tokenCalculationPromise) {
842
- const _t3 = Date.now();
843
- await agentContext.tokenCalculationPromise;
844
- console.info(`[TIMING] tokenCalculationPromise: ${Date.now() - _t3}ms`);
845
- }
846
- if (!config.signal) {
847
- config.signal = this.signal;
848
- }
849
- this.config = config;
850
- let messagesToUse = messages;
851
- // ====================================================================
852
- // PRE-PRUNING DELEGATION CHECK
853
- // Before pruning strips messages (losing context), check if we should
854
- // delegate instead. If context would be pruned AND the agent has the
855
- // task tool, inject a delegation hint and SKIP pruning — preserving
856
- // the content for the LLM to understand what to delegate.
857
- // ====================================================================
858
- console.info(`[TIMING] pre-processing: ${Date.now() - _agentNodeStart}ms (from agent node start)`);
859
- const _t4 = Date.now();
860
- let delegationInjectedPrePrune = false;
861
- const hasTaskToolPrePrune = agentContext.tools?.some((tool) => {
862
- const toolName = typeof tool === 'object' && 'name' in tool
863
- ? tool.name
864
- : '';
865
- return toolName === 'task';
866
- });
867
- if (hasTaskToolPrePrune === true &&
868
- agentContext.tokenCounter &&
869
- agentContext.maxContextTokens != null) {
870
- // Estimate total tokens in messages BEFORE pruning
871
- let prePruneTokens = 0;
872
- for (const msg of messages) {
873
- prePruneTokens += agentContext.tokenCounter(msg);
874
- }
875
- // Add instruction tokens (system prompt)
876
- prePruneTokens += agentContext.instructionTokens;
877
- const prePruneUtilization = (prePruneTokens / agentContext.maxContextTokens) * 100;
878
- if (prePruneUtilization > 70) {
879
- console.warn(`[Graph] PRE-PRUNE delegation check: ${prePruneUtilization.toFixed(1)}% utilization ` +
880
- `(${prePruneTokens}/${agentContext.maxContextTokens} tokens). ` +
881
- 'Injecting delegation hint INSTEAD of pruning.');
882
- delegationInjectedPrePrune = true;
883
- }
884
- }
885
- if (!agentContext.pruneMessages &&
886
- agentContext.tokenCounter &&
887
- agentContext.maxContextTokens != null &&
888
- agentContext.indexTokenCountMap[0] != null) {
889
- const isAnthropicWithThinking = (agentContext.provider === Providers.ANTHROPIC &&
890
- agentContext.clientOptions.thinking !=
891
- null) ||
892
- (agentContext.provider === Providers.BEDROCK &&
893
- agentContext.clientOptions
894
- .additionalModelRequestFields?.['thinking'] != null) ||
895
- (agentContext.provider === Providers.OPENAI &&
896
- agentContext.clientOptions.modelKwargs
897
- ?.thinking?.type === 'enabled');
898
- agentContext.pruneMessages = createPruneMessages({
899
- startIndex: this.startIndex,
900
- provider: agentContext.provider,
901
- tokenCounter: agentContext.tokenCounter,
902
- maxTokens: agentContext.maxContextTokens,
903
- thinkingEnabled: isAnthropicWithThinking,
904
- indexTokenCountMap: agentContext.indexTokenCountMap,
905
- });
906
- }
907
- // Inject persisted summary from previous turns (loaded from MongoDB/Redis by Ranger)
908
- if (agentContext.persistedSummary && !messages.some((m) => typeof m.content === 'string' && m.content.startsWith('[Conversation Summary]'))) {
909
- const summaryMsg = new SystemMessage(`[Conversation Summary]\n${agentContext.persistedSummary}`);
910
- const systemIdx = messages[0]?.getType() === 'system' ? 1 : 0;
911
- messages = [
912
- ...messages.slice(0, systemIdx),
913
- summaryMsg,
914
- ...messages.slice(systemIdx),
915
- ];
916
- console.info(`[E2E:Summary][Graph] Persisted summary INJECTED at index ${systemIdx} | len=${agentContext.persistedSummary.length} | msgCount=${messages.length} | preview="${agentContext.persistedSummary.substring(0, 80)}..."`);
917
- }
918
- if (agentContext.pruneMessages && !delegationInjectedPrePrune) {
919
- console.info(`[E2E:Summary][Graph] Pruning START | inputCount=${messages.length} | maxTokens=${agentContext.maxContextTokens} | hasSummarizeCallback=${!!agentContext.summarizeCallback}`);
920
- const { context, indexTokenCountMap, messagesToRefine } = agentContext.pruneMessages({
921
- messages,
922
- usageMetadata: agentContext.currentUsage,
923
- // startOnMessageType: 'human',
924
- });
925
- agentContext.indexTokenCountMap = indexTokenCountMap;
926
- messagesToUse = context;
927
- console.info(`[E2E:Summary][Graph] Pruned | kept=${context.length} | discarded=${messagesToRefine.length} | originalCount=${messages.length}`);
928
- // Summarize discarded messages if callback provided
929
- if (messagesToRefine.length > 0 && agentContext.summarizeCallback) {
930
- console.info(`[E2E:Summary][Graph] Summarizing ${messagesToRefine.length} discarded messages via callback...`);
931
- try {
932
- const summary = await agentContext.summarizeCallback(messagesToRefine);
933
- console.info(`[E2E:Summary][Graph] Summary RECEIVED from callback | len=${summary?.length ?? 0} | hasContent=${summary != null && summary !== ''}`);
934
- if (summary != null && summary !== '') {
935
- const summaryMsg = new SystemMessage(`[Conversation Summary]\n${summary}`);
936
- // Remove any existing summary message to avoid duplicates
937
- messagesToUse = messagesToUse.filter((m) => !(typeof m.content === 'string' && m.content.startsWith('[Conversation Summary]')));
938
- const systemIdx = messagesToUse[0]?.getType() === 'system' ? 1 : 0;
939
- messagesToUse = [
940
- ...messagesToUse.slice(0, systemIdx),
941
- summaryMsg,
942
- ...messagesToUse.slice(systemIdx),
943
- ];
944
- console.info(`[E2E:Summary][Graph] New summary INJECTED at index ${systemIdx} | finalMsgCount=${messagesToUse.length} | preview="${summary.substring(0, 80)}..."`);
945
- }
946
- }
947
- catch (err) {
948
- console.error('[Graph] Summarization callback failed:', err);
949
- }
950
- }
951
- }
952
- else if (delegationInjectedPrePrune) {
953
- console.info('[Graph] Skipping pruning — delegation will handle context pressure');
954
- }
955
- let finalMessages = messagesToUse;
956
- if (agentContext.useLegacyContent) {
957
- finalMessages = formatContentStrings(finalMessages);
958
- }
959
- const lastMessageX = finalMessages.length >= 2
960
- ? finalMessages[finalMessages.length - 2]
961
- : null;
962
- const lastMessageY = finalMessages.length >= 1
963
- ? finalMessages[finalMessages.length - 1]
964
- : null;
965
- if (agentContext.provider === Providers.BEDROCK &&
966
- lastMessageX instanceof AIMessageChunk &&
967
- lastMessageY?.getType() === MessageTypes.TOOL &&
968
- typeof lastMessageX.content === 'string') {
969
- finalMessages[finalMessages.length - 2].content = '';
970
- }
971
- // Use getType() instead of instanceof to avoid module mismatch issues
972
- const isLatestToolMessage = lastMessageY?.getType() === MessageTypes.TOOL;
973
- if (isLatestToolMessage &&
974
- agentContext.provider === Providers.ANTHROPIC) {
975
- formatAnthropicArtifactContent(finalMessages);
976
- }
977
- else if (isLatestToolMessage &&
978
- ((isOpenAILike(agentContext.provider) &&
979
- agentContext.provider !== Providers.DEEPSEEK) ||
980
- isGoogleLike(agentContext.provider))) {
981
- formatArtifactPayload(finalMessages);
982
- }
983
- /**
984
- * Handle edge case: when switching from a non-thinking agent to a thinking-enabled agent,
985
- * convert AI messages with tool calls to HumanMessages to avoid thinking block requirements.
986
- * This is required by Anthropic/Bedrock when thinking is enabled.
987
- *
988
- * IMPORTANT: This MUST happen BEFORE cache control is applied.
989
- * If we add cachePoint to an AI message first, then convert that AI message to a HumanMessage,
990
- * the cachePoint is lost. By converting first, we ensure cache control is applied to the
991
- * final message structure that will be sent to the API.
992
- */
993
- const isAnthropicWithThinking = (agentContext.provider === Providers.ANTHROPIC &&
994
- agentContext.clientOptions.thinking !=
995
- null) ||
996
- (agentContext.provider === Providers.BEDROCK &&
997
- agentContext.clientOptions
998
- .additionalModelRequestFields?.['thinking'] != null);
999
- if (isAnthropicWithThinking) {
1000
- finalMessages = ensureThinkingBlockInMessages(finalMessages, agentContext.provider);
1001
- }
1002
- // Apply cache control AFTER thinking block handling to ensure cachePoints aren't lost
1003
- // when AI messages are converted to HumanMessages
1004
- if (agentContext.provider === Providers.ANTHROPIC) {
1005
- const anthropicOptions = agentContext.clientOptions;
1006
- if (anthropicOptions?.promptCache === true) {
1007
- finalMessages = addCacheControl(finalMessages);
1008
- }
1009
- }
1010
- else if (agentContext.provider === Providers.BEDROCK) {
1011
- const bedrockOptions = agentContext.clientOptions;
1012
- // Both Claude and Nova models support cachePoint in system and messages
1013
- // (Llama, Titan, and other models do NOT support cachePoint)
1014
- const modelId = bedrockOptions?.model?.toLowerCase() ?? '';
1015
- const supportsCaching = modelId.includes('claude') ||
1016
- modelId.includes('anthropic') ||
1017
- modelId.includes('nova');
1018
- if (bedrockOptions?.promptCache === true && supportsCaching) {
1019
- finalMessages = addBedrockCacheControl(finalMessages);
1020
- }
1021
- }
1022
- if (agentContext.lastStreamCall != null &&
1023
- agentContext.streamBuffer != null) {
1024
- const timeSinceLastCall = Date.now() - agentContext.lastStreamCall;
1025
- if (timeSinceLastCall < agentContext.streamBuffer) {
1026
- const timeToWait = Math.ceil((agentContext.streamBuffer - timeSinceLastCall) / 1000) *
1027
- 1000;
1028
- await sleep(timeToWait);
1029
- }
1030
- }
1031
- agentContext.lastStreamCall = Date.now();
1032
- let result;
1033
- const fallbacks = agentContext.clientOptions?.fallbacks ??
1034
- [];
1035
- if (finalMessages.length === 0) {
1036
- throw new Error(JSON.stringify({
1037
- type: 'empty_messages',
1038
- info: 'Message pruning removed all messages as none fit in the context window. Please increase the context window size or make your message shorter.',
1039
- }));
1040
- }
1041
- // Get model info for analytics
1042
- const bedrockOpts = agentContext.clientOptions;
1043
- const modelId = bedrockOpts?.model ??
1044
- agentContext.clientOptions
1045
- ?.modelName;
1046
- const thinkingConfig = bedrockOpts?.additionalModelRequestFields?.['thinking'] ??
1047
- agentContext.clientOptions
1048
- ?.thinking;
1049
- console.info(`[TIMING] pruning+formatting: ${Date.now() - _t4}ms`);
1050
- const _t5 = Date.now();
1051
- // Build and emit context analytics for traces
1052
- const contextAnalytics = buildContextAnalytics(finalMessages, {
1053
- tokenCounter: agentContext.tokenCounter,
1054
- maxContextTokens: agentContext.maxContextTokens,
1055
- instructionTokens: agentContext.instructionTokens,
1056
- indexTokenCountMap: agentContext.indexTokenCountMap,
1057
- });
1058
- console.info(`[TIMING] buildContextAnalytics: ${Date.now() - _t5}ms`);
1059
- // Store for retrieval via getContextAnalytics() after run completes
1060
- this.lastContextAnalytics = contextAnalytics;
1061
- await safeDispatchCustomEvent(GraphEvents.ON_CONTEXT_ANALYTICS, {
1062
- provider: agentContext.provider,
1063
- model: modelId,
1064
- thinkingEnabled: thinkingConfig != null,
1065
- cacheEnabled: bedrockOpts?.promptCache === true,
1066
- analytics: contextAnalytics,
1067
- }, config);
1068
- // ====================================================================
1069
- // CONTEXT PRESSURE AWARENESS — Intelligent Sub-Agent Delegation
1070
- //
1071
- // Two triggers for delegation hints:
1072
- // 1. DOCUMENT COUNT: When 3+ documents are detected in the conversation,
1073
- // inject a delegation hint on the FIRST iteration (before the LLM
1074
- // has called any tools). This ensures the agent delegates upfront
1075
- // rather than trying to process all documents itself.
1076
- // 2. TOKEN UTILIZATION: At EVERY iteration, if context is filling up
1077
- // (70%/85%), inject escalating hints to delegate remaining work.
1078
- //
1079
- // This runs mid-chain — so even if tool responses push context up
1080
- // after the first LLM call, subsequent iterations get the hint.
1081
- // ====================================================================
1082
- const hasTaskToolInContext = agentContext.tools?.some((tool) => {
1083
- const toolName = typeof tool === 'object' && 'name' in tool
1084
- ? tool.name
1085
- : '';
1086
- return toolName === 'task';
1087
- });
1088
- if (hasTaskToolInContext === true &&
1089
- contextAnalytics.utilizationPercent != null &&
1090
- contextAnalytics.maxContextTokens != null) {
1091
- const utilization = contextAnalytics.utilizationPercent;
1092
- const totalTokens = contextAnalytics.totalTokens;
1093
- const maxTokens = contextAnalytics.maxContextTokens;
1094
- const remainingTokens = maxTokens - totalTokens;
1095
- // Count attached documents by scanning for document patterns in HumanMessages:
1096
- // 1. # "filename" headers in "Attached document(s):" blocks (text content)
1097
- // 2. **filename1, filename2** in "The user has attached:" blocks (embedded files)
1098
- // 3. Filenames in file_search tool results
1099
- let documentCount = 0;
1100
- const documentNames = [];
1101
- for (const msg of finalMessages) {
1102
- const content = typeof msg.content === 'string'
1103
- ? msg.content
1104
- : Array.isArray(msg.content)
1105
- ? msg.content
1106
- .map((p) => {
1107
- const part = p;
1108
- return String(part.text ?? part.content ?? '');
1109
- })
1110
- .join(' ')
1111
- : '';
1112
- // Pattern 1: # "filename" headers in attached document blocks
1113
- const docMatches = content.match(/# "([^"]+)"/g);
1114
- if (docMatches) {
1115
- for (const match of docMatches) {
1116
- const name = match.replace(/# "/, '').replace(/"$/, '');
1117
- if (!documentNames.includes(name)) {
1118
- documentNames.push(name);
1119
- documentCount++;
1120
- }
1121
- }
1122
- }
1123
- // Pattern 2: "The user has attached: **file1, file2**" (embedded files)
1124
- const attachedMatch = content.match(/user has attached:\s*\*\*([^*]+)\*\*/i);
1125
- if (attachedMatch) {
1126
- const names = attachedMatch[1]
1127
- .split(',')
1128
- .map((n) => n.trim())
1129
- .filter(Boolean);
1130
- for (const name of names) {
1131
- if (!documentNames.includes(name)) {
1132
- documentNames.push(name);
1133
- documentCount++;
1134
- }
1135
- }
1136
- }
1137
- }
1138
- // BASELINE LOG: Always fires so we can verify this code path runs
1139
- console.info(`[Graph] Context utilization: ${utilization.toFixed(1)}% ` +
1140
- `(${totalTokens}/${maxTokens} tokens, ${remainingTokens} remaining) | ` +
1141
- `hasTaskTool: true | messages: ${finalMessages.length} | docs: ${documentCount}`);
1142
- // TRIGGER 1: Multi-document delegation (3+ documents detected)
1143
- // Only inject on first iteration (no AI messages yet = agent hasn't responded)
1144
- const hasAiResponse = finalMessages.some((m) => m._getType() === 'ai' || m._getType() === 'tool');
1145
- if (documentCount >= 3 && !hasAiResponse) {
1146
- const pressureMsg = new HumanMessage({
1147
- content: `[MULTI-DOCUMENT PROCESSING — ${documentCount} documents detected]\n` +
1148
- `Documents: ${documentNames.join(', ')}\n\n` +
1149
- `You have ${documentCount} documents attached. For thorough analysis, use the "task" tool ` +
1150
- 'to delegate each document (or group of related documents) to a sub-agent.\n' +
1151
- 'Each sub-agent has its own fresh context window and can use file_search to retrieve the full document content.\n' +
1152
- 'After all sub-agents complete, synthesize their results into a comprehensive response.\n\n' +
1153
- 'This approach ensures each document gets full attention without context limitations.',
1154
- });
1155
- finalMessages = [...finalMessages, pressureMsg];
1156
- console.info(`[Graph] Multi-document delegation hint injected for ${documentCount} documents: ` +
1157
- `${documentNames.join(', ')}`);
1158
- }
1159
- // TRIGGER 2: Token utilization thresholds (mid-chain safety net)
1160
- // Also fires when we skipped pruning due to delegationInjectedPrePrune
1161
- if (utilization > 85 ||
1162
- (delegationInjectedPrePrune && utilization > 50)) {
1163
- // CRITICAL: Context is high — MANDATE delegation
1164
- const pressureMsg = new HumanMessage({
1165
- content: `[CONTEXT BUDGET CRITICAL — ${utilization.toFixed(0)}% used]\n` +
1166
- `You have used ${totalTokens} of ${maxTokens} tokens (${remainingTokens} remaining).\n` +
1167
- 'Your context is very large. You MUST use the "task" tool to delegate work to sub-agents.\n' +
1168
- 'Each sub-agent runs in its own fresh context window and can use file_search to access documents.\n' +
1169
- 'Do NOT attempt to process documents directly — delegate each document to a sub-agent, then synthesize results.',
1170
- });
1171
- finalMessages = [...finalMessages, pressureMsg];
1172
- console.warn(`[Graph] Context pressure CRITICAL (${utilization.toFixed(0)}%): ` +
1173
- `Injected mandatory delegation hint. ${remainingTokens} tokens remaining. ` +
1174
- `prePruneSkipped: ${delegationInjectedPrePrune}`);
1175
- }
1176
- else if (utilization > 70) {
1177
- // WARNING: Context filling up — suggest delegation
1178
- const pressureMsg = new HumanMessage({
1179
- content: `[CONTEXT BUDGET WARNING — ${utilization.toFixed(0)}% used]\n` +
1180
- `You have used ${totalTokens} of ${maxTokens} tokens (${remainingTokens} remaining).\n` +
1181
- 'Your context is filling up. Consider using the "task" tool to delegate complex operations to sub-agents.\n' +
1182
- 'Sub-agents run in fresh context windows and won\'t consume your remaining budget.',
1183
- });
1184
- finalMessages = [...finalMessages, pressureMsg];
1185
- console.info(`[Graph] Context pressure WARNING (${utilization.toFixed(0)}%): ` +
1186
- `Injected delegation suggestion. ${remainingTokens} tokens remaining.`);
1187
- }
1188
- }
1189
- // Structured output mode: when the agent has NO tools, produce structured JSON immediately.
1190
- // When the agent HAS tools, we defer structured output until after tool use completes
1191
- // (see the deferred structured output block after attemptInvoke below).
1192
- const hasTools = (toolsForBinding?.length ?? 0) > 0;
1193
- if (agentContext.isStructuredOutputMode &&
1194
- agentContext.structuredOutput &&
1195
- !hasTools) {
1196
- try {
1197
- const structuredResult = await this.performStructuredOutput({
1198
- agentContext,
1199
- finalMessages,
1200
- config,
1201
- });
1202
- agentContext.currentUsage = this.getUsageMetadata(structuredResult.messages?.[0]);
1203
- this.cleanupSignalListener();
1204
- return structuredResult;
1205
- }
1206
- catch (structuredError) {
1207
- console.error('[Graph] Structured output failed:', structuredError);
1208
- throw structuredError;
1209
- }
1210
- }
1211
- console.info(`[TIMING] total pre-LLM overhead: ${Date.now() - _agentNodeStart}ms`);
1212
- const _t6 = Date.now();
1213
- try {
1214
- result = await this.attemptInvoke({
1215
- currentModel: model,
1216
- finalMessages,
1217
- provider: agentContext.provider,
1218
- tools: agentContext.tools,
1219
- }, config);
1220
- console.info(`[TIMING] attemptInvoke (LLM call): ${Date.now() - _t6}ms`);
1221
- console.info(`[TIMING] TOTAL agent node: ${Date.now() - _agentNodeStart}ms`);
1222
- }
1223
- catch (primaryError) {
1224
- console.info(`[TIMING] attemptInvoke FAILED after: ${Date.now() - _t6}ms`);
1225
- // Check if this is a "input too long" error from Bedrock/Anthropic
1226
- const errorMessage = primaryError.message.toLowerCase();
1227
- const isInputTooLongError = errorMessage.includes('too long') ||
1228
- errorMessage.includes('input is too long') ||
1229
- errorMessage.includes('context length') ||
1230
- errorMessage.includes('maximum context') ||
1231
- errorMessage.includes('validationexception') ||
1232
- errorMessage.includes('prompt is too long');
1233
- // Log when we detect the error
1234
- if (isInputTooLongError) {
1235
- console.warn('[Graph] Detected input too long error:', errorMessage.substring(0, 200));
1236
- console.warn('[Graph] Checking emergency pruning conditions:', {
1237
- hasPruneMessages: !!agentContext.pruneMessages,
1238
- hasTokenCounter: !!agentContext.tokenCounter,
1239
- maxContextTokens: agentContext.maxContextTokens,
1240
- indexTokenMapKeys: Object.keys(agentContext.indexTokenCountMap)
1241
- .length,
1242
- });
1243
- }
1244
- // If input too long and we have pruning capability OR tokenCounter, retry with progressively more aggressive pruning
1245
- // Note: We can create emergency pruneMessages dynamically if we have tokenCounter and maxContextTokens
1246
- const canPrune = agentContext.tokenCounter != null &&
1247
- agentContext.maxContextTokens != null &&
1248
- agentContext.maxContextTokens > 0;
1249
- if (isInputTooLongError && canPrune) {
1250
- // Progressive reduction: 50% -> 25% -> 10% of original context
1251
- const reductionLevels = [0.5, 0.25, 0.1];
1252
- for (const reductionFactor of reductionLevels) {
1253
- if (result)
1254
- break; // Exit if we got a result
1255
- const reducedMaxTokens = Math.floor(agentContext.maxContextTokens * reductionFactor);
1256
- console.warn(`[Graph] Input too long. Retrying with ${reductionFactor * 100}% context (${reducedMaxTokens} tokens)...`);
1257
- // Build fresh indexTokenCountMap if missing/incomplete
1258
- // This is needed when messages were dynamically added without updating the token map
1259
- let tokenMapForPruning = agentContext.indexTokenCountMap;
1260
- if (Object.keys(tokenMapForPruning).length < messages.length) {
1261
- console.warn('[Graph] Building fresh token count map for emergency pruning...');
1262
- tokenMapForPruning = {};
1263
- for (let i = 0; i < messages.length; i++) {
1264
- tokenMapForPruning[i] = agentContext.tokenCounter(messages[i]);
1265
- }
1266
- }
1267
- const emergencyPrune = createPruneMessages({
1268
- startIndex: this.startIndex,
1269
- provider: agentContext.provider,
1270
- tokenCounter: agentContext.tokenCounter,
1271
- maxTokens: reducedMaxTokens,
1272
- thinkingEnabled: false, // Disable thinking for emergency prune
1273
- indexTokenCountMap: tokenMapForPruning,
1274
- });
1275
- const { context: reducedMessages } = emergencyPrune({
1276
- messages,
1277
- usageMetadata: agentContext.currentUsage,
1278
- });
1279
- // Skip if we can't fit any messages
1280
- if (reducedMessages.length === 0) {
1281
- console.warn(`[Graph] Cannot fit any messages at ${reductionFactor * 100}% reduction, trying next level...`);
1282
- continue;
1283
- }
1284
- // Calculate how many messages were pruned and estimate context timeframe
1285
- const prunedCount = finalMessages.length - reducedMessages.length;
1286
- const remainingCount = reducedMessages.length;
1287
- const estimatedContextDescription = this.getContextTimeframeDescription(remainingCount);
1288
- // Inject a personalized context message to inform the agent about pruning
1289
- const pruneNoticeMessage = new HumanMessage({
1290
- content: `[CONTEXT NOTICE]
1291
- Our conversation has grown quite long, so I've focused on ${estimatedContextDescription} of our chat (${remainingCount} recent messages). ${prunedCount} earlier messages are no longer in my immediate memory.
1292
-
1293
- If I seem to be missing something we discussed earlier, just give me a quick reminder and I'll pick right back up! I'm still fully engaged and ready to help with whatever you need.`,
1294
- });
1295
- // Insert the notice after the system message (if any) but before conversation
1296
- const hasSystemMessage = reducedMessages[0]?.getType() === 'system';
1297
- const insertIndex = hasSystemMessage ? 1 : 0;
1298
- // Create new array with the pruning notice
1299
- const messagesWithNotice = [
1300
- ...reducedMessages.slice(0, insertIndex),
1301
- pruneNoticeMessage,
1302
- ...reducedMessages.slice(insertIndex),
1303
- ];
1304
- let retryMessages = agentContext.useLegacyContent
1305
- ? formatContentStrings(messagesWithNotice)
1306
- : messagesWithNotice;
1307
- // Apply thinking block handling first (before cache control)
1308
- // This ensures AI+Tool sequences are converted to HumanMessages
1309
- // before we add cache points that could be lost in the conversion
1310
- if (isAnthropicWithThinking) {
1311
- retryMessages = ensureThinkingBlockInMessages(retryMessages, agentContext.provider);
1312
- }
1313
- // Apply Bedrock cache control if needed (after thinking block handling)
1314
- if (agentContext.provider === Providers.BEDROCK) {
1315
- const bedrockOptions = agentContext.clientOptions;
1316
- const modelId = bedrockOptions?.model?.toLowerCase() ?? '';
1317
- const supportsCaching = modelId.includes('claude') ||
1318
- modelId.includes('anthropic') ||
1319
- modelId.includes('nova');
1320
- if (bedrockOptions?.promptCache === true && supportsCaching) {
1321
- retryMessages =
1322
- addBedrockCacheControl(retryMessages);
1323
- }
1324
- }
1325
- try {
1326
- result = await this.attemptInvoke({
1327
- currentModel: model,
1328
- finalMessages: retryMessages,
1329
- provider: agentContext.provider,
1330
- tools: agentContext.tools,
1331
- }, config);
1332
- // Success with reduced context
1333
- console.info(`[Graph] ✅ Retry successful at ${reductionFactor * 100}% with ${reducedMessages.length} messages (reduced from ${finalMessages.length})`);
1334
- }
1335
- catch (retryError) {
1336
- const retryErrorMsg = retryError.message.toLowerCase();
1337
- const stillTooLong = retryErrorMsg.includes('too long') ||
1338
- retryErrorMsg.includes('context length') ||
1339
- retryErrorMsg.includes('validationexception');
1340
- if (stillTooLong && reductionFactor > 0.1) {
1341
- console.warn(`[Graph] Still too long at ${reductionFactor * 100}%, trying more aggressive pruning...`);
1342
- }
1343
- else {
1344
- console.error(`[Graph] Retry at ${reductionFactor * 100}% failed:`, retryError.message);
1345
- }
1346
- }
1347
- }
1348
- }
1349
- // If we got a result from retry, skip fallbacks
1350
- if (result) {
1351
- // result already set from retry
1352
- }
1353
- else {
1354
- let lastError = primaryError;
1355
- for (const fb of fallbacks) {
1356
- try {
1357
- let model = this.getNewModel({
1358
- provider: fb.provider,
1359
- clientOptions: fb.clientOptions,
1360
- });
1361
- const bindableTools = agentContext.tools;
1362
- model = (!bindableTools || bindableTools.length === 0
1363
- ? model
1364
- : model.bindTools(bindableTools));
1365
- result = await this.attemptInvoke({
1366
- currentModel: model,
1367
- finalMessages,
1368
- provider: fb.provider,
1369
- tools: agentContext.tools,
1370
- }, config);
1371
- lastError = undefined;
1372
- break;
1373
- }
1374
- catch (e) {
1375
- lastError = e;
1376
- continue;
1377
- }
1378
- }
1379
- if (lastError !== undefined) {
1380
- throw lastError;
1381
- }
1382
- }
1383
- }
1384
- if (!result) {
1385
- throw new Error('No result after model invocation');
1386
- }
1387
- /**
1388
- * Fallback: populate toolCallStepIds in the graph execution context.
1389
- *
1390
- * When model.stream() is available (the common case), attemptInvoke
1391
- * processes all chunks through a local ChatModelStreamHandler which
1392
- * creates run steps and populates toolCallStepIds before returning.
1393
- * The code below is a fallback for the rare case where model.stream
1394
- * is unavailable and model.invoke() was used instead.
1395
- *
1396
- * Text content is dispatched FIRST so that MESSAGE_CREATION is the
1397
- * current step when handleToolCalls runs. handleToolCalls then creates
1398
- * TOOL_CALLS on top of it. The dedup in getMessageId and
1399
- * toolCallStepIds.has makes this safe when attemptInvoke already
1400
- * handled everything — both paths become no-ops.
1401
- */
1402
- const responseMessage = result.messages?.[0];
1403
- const toolCalls = responseMessage
1404
- ?.tool_calls;
1405
- const hasToolCalls = Array.isArray(toolCalls) && toolCalls.length > 0;
1406
- if (hasToolCalls) {
1407
- const metadata = config.metadata;
1408
- const stepKey = this.getStepKey(metadata);
1409
- const content = responseMessage?.content;
1410
- const hasTextContent = content != null &&
1411
- (typeof content === 'string'
1412
- ? content !== ''
1413
- : Array.isArray(content) && content.length > 0);
1414
- /**
1415
- * Dispatch text content BEFORE creating TOOL_CALLS steps.
1416
- * getMessageId returns a new ID only on the first call for a step key;
1417
- * if the for-await consumer already claimed it, this is a no-op.
1418
- */
1419
- if (hasTextContent) {
1420
- const messageId = getMessageId(stepKey, this) ?? '';
1421
- if (messageId) {
1422
- await this.dispatchRunStep(stepKey, {
1423
- type: StepTypes.MESSAGE_CREATION,
1424
- message_creation: { message_id: messageId },
1425
- }, metadata);
1426
- const stepId = this.getStepIdByKey(stepKey);
1427
- if (typeof content === 'string') {
1428
- await this.dispatchMessageDelta(stepId, {
1429
- content: [{ type: ContentTypes.TEXT, text: content }],
1430
- });
1431
- }
1432
- else if (Array.isArray(content) &&
1433
- content.every((c) => typeof c === 'object' &&
1434
- 'type' in c &&
1435
- typeof c.type === 'string' &&
1436
- c.type.startsWith('text'))) {
1437
- await this.dispatchMessageDelta(stepId, {
1438
- content: content,
1439
- });
1440
- }
1441
- }
1442
- }
1443
- await handleToolCalls(toolCalls, metadata, this);
1444
- }
1445
- /**
1446
- * When streaming is disabled, on_chat_model_stream events are never
1447
- * emitted so ChatModelStreamHandler never fires. Dispatch the text
1448
- * content as MESSAGE_CREATION + MESSAGE_DELTA here.
1449
- */
1450
- const disableStreaming = agentContext.clientOptions
1451
- ?.disableStreaming === true;
1452
- if (disableStreaming &&
1453
- !hasToolCalls &&
1454
- responseMessage != null &&
1455
- responseMessage.content != null) {
1456
- const metadata = config.metadata;
1457
- const stepKey = this.getStepKey(metadata);
1458
- const messageId = getMessageId(stepKey, this) ?? '';
1459
- if (messageId) {
1460
- await this.dispatchRunStep(stepKey, {
1461
- type: StepTypes.MESSAGE_CREATION,
1462
- message_creation: { message_id: messageId },
1463
- }, metadata);
1464
- const stepId = this.getStepIdByKey(stepKey);
1465
- const content = responseMessage.content;
1466
- if (typeof content === 'string') {
1467
- await this.dispatchMessageDelta(stepId, {
1468
- content: [{ type: ContentTypes.TEXT, text: content }],
1469
- });
1470
- }
1471
- else if (Array.isArray(content) &&
1472
- content.every((c) => typeof c === 'object' &&
1473
- 'type' in c &&
1474
- typeof c.type === 'string' &&
1475
- c.type.startsWith('text'))) {
1476
- await this.dispatchMessageDelta(stepId, {
1477
- content: content,
1478
- });
1479
- }
1480
- }
1481
- }
1482
- agentContext.currentUsage = this.getUsageMetadata(result.messages?.[0]);
1483
- // Extract and normalize the LLM's finish/stop reason for auto-continuation support
1484
- const finalMsg = result.messages?.[0];
1485
- if (finalMsg && 'response_metadata' in finalMsg) {
1486
- const meta = finalMsg.response_metadata;
1487
- // Bedrock streaming nests stopReason inside messageStop: { stopReason: '...' }
1488
- const messageStop = meta.messageStop;
1489
- this.lastFinishReason =
1490
- meta.finish_reason ?? // OpenAI/Azure
1491
- meta.stop_reason ?? // Anthropic direct API
1492
- meta.stopReason ?? // Bedrock invoke (non-streaming)
1493
- messageStop?.stopReason ?? // Bedrock streaming
1494
- meta.finishReason; // VertexAI/Google
1495
- }
1496
- this.cleanupSignalListener();
1497
- // DEFERRED STRUCTURED OUTPUT: When the agent has tools AND structured output configured,
1498
- // we let the agent use tools normally via attemptInvoke(). Once the agent's response
1499
- // has NO tool_calls (it's done with tools), we produce the final structured JSON response.
1500
- if (agentContext.isStructuredOutputMode &&
1501
- agentContext.structuredOutput != null) {
1502
- const lastMessage = result.messages?.[0];
1503
- const resultHasToolCalls = lastMessage != null &&
1504
- 'tool_calls' in lastMessage &&
1505
- (lastMessage.tool_calls?.length ?? 0) > 0;
1506
- if (resultHasToolCalls !== true) {
1507
- try {
1508
- // Build messages for structured output: include the full conversation
1509
- // plus the agent's text response from attemptInvoke, so the structured
1510
- // output model has full context (tool results + agent reasoning).
1511
- const messagesForStructured = [...finalMessages];
1512
- if (lastMessage) {
1513
- messagesForStructured.push(lastMessage);
1514
- }
1515
- const structuredResult = await this.performStructuredOutput({
1516
- agentContext,
1517
- finalMessages: messagesForStructured,
1518
- config,
1519
- });
1520
- // Accumulate token usage from both API calls
1521
- const structuredUsage = this.getUsageMetadata(structuredResult.messages?.[0]);
1522
- if (structuredUsage && agentContext.currentUsage) {
1523
- agentContext.currentUsage = {
1524
- input_tokens: (agentContext.currentUsage.input_tokens ?? 0) +
1525
- (structuredUsage.input_tokens ?? 0),
1526
- output_tokens: (agentContext.currentUsage.output_tokens ?? 0) +
1527
- (structuredUsage.output_tokens ?? 0),
1528
- total_tokens: (agentContext.currentUsage.total_tokens ?? 0) +
1529
- (structuredUsage.total_tokens ?? 0),
1530
- };
1531
- }
1532
- else if (structuredUsage) {
1533
- agentContext.currentUsage = structuredUsage;
1534
- }
1535
- return structuredResult;
1536
- }
1537
- catch (structuredError) {
1538
- // Graceful fallback: the agent completed its work with tools,
1539
- // but we couldn't format the output as structured JSON.
1540
- // Return the unstructured text response from attemptInvoke.
1541
- console.error('[Graph] Deferred structured output failed after successful tool use:', structuredError);
1542
- console.warn('[Graph] Falling back to unstructured response from tool-use phase');
1543
- return result;
1544
- }
1545
- }
1546
- }
1547
- return result;
1548
- };
1549
- }
1550
- createAgentNode(agentId) {
1551
- const agentContext = this.agentContexts.get(agentId);
1552
- if (!agentContext) {
1553
- throw new Error(`Agent context not found for agentId: ${agentId}`);
1554
- }
1555
- const agentNode = `${AGENT}${agentId}`;
1556
- const toolNode = `${TOOLS}${agentId}`;
1557
- const routeMessage = (state, config) => {
1558
- this.config = config;
1559
- return toolsCondition(state, toolNode, this.invokedToolIds);
1560
- };
1561
- const StateAnnotation = Annotation.Root({
1562
- messages: Annotation({
1563
- reducer: messagesStateReducer,
1564
- default: () => [],
1565
- }),
1566
- });
1567
- const workflow = new StateGraph(StateAnnotation)
1568
- .addNode(agentNode, this.createCallModel(agentId))
1569
- .addNode(toolNode, this.initializeTools({
1570
- currentTools: agentContext.tools,
1571
- currentToolMap: agentContext.toolMap,
1572
- agentContext,
1573
- }))
1574
- .addEdge(START, agentNode)
1575
- .addConditionalEdges(agentNode, routeMessage)
1576
- .addEdge(toolNode, agentContext.toolEnd ? END : agentNode);
1577
- // Cast to unknown to avoid tight coupling to external types; options are opt-in
1578
- return workflow.compile(this.compileOptions);
1579
- }
1580
- createWorkflow() {
1581
- /** Use the default (first) agent for now */
1582
- const agentNode = this.createAgentNode(this.defaultAgentId);
1583
- const StateAnnotation = Annotation.Root({
1584
- messages: Annotation({
1585
- reducer: (a, b) => {
1586
- if (!a.length) {
1587
- this.startIndex = a.length + b.length;
1588
- console.info(`[Graph:Reducer] Initial messages | startIndex=${this.startIndex} | inputMsgCount=${b.length}`);
1589
- }
1590
- else {
1591
- console.debug(`[Graph:Reducer] Appending messages | existing=${a.length} | new=${b.length} | startIndex=${this.startIndex}`);
1592
- }
1593
- const result = messagesStateReducer(a, b);
1594
- this.messages = result;
1595
- return result;
1596
- },
1597
- default: () => [],
1598
- }),
1599
- });
1600
- const workflow = new StateGraph(StateAnnotation)
1601
- .addNode(this.defaultAgentId, agentNode, { ends: [END] })
1602
- .addEdge(START, this.defaultAgentId)
1603
- .compile();
1604
- return workflow;
1605
- }
1606
- /**
1607
- * Indicates if this is a multi-agent graph.
1608
- * Override in MultiAgentGraph to return true.
1609
- * Used to conditionally include agentId in RunStep for frontend rendering.
1610
- */
1611
- isMultiAgentGraph() {
1612
- return false;
1613
- }
1614
- /**
1615
- * Get the parallel group ID for an agent, if any.
1616
- * Override in MultiAgentGraph to provide actual group IDs.
1617
- * Group IDs are incrementing numbers (1, 2, 3...) reflecting execution order.
1618
- * @param _agentId - The agent ID to look up
1619
- * @returns undefined for StandardGraph (no parallel groups), or group number for MultiAgentGraph
1620
- */
1621
- getParallelGroupIdForAgent(_agentId) {
1622
- return undefined;
1623
- }
1624
- /* Dispatchers */
1625
- /**
1626
- * Dispatches a run step to the client, returns the step ID
1627
- */
1628
- async dispatchRunStep(stepKey, stepDetails, metadata) {
1629
- if (!this.config) {
1630
- throw new Error('No config provided');
1631
- }
1632
- const [stepId, stepIndex] = this.generateStepId(stepKey);
1633
- if (stepDetails.type === StepTypes.TOOL_CALLS && stepDetails.tool_calls) {
1634
- for (const tool_call of stepDetails.tool_calls) {
1635
- const toolCallId = tool_call.id ?? '';
1636
- if (!toolCallId || this.toolCallStepIds.has(toolCallId)) {
1637
- continue;
1638
- }
1639
- this.toolCallStepIds.set(toolCallId, stepId);
1640
- }
1641
- }
1642
- const runStep = {
1643
- stepIndex,
1644
- id: stepId,
1645
- type: stepDetails.type,
1646
- index: this.contentData.length,
1647
- stepDetails,
1648
- usage: null,
1649
- };
1650
- const runId = this.runId ?? '';
1651
- if (runId) {
1652
- runStep.runId = runId;
1653
- }
1654
- /**
1655
- * Extract agentId and parallelGroupId from metadata
1656
- * Only set agentId for MultiAgentGraph (so frontend knows when to show agent labels)
1657
- */
1658
- if (metadata) {
1659
- try {
1660
- const agentContext = this.getAgentContext(metadata);
1661
- if (this.isMultiAgentGraph() && agentContext.agentId) {
1662
- // Only include agentId for MultiAgentGraph - enables frontend to show agent labels
1663
- runStep.agentId = agentContext.agentId;
1664
- // Set group ID if this agent is part of a parallel group
1665
- // Group IDs are incrementing numbers (1, 2, 3...) reflecting execution order
1666
- const groupId = this.getParallelGroupIdForAgent(agentContext.agentId);
1667
- if (groupId != null) {
1668
- runStep.groupId = groupId;
1669
- }
1670
- }
1671
- }
1672
- catch (_e) {
1673
- /** If we can't get agent context, that's okay - agentId remains undefined */
1674
- }
1675
- }
1676
- this.contentData.push(runStep);
1677
- this.contentIndexMap.set(stepId, runStep.index);
1678
- await safeDispatchCustomEvent(GraphEvents.ON_RUN_STEP, runStep, this.config);
1679
- return stepId;
1680
- }
1681
- async handleToolCallCompleted(data, metadata, omitOutput) {
1682
- if (!this.config) {
1683
- throw new Error('No config provided');
1684
- }
1685
- if (!data.output) {
1686
- return;
1687
- }
1688
- const { input, output: _output } = data;
1689
- if (_output?.lg_name === 'Command') {
1690
- return;
1691
- }
1692
- const output = _output;
1693
- const { tool_call_id } = output;
1694
- const stepId = this.toolCallStepIds.get(tool_call_id) ?? '';
1695
- if (!stepId) {
1696
- throw new Error(`No stepId found for tool_call_id ${tool_call_id}`);
1697
- }
1698
- const runStep = this.getRunStep(stepId);
1699
- if (!runStep) {
1700
- throw new Error(`No run step found for stepId ${stepId}`);
1701
- }
1702
- /**
1703
- * Extract and store code execution session context from artifacts.
1704
- * Each file is stamped with its source session_id to support multi-session file tracking.
1705
- * When the same filename appears in a later execution, the newer version replaces the old.
1706
- */
1707
- const toolName = output.name;
1708
- if (toolName === Constants.EXECUTE_CODE ||
1709
- toolName === Constants.PROGRAMMATIC_TOOL_CALLING) {
1710
- const artifact = output.artifact;
1711
- const newFiles = artifact?.files ?? [];
1712
- const hasNewFiles = newFiles.length > 0;
1713
- if (artifact?.session_id != null && artifact.session_id !== '') {
1714
- const existingSession = this.sessions.get(Constants.EXECUTE_CODE);
1715
- const existingFiles = existingSession?.files ?? [];
1716
- if (hasNewFiles) {
1717
- /**
1718
- * Stamp each new file with its source session_id.
1719
- * This enables files from different executions (parallel or sequential)
1720
- * to be tracked and passed to subsequent calls.
1721
- */
1722
- const filesWithSession = newFiles.map((file) => ({
1723
- ...file,
1724
- session_id: artifact.session_id,
1725
- }));
1726
- /**
1727
- * Merge files, preferring latest versions by name.
1728
- * If a file with the same name exists, replace it with the new version.
1729
- * This handles cases where files are edited/recreated in subsequent executions.
1730
- */
1731
- const newFileNames = new Set(filesWithSession.map((f) => f.name));
1732
- const filteredExisting = existingFiles.filter((f) => !newFileNames.has(f.name));
1733
- this.sessions.set(Constants.EXECUTE_CODE, {
1734
- /** Keep latest session_id for reference/fallback */
1735
- session_id: artifact.session_id,
1736
- /** Accumulated files with latest versions preferred */
1737
- files: [...filteredExisting, ...filesWithSession],
1738
- lastUpdated: Date.now(),
1739
- });
1740
- }
1741
- else {
1742
- /**
1743
- * Even when execution produces no files (e.g., error or print-only),
1744
- * store the session_id so retries can reuse the same workspace
1745
- * and access any files written to disk during the failed execution.
1746
- */
1747
- this.sessions.set(Constants.EXECUTE_CODE, {
1748
- session_id: artifact.session_id,
1749
- files: existingFiles,
1750
- lastUpdated: Date.now(),
1751
- });
1752
- }
1753
- }
1754
- }
1755
- const dispatchedOutput = typeof output.content === 'string'
1756
- ? output.content
1757
- : JSON.stringify(output.content);
1758
- const args = typeof input === 'string' ? input : input.input;
1759
- const tool_call = {
1760
- args: typeof args === 'string' ? args : JSON.stringify(args),
1761
- name: output.name ?? '',
1762
- id: output.tool_call_id,
1763
- output: omitOutput === true ? '' : dispatchedOutput,
1764
- progress: 1,
1765
- };
1766
- await this.handlerRegistry
1767
- ?.getHandler(GraphEvents.ON_RUN_STEP_COMPLETED)
1768
- ?.handle(GraphEvents.ON_RUN_STEP_COMPLETED, {
1769
- result: {
1770
- id: stepId,
1771
- index: runStep.index,
1772
- type: 'tool_call',
1773
- tool_call,
1774
- },
1775
- }, metadata, this);
1776
- }
1777
- /**
1778
- * Static version of handleToolCallError to avoid creating strong references
1779
- * that prevent garbage collection
1780
- */
1781
- static async handleToolCallErrorStatic(graph, data, metadata) {
1782
- if (!graph.config) {
1783
- throw new Error('No config provided');
1784
- }
1785
- if (!data.id) {
1786
- console.warn('No Tool ID provided for Tool Error');
1787
- return;
1788
- }
1789
- const stepId = graph.toolCallStepIds.get(data.id) ?? '';
1790
- if (!stepId) {
1791
- throw new Error(`No stepId found for tool_call_id ${data.id}`);
1792
- }
1793
- const { name, input: args, error } = data;
1794
- const runStep = graph.getRunStep(stepId);
1795
- if (!runStep) {
1796
- throw new Error(`No run step found for stepId ${stepId}`);
1797
- }
1798
- const tool_call = {
1799
- id: data.id,
1800
- name: name || '',
1801
- args: typeof args === 'string' ? args : JSON.stringify(args),
1802
- output: `Error processing tool${error?.message != null ? `: ${error.message}` : ''}`,
1803
- progress: 1,
1804
- };
1805
- await graph.handlerRegistry
1806
- ?.getHandler(GraphEvents.ON_RUN_STEP_COMPLETED)
1807
- ?.handle(GraphEvents.ON_RUN_STEP_COMPLETED, {
1808
- result: {
1809
- id: stepId,
1810
- index: runStep.index,
1811
- type: 'tool_call',
1812
- tool_call,
1813
- },
1814
- }, metadata, graph);
1815
- }
1816
- /**
1817
- * Instance method that delegates to the static method
1818
- * Kept for backward compatibility
1819
- */
1820
- async handleToolCallError(data, metadata) {
1821
- await StandardGraph.handleToolCallErrorStatic(this, data, metadata);
1822
- }
1823
- async dispatchRunStepDelta(id, delta) {
1824
- if (!this.config) {
1825
- throw new Error('No config provided');
1826
- }
1827
- else if (!id) {
1828
- throw new Error('No step ID found');
1829
- }
1830
- const runStepDelta = {
1831
- id,
1832
- delta,
1833
- };
1834
- await safeDispatchCustomEvent(GraphEvents.ON_RUN_STEP_DELTA, runStepDelta, this.config);
1835
- }
1836
- async dispatchMessageDelta(id, delta) {
1837
- if (!this.config) {
1838
- throw new Error('No config provided');
1839
- }
1840
- const messageDelta = {
1841
- id,
1842
- delta,
1843
- };
1844
- await safeDispatchCustomEvent(GraphEvents.ON_MESSAGE_DELTA, messageDelta, this.config);
1845
- }
1846
- dispatchReasoningDelta = async (stepId, delta) => {
1847
- if (!this.config) {
1848
- throw new Error('No config provided');
1849
- }
1850
- const reasoningDelta = {
1851
- id: stepId,
1852
- delta,
1853
- };
1854
- await safeDispatchCustomEvent(GraphEvents.ON_REASONING_DELTA, reasoningDelta, this.config);
1855
- };
1856
- }
1857
- //# sourceMappingURL=Graph.js.map