@librechat/agents 3.1.85 → 3.1.87
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.
- package/README.md +69 -0
- package/dist/cjs/agents/AgentContext.cjs +7 -2
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/events.cjs +23 -0
- package/dist/cjs/events.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +133 -18
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/graphs/MultiAgentGraph.cjs +1 -1
- package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/index.cjs +251 -53
- package/dist/cjs/llm/anthropic/index.cjs.map +1 -1
- package/dist/cjs/llm/init.cjs +1 -5
- package/dist/cjs/llm/init.cjs.map +1 -1
- package/dist/cjs/llm/openai/index.cjs +113 -24
- package/dist/cjs/llm/openai/index.cjs.map +1 -1
- package/dist/cjs/llm/openai/utils/index.cjs.map +1 -1
- package/dist/cjs/llm/openrouter/index.cjs +3 -1
- package/dist/cjs/llm/openrouter/index.cjs.map +1 -1
- package/dist/cjs/main.cjs +18 -5
- package/dist/cjs/main.cjs.map +1 -1
- package/dist/cjs/openai/index.cjs +253 -0
- package/dist/cjs/openai/index.cjs.map +1 -0
- package/dist/cjs/responses/index.cjs +448 -0
- package/dist/cjs/responses/index.cjs.map +1 -0
- package/dist/cjs/run.cjs +108 -7
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/session/AgentSession.cjs +1057 -0
- package/dist/cjs/session/AgentSession.cjs.map +1 -0
- package/dist/cjs/session/JsonlSessionStore.cjs +425 -0
- package/dist/cjs/session/JsonlSessionStore.cjs.map +1 -0
- package/dist/cjs/session/handlers.cjs +221 -0
- package/dist/cjs/session/handlers.cjs.map +1 -0
- package/dist/cjs/session/ids.cjs +22 -0
- package/dist/cjs/session/ids.cjs.map +1 -0
- package/dist/cjs/session/messageSerialization.cjs +179 -0
- package/dist/cjs/session/messageSerialization.cjs.map +1 -0
- package/dist/cjs/stream.cjs +472 -11
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/summarization/node.cjs +1 -1
- package/dist/cjs/summarization/node.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +177 -59
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/eagerEventExecution.cjs +113 -0
- package/dist/cjs/tools/eagerEventExecution.cjs.map +1 -0
- package/dist/cjs/tools/handlers.cjs +1 -1
- package/dist/cjs/tools/handlers.cjs.map +1 -1
- package/dist/cjs/tools/streamedToolCallSeals.cjs +42 -0
- package/dist/cjs/tools/streamedToolCallSeals.cjs.map +1 -0
- package/dist/esm/agents/AgentContext.mjs +7 -2
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/events.mjs +23 -1
- package/dist/esm/events.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +133 -18
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/graphs/MultiAgentGraph.mjs +1 -1
- package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
- package/dist/esm/llm/anthropic/index.mjs +251 -53
- package/dist/esm/llm/anthropic/index.mjs.map +1 -1
- package/dist/esm/llm/init.mjs +1 -5
- package/dist/esm/llm/init.mjs.map +1 -1
- package/dist/esm/llm/openai/index.mjs +113 -25
- package/dist/esm/llm/openai/index.mjs.map +1 -1
- package/dist/esm/llm/openai/utils/index.mjs.map +1 -1
- package/dist/esm/llm/openrouter/index.mjs +4 -2
- package/dist/esm/llm/openrouter/index.mjs.map +1 -1
- package/dist/esm/main.mjs +5 -1
- package/dist/esm/main.mjs.map +1 -1
- package/dist/esm/openai/index.mjs +246 -0
- package/dist/esm/openai/index.mjs.map +1 -0
- package/dist/esm/responses/index.mjs +440 -0
- package/dist/esm/responses/index.mjs.map +1 -0
- package/dist/esm/run.mjs +108 -7
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/session/AgentSession.mjs +1054 -0
- package/dist/esm/session/AgentSession.mjs.map +1 -0
- package/dist/esm/session/JsonlSessionStore.mjs +422 -0
- package/dist/esm/session/JsonlSessionStore.mjs.map +1 -0
- package/dist/esm/session/handlers.mjs +219 -0
- package/dist/esm/session/handlers.mjs.map +1 -0
- package/dist/esm/session/ids.mjs +17 -0
- package/dist/esm/session/ids.mjs.map +1 -0
- package/dist/esm/session/messageSerialization.mjs +173 -0
- package/dist/esm/session/messageSerialization.mjs.map +1 -0
- package/dist/esm/stream.mjs +473 -12
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/summarization/node.mjs +1 -1
- package/dist/esm/summarization/node.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +177 -59
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/eagerEventExecution.mjs +107 -0
- package/dist/esm/tools/eagerEventExecution.mjs.map +1 -0
- package/dist/esm/tools/handlers.mjs +1 -1
- package/dist/esm/tools/handlers.mjs.map +1 -1
- package/dist/esm/tools/streamedToolCallSeals.mjs +36 -0
- package/dist/esm/tools/streamedToolCallSeals.mjs.map +1 -0
- package/dist/types/events.d.ts +1 -0
- package/dist/types/graphs/Graph.d.ts +24 -9
- package/dist/types/index.d.ts +1 -0
- package/dist/types/llm/openai/index.d.ts +1 -0
- package/dist/types/openai/index.d.ts +75 -0
- package/dist/types/responses/index.d.ts +97 -0
- package/dist/types/run.d.ts +2 -0
- package/dist/types/session/AgentSession.d.ts +32 -0
- package/dist/types/session/JsonlSessionStore.d.ts +67 -0
- package/dist/types/session/handlers.d.ts +8 -0
- package/dist/types/session/ids.d.ts +4 -0
- package/dist/types/session/index.d.ts +5 -0
- package/dist/types/session/messageSerialization.d.ts +7 -0
- package/dist/types/session/types.d.ts +191 -0
- package/dist/types/tools/ToolNode.d.ts +12 -1
- package/dist/types/tools/eagerEventExecution.d.ts +23 -0
- package/dist/types/tools/streamedToolCallSeals.d.ts +13 -0
- package/dist/types/types/hitl.d.ts +4 -0
- package/dist/types/types/run.d.ts +11 -1
- package/dist/types/types/tools.d.ts +36 -0
- package/package.json +19 -2
- package/src/__tests__/stream.eagerEventExecution.test.ts +2458 -0
- package/src/agents/AgentContext.ts +7 -2
- package/src/agents/__tests__/AgentContext.test.ts +254 -5
- package/src/events.ts +29 -0
- package/src/graphs/Graph.ts +224 -50
- package/src/graphs/MultiAgentGraph.ts +1 -1
- package/src/graphs/__tests__/composition.smoke.test.ts +30 -0
- package/src/index.ts +3 -0
- package/src/llm/anthropic/index.ts +356 -84
- package/src/llm/anthropic/llm.spec.ts +64 -0
- package/src/llm/custom-chat-models.smoke.test.ts +175 -4
- package/src/llm/openai/contentBlocks.test.ts +35 -0
- package/src/llm/openai/deepseek.test.ts +201 -2
- package/src/llm/openai/index.ts +171 -26
- package/src/llm/openai/utils/index.ts +22 -0
- package/src/llm/openrouter/index.ts +4 -2
- package/src/openai/__tests__/openai.test.ts +337 -0
- package/src/openai/index.ts +404 -0
- package/src/responses/__tests__/responses.test.ts +652 -0
- package/src/responses/index.ts +677 -0
- package/src/run.ts +158 -8
- package/src/scripts/compare_pi_vs_ours.ts +592 -173
- package/src/scripts/session_live.ts +548 -0
- package/src/session/AgentSession.ts +1432 -0
- package/src/session/JsonlSessionStore.ts +572 -0
- package/src/session/__tests__/JsonlSessionStore.test.ts +1410 -0
- package/src/session/__tests__/handlers.test.ts +161 -0
- package/src/session/handlers.ts +272 -0
- package/src/session/ids.ts +17 -0
- package/src/session/index.ts +44 -0
- package/src/session/messageSerialization.ts +207 -0
- package/src/session/types.ts +275 -0
- package/src/specs/custom-event-await.test.ts +89 -0
- package/src/specs/summarization.test.ts +1 -1
- package/src/stream.ts +755 -48
- package/src/summarization/node.ts +1 -1
- package/src/tools/ToolNode.ts +299 -126
- package/src/tools/__tests__/ToolNode.eagerEventExecution.test.ts +373 -0
- package/src/tools/__tests__/handlers.test.ts +2 -1
- package/src/tools/__tests__/hitl.test.ts +206 -110
- package/src/tools/eagerEventExecution.ts +153 -0
- package/src/tools/handlers.ts +8 -4
- package/src/tools/streamedToolCallSeals.ts +57 -0
- package/src/types/hitl.ts +4 -0
- package/src/types/run.ts +11 -0
- package/src/types/tools.ts +36 -0
- package/dist/cjs/llm/text.cjs +0 -69
- package/dist/cjs/llm/text.cjs.map +0 -1
- package/dist/esm/llm/text.mjs +0 -67
- package/dist/esm/llm/text.mjs.map +0 -1
package/dist/esm/stream.mjs
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import { StepTypes, ContentTypes, Providers, GraphEvents, ToolCallTypes } from './common/enum.mjs';
|
|
1
|
+
import { LOCAL_CODING_BUNDLE_NAMES, StepTypes, ContentTypes, Providers, GraphEvents, Constants, ToolCallTypes, CODE_EXECUTION_TOOLS } from './common/enum.mjs';
|
|
2
2
|
import { handleServerToolResult, handleToolCalls, handleToolCallChunks } from './tools/handlers.mjs';
|
|
3
3
|
import './messages/core.mjs';
|
|
4
4
|
import { getMessageId } from './messages/ids.mjs';
|
|
5
5
|
import '@langchain/core/messages';
|
|
6
|
-
import '
|
|
6
|
+
import { safeDispatchCustomEvent } from './utils/events.mjs';
|
|
7
7
|
import 'uuid';
|
|
8
|
+
import { normalizeError, buildToolExecutionRequestPlan, coerceRecordArgs } from './tools/eagerEventExecution.mjs';
|
|
9
|
+
import { getStreamedToolCallSeal, getStreamedToolCallAdapter } from './tools/streamedToolCallSeals.mjs';
|
|
8
10
|
|
|
11
|
+
const LOCAL_CODING_BUNDLE_NAME_SET = new Set(LOCAL_CODING_BUNDLE_NAMES);
|
|
9
12
|
/**
|
|
10
13
|
* Parses content to extract thinking sections enclosed in <think> tags using string operations
|
|
11
14
|
* @param content The content to parse
|
|
@@ -53,6 +56,428 @@ function getNonEmptyValue(possibleValues) {
|
|
|
53
56
|
}
|
|
54
57
|
return undefined;
|
|
55
58
|
}
|
|
59
|
+
function isBatchSensitiveToolExecution(graph) {
|
|
60
|
+
return (graph.hookRegistry != null ||
|
|
61
|
+
graph.humanInTheLoop?.enabled === true ||
|
|
62
|
+
graph.toolOutputReferences?.enabled === true);
|
|
63
|
+
}
|
|
64
|
+
function isDirectGraphTool(name, agentContext) {
|
|
65
|
+
if (name.startsWith(Constants.LC_TRANSFER_TO_)) {
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
return (agentContext?.graphTools?.some((tool) => 'name' in tool && tool.name === name) === true);
|
|
69
|
+
}
|
|
70
|
+
function isDirectLocalTool(name, graph) {
|
|
71
|
+
if (graph.toolExecution?.engine !== 'local') {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
if (graph.toolExecution.local?.includeCodingTools === false) {
|
|
75
|
+
return CODE_EXECUTION_TOOLS.has(name);
|
|
76
|
+
}
|
|
77
|
+
return LOCAL_CODING_BUNDLE_NAME_SET.has(name);
|
|
78
|
+
}
|
|
79
|
+
function toCodeEnvFile(file, execSessionId) {
|
|
80
|
+
const base = {
|
|
81
|
+
id: file.id,
|
|
82
|
+
resource_id: file.resource_id ?? file.id,
|
|
83
|
+
name: file.name,
|
|
84
|
+
storage_session_id: file.storage_session_id ?? execSessionId,
|
|
85
|
+
};
|
|
86
|
+
const kind = file.kind ?? 'user';
|
|
87
|
+
if (kind === 'skill' && file.version != null) {
|
|
88
|
+
return { ...base, kind: 'skill', version: file.version };
|
|
89
|
+
}
|
|
90
|
+
if (kind === 'agent') {
|
|
91
|
+
return { ...base, kind: 'agent' };
|
|
92
|
+
}
|
|
93
|
+
return { ...base, kind: 'user' };
|
|
94
|
+
}
|
|
95
|
+
function getCodeSessionContext(graph, name) {
|
|
96
|
+
if (!CODE_EXECUTION_TOOLS.has(name) &&
|
|
97
|
+
name !== Constants.SKILL_TOOL &&
|
|
98
|
+
name !== Constants.READ_FILE) {
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
const codeSession = graph.sessions.get(Constants.EXECUTE_CODE);
|
|
102
|
+
if (codeSession?.session_id == null || codeSession.session_id === '') {
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
session_id: codeSession.session_id,
|
|
107
|
+
files: codeSession.files?.map((file) => toCodeEnvFile(file, codeSession.session_id)),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function isEagerToolExecutionEnabledForBatch(args) {
|
|
111
|
+
const { graph, metadata, agentContext } = args;
|
|
112
|
+
if (graph.eagerEventToolExecution?.enabled !== true) {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
if ((agentContext?.toolDefinitions?.length ?? 0) === 0) {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
if (isBatchSensitiveToolExecution(graph)) {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
if (metadata?.[Constants.PROGRAMMATIC_TOOL_CALLING] === true ||
|
|
122
|
+
metadata?.[Constants.BASH_PROGRAMMATIC_TOOL_CALLING] === true) {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
if (graph.handlerRegistry?.getHandler(GraphEvents.ON_TOOL_EXECUTE) == null) {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
function hasFinalToolCallSignal(chunk) {
|
|
131
|
+
const metadata = chunk.response_metadata;
|
|
132
|
+
const finishReason = metadata?.finish_reason ??
|
|
133
|
+
metadata?.finishReason ??
|
|
134
|
+
metadata?.stop_reason ??
|
|
135
|
+
metadata?.stopReason;
|
|
136
|
+
return finishReason === 'tool_calls' || finishReason === 'tool_use';
|
|
137
|
+
}
|
|
138
|
+
function canPrestartSequentialStreamedToolChunks(agentContext) {
|
|
139
|
+
return (agentContext?.provider === Providers.ANTHROPIC ||
|
|
140
|
+
agentContext?.provider === Providers.MOONSHOT);
|
|
141
|
+
}
|
|
142
|
+
function hasExplicitStreamedToolCallSeals(chunk) {
|
|
143
|
+
return (getStreamedToolCallAdapter(chunk.response_metadata) != null);
|
|
144
|
+
}
|
|
145
|
+
function hasDirectToolCallInBatch(args) {
|
|
146
|
+
const { graph, agentContext, toolCalls } = args;
|
|
147
|
+
return toolCalls.some((toolCall) => toolCall.name !== '' &&
|
|
148
|
+
(isDirectGraphTool(toolCall.name, agentContext) ||
|
|
149
|
+
isDirectLocalTool(toolCall.name, graph)));
|
|
150
|
+
}
|
|
151
|
+
function hasPotentialDirectToolInStreamContext(args) {
|
|
152
|
+
const { graph, agentContext } = args;
|
|
153
|
+
if (graph.toolExecution?.engine === 'local') {
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
if ((agentContext?.graphTools?.length ?? 0) > 0) {
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
return (agentContext?.toolDefinitions?.some((toolDefinition) => toolDefinition.name.startsWith(Constants.LC_TRANSFER_TO_)) === true);
|
|
160
|
+
}
|
|
161
|
+
function createEagerToolExecutionPlan(args) {
|
|
162
|
+
const { graph, metadata, agentContext, toolCalls, skipExisting = false, } = args;
|
|
163
|
+
if (!isEagerToolExecutionEnabledForBatch({
|
|
164
|
+
graph,
|
|
165
|
+
metadata,
|
|
166
|
+
agentContext,
|
|
167
|
+
})) {
|
|
168
|
+
return undefined;
|
|
169
|
+
}
|
|
170
|
+
if (hasDirectToolCallInBatch({ graph, agentContext, toolCalls })) {
|
|
171
|
+
return undefined;
|
|
172
|
+
}
|
|
173
|
+
const candidateToolCalls = skipExisting
|
|
174
|
+
? toolCalls.filter((toolCall) => {
|
|
175
|
+
if (toolCall.id == null || toolCall.id === '') {
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
return !graph.eagerEventToolExecutions.has(toolCall.id);
|
|
179
|
+
})
|
|
180
|
+
: toolCalls;
|
|
181
|
+
if (candidateToolCalls.length === 0) {
|
|
182
|
+
return [];
|
|
183
|
+
}
|
|
184
|
+
// Eager execution must preserve ToolNode batch semantics exactly for every
|
|
185
|
+
// unstarted call. If any candidate cannot be planned, fall back for that
|
|
186
|
+
// candidate set.
|
|
187
|
+
if (candidateToolCalls.some((toolCall) => toolCall.id == null ||
|
|
188
|
+
toolCall.id === '' ||
|
|
189
|
+
toolCall.name === '' ||
|
|
190
|
+
(!skipExisting && graph.eagerEventToolExecutions.has(toolCall.id)))) {
|
|
191
|
+
return undefined;
|
|
192
|
+
}
|
|
193
|
+
const plan = buildToolExecutionRequestPlan({
|
|
194
|
+
toolCalls: candidateToolCalls.map((toolCall) => ({
|
|
195
|
+
id: toolCall.id,
|
|
196
|
+
name: toolCall.name,
|
|
197
|
+
args: toolCall.args,
|
|
198
|
+
stepId: graph.toolCallStepIds.get(toolCall.id) ?? '',
|
|
199
|
+
codeSessionContext: getCodeSessionContext(graph, toolCall.name),
|
|
200
|
+
})),
|
|
201
|
+
usageCount: graph.getEagerEventToolUsageCount(agentContext?.agentId),
|
|
202
|
+
});
|
|
203
|
+
if (plan == null) {
|
|
204
|
+
return undefined;
|
|
205
|
+
}
|
|
206
|
+
return plan.requests.map((request) => ({
|
|
207
|
+
id: request.id,
|
|
208
|
+
toolName: request.name,
|
|
209
|
+
coercedArgs: request.args,
|
|
210
|
+
request,
|
|
211
|
+
}));
|
|
212
|
+
}
|
|
213
|
+
function startEagerToolExecutions(args) {
|
|
214
|
+
const { graph, metadata, agentContext, toolCalls, skipExisting } = args;
|
|
215
|
+
const entries = createEagerToolExecutionPlan({
|
|
216
|
+
graph,
|
|
217
|
+
metadata,
|
|
218
|
+
agentContext,
|
|
219
|
+
toolCalls,
|
|
220
|
+
skipExisting,
|
|
221
|
+
});
|
|
222
|
+
if (entries == null || entries.length === 0) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
const promise = new Promise((resolve, reject) => {
|
|
226
|
+
let dispatchSettled = false;
|
|
227
|
+
let resultSettled = false;
|
|
228
|
+
let settledResults;
|
|
229
|
+
const maybeResolve = () => {
|
|
230
|
+
if (dispatchSettled && resultSettled) {
|
|
231
|
+
resolve(settledResults ?? []);
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
const batchRequest = {
|
|
235
|
+
toolCalls: entries.map((entry) => entry.request),
|
|
236
|
+
userId: graph.config?.configurable?.user_id,
|
|
237
|
+
agentId: agentContext?.agentId,
|
|
238
|
+
configurable: graph.config?.configurable,
|
|
239
|
+
metadata,
|
|
240
|
+
resolve: (results) => {
|
|
241
|
+
resultSettled = true;
|
|
242
|
+
settledResults = results;
|
|
243
|
+
maybeResolve();
|
|
244
|
+
},
|
|
245
|
+
reject,
|
|
246
|
+
};
|
|
247
|
+
void safeDispatchCustomEvent(GraphEvents.ON_TOOL_EXECUTE, batchRequest, graph.config)
|
|
248
|
+
.then(() => {
|
|
249
|
+
dispatchSettled = true;
|
|
250
|
+
maybeResolve();
|
|
251
|
+
})
|
|
252
|
+
.catch(reject);
|
|
253
|
+
}).then((results) => ({ results }), (error) => ({
|
|
254
|
+
error: normalizeError(error),
|
|
255
|
+
}));
|
|
256
|
+
for (const entry of entries) {
|
|
257
|
+
graph.eagerEventToolExecutions.set(entry.id, {
|
|
258
|
+
toolCallId: entry.id,
|
|
259
|
+
toolName: entry.toolName,
|
|
260
|
+
args: entry.coercedArgs,
|
|
261
|
+
request: entry.request,
|
|
262
|
+
promise,
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
function getEagerToolChunkKey(stepKey, toolCallChunk) {
|
|
267
|
+
let chunkKey;
|
|
268
|
+
if (typeof toolCallChunk.index === 'number') {
|
|
269
|
+
chunkKey = String(toolCallChunk.index);
|
|
270
|
+
}
|
|
271
|
+
else if (toolCallChunk.id != null && toolCallChunk.id !== '') {
|
|
272
|
+
chunkKey = toolCallChunk.id;
|
|
273
|
+
}
|
|
274
|
+
if (chunkKey == null) {
|
|
275
|
+
return undefined;
|
|
276
|
+
}
|
|
277
|
+
return `${stepKey}\u0000${chunkKey}`;
|
|
278
|
+
}
|
|
279
|
+
function getEagerToolChunkIndex(toolCallChunk) {
|
|
280
|
+
return typeof toolCallChunk.index === 'number'
|
|
281
|
+
? toolCallChunk.index
|
|
282
|
+
: undefined;
|
|
283
|
+
}
|
|
284
|
+
function pruneEagerToolCallChunkStates(args) {
|
|
285
|
+
const { graph, stepKey, toolCallIds, clearStep = false } = args;
|
|
286
|
+
const prefix = `${stepKey}\u0000`;
|
|
287
|
+
for (const [key, state] of graph.eagerEventToolCallChunks) {
|
|
288
|
+
if (!key.startsWith(prefix)) {
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
if (clearStep ||
|
|
292
|
+
(state.id != null && toolCallIds?.has(state.id) === true)) {
|
|
293
|
+
graph.eagerEventToolCallChunks.delete(key);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
function isEagerToolChunkStateComplete(state) {
|
|
298
|
+
return (state.id != null &&
|
|
299
|
+
state.id !== '' &&
|
|
300
|
+
state.name != null &&
|
|
301
|
+
state.name !== '' &&
|
|
302
|
+
coerceRecordArgs(state.argsText) != null);
|
|
303
|
+
}
|
|
304
|
+
function mergeToolCallArgsText(existing, incoming) {
|
|
305
|
+
if (incoming === '') {
|
|
306
|
+
return existing;
|
|
307
|
+
}
|
|
308
|
+
if (existing === '') {
|
|
309
|
+
return incoming;
|
|
310
|
+
}
|
|
311
|
+
if (incoming === existing) {
|
|
312
|
+
try {
|
|
313
|
+
JSON.parse(incoming);
|
|
314
|
+
return incoming;
|
|
315
|
+
}
|
|
316
|
+
catch {
|
|
317
|
+
return `${existing}${incoming}`;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
if (incoming.startsWith(existing)) {
|
|
321
|
+
return incoming;
|
|
322
|
+
}
|
|
323
|
+
if (existing.startsWith(incoming)) {
|
|
324
|
+
return existing;
|
|
325
|
+
}
|
|
326
|
+
try {
|
|
327
|
+
JSON.parse(existing);
|
|
328
|
+
JSON.parse(incoming);
|
|
329
|
+
return incoming;
|
|
330
|
+
}
|
|
331
|
+
catch {
|
|
332
|
+
// Fall through to delta concatenation.
|
|
333
|
+
}
|
|
334
|
+
for (let overlap = Math.min(existing.length, incoming.length); overlap >= 8; overlap -= 1) {
|
|
335
|
+
if (existing.endsWith(incoming.slice(0, overlap))) {
|
|
336
|
+
return `${existing}${incoming.slice(overlap)}`;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return `${existing}${incoming}`;
|
|
340
|
+
}
|
|
341
|
+
function recordEagerToolCallChunks(args) {
|
|
342
|
+
const { graph, stepKey, toolCallChunks } = args;
|
|
343
|
+
if (toolCallChunks == null || toolCallChunks.length === 0) {
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
// Streamed args can be cumulative and parseable before the provider has
|
|
347
|
+
// sealed the call. Recording stays separate from dispatch so the boundary
|
|
348
|
+
// logic can wait for either a later tool index or the final tool-call signal.
|
|
349
|
+
for (const toolCallChunk of toolCallChunks) {
|
|
350
|
+
const key = getEagerToolChunkKey(stepKey, toolCallChunk);
|
|
351
|
+
if (key == null) {
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
const incomingId = toolCallChunk.id != null && toolCallChunk.id !== ''
|
|
355
|
+
? toolCallChunk.id
|
|
356
|
+
: undefined;
|
|
357
|
+
const incomingName = toolCallChunk.name != null && toolCallChunk.name !== ''
|
|
358
|
+
? toolCallChunk.name
|
|
359
|
+
: undefined;
|
|
360
|
+
const previous = graph.eagerEventToolCallChunks.get(key);
|
|
361
|
+
const shouldReset = previous != null &&
|
|
362
|
+
((incomingId != null &&
|
|
363
|
+
previous.id != null &&
|
|
364
|
+
incomingId !== previous.id) ||
|
|
365
|
+
(incomingName != null &&
|
|
366
|
+
previous.name != null &&
|
|
367
|
+
incomingName !== previous.name));
|
|
368
|
+
const existing = previous == null || shouldReset
|
|
369
|
+
? {
|
|
370
|
+
argsText: '',
|
|
371
|
+
}
|
|
372
|
+
: previous;
|
|
373
|
+
const id = incomingId ?? existing.id;
|
|
374
|
+
const name = incomingName ?? existing.name;
|
|
375
|
+
const incomingArgs = toolCallChunk.args ?? '';
|
|
376
|
+
const isRepeatedObservedFragment = incomingArgs !== '' &&
|
|
377
|
+
incomingArgs.length > 1 &&
|
|
378
|
+
incomingArgs === existing.lastArgsFragment;
|
|
379
|
+
const argsText = isRepeatedObservedFragment
|
|
380
|
+
? existing.argsText
|
|
381
|
+
: mergeToolCallArgsText(existing.argsText, incomingArgs);
|
|
382
|
+
const next = {
|
|
383
|
+
id,
|
|
384
|
+
name,
|
|
385
|
+
argsText,
|
|
386
|
+
index: getEagerToolChunkIndex(toolCallChunk) ?? existing.index,
|
|
387
|
+
lastArgsFragment: incomingArgs !== '' ? incomingArgs : existing.lastArgsFragment,
|
|
388
|
+
};
|
|
389
|
+
graph.eagerEventToolCallChunks.set(key, next);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
function getStreamedReadyToolCalls(args) {
|
|
393
|
+
const { graph, stepKey, toolCallChunks, seal, allowSequentialSeal = false, sealAll = false, } = args;
|
|
394
|
+
const currentIndices = new Set();
|
|
395
|
+
for (const toolCallChunk of toolCallChunks ?? []) {
|
|
396
|
+
const index = getEagerToolChunkIndex(toolCallChunk);
|
|
397
|
+
if (index != null) {
|
|
398
|
+
currentIndices.add(index);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
const highestCurrentIndex = currentIndices.size > 0 ? Math.max(...currentIndices) : undefined;
|
|
402
|
+
const prefix = `${stepKey}\u0000`;
|
|
403
|
+
const readyEntries = [];
|
|
404
|
+
for (const [key, state] of graph.eagerEventToolCallChunks) {
|
|
405
|
+
if (!key.startsWith(prefix)) {
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
408
|
+
if (state.id != null && graph.eagerEventToolExecutions.has(state.id)) {
|
|
409
|
+
graph.eagerEventToolCallChunks.delete(key);
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
412
|
+
if (!isEagerToolChunkStateComplete(state)) {
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
const isSealedByLaterChunk = allowSequentialSeal &&
|
|
416
|
+
highestCurrentIndex != null &&
|
|
417
|
+
state.index != null &&
|
|
418
|
+
state.index < highestCurrentIndex &&
|
|
419
|
+
!currentIndices.has(state.index);
|
|
420
|
+
const isSealedExplicitly = seal?.kind === 'single' &&
|
|
421
|
+
((seal.id != null && state.id === seal.id) ||
|
|
422
|
+
(seal.index != null && state.index === seal.index));
|
|
423
|
+
if (sealAll ||
|
|
424
|
+
seal?.kind === 'all' ||
|
|
425
|
+
isSealedByLaterChunk ||
|
|
426
|
+
isSealedExplicitly) {
|
|
427
|
+
readyEntries.push({ key, state });
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
pruneEagerToolCallChunkStates({
|
|
431
|
+
graph,
|
|
432
|
+
stepKey,
|
|
433
|
+
toolCallIds: new Set(readyEntries
|
|
434
|
+
.map(({ state }) => state.id)
|
|
435
|
+
.filter((id) => id != null && id !== '')),
|
|
436
|
+
});
|
|
437
|
+
if (sealAll) {
|
|
438
|
+
pruneEagerToolCallChunkStates({ graph, stepKey, clearStep: true });
|
|
439
|
+
}
|
|
440
|
+
return readyEntries
|
|
441
|
+
.sort((left, right) => (left.state.index ?? 0) - (right.state.index ?? 0))
|
|
442
|
+
.flatMap(({ state }) => {
|
|
443
|
+
const args = coerceRecordArgs(state.argsText);
|
|
444
|
+
if (args == null) {
|
|
445
|
+
return [];
|
|
446
|
+
}
|
|
447
|
+
return [
|
|
448
|
+
{
|
|
449
|
+
id: state.id,
|
|
450
|
+
name: state.name ?? '',
|
|
451
|
+
args,
|
|
452
|
+
},
|
|
453
|
+
];
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
function startReadyStreamedEagerToolExecutions(args) {
|
|
457
|
+
const { graph, metadata, agentContext, stepKey, toolCallChunks, seal, allowSequentialSeal, sealAll, } = args;
|
|
458
|
+
if (hasPotentialDirectToolInStreamContext({ graph, agentContext }) ||
|
|
459
|
+
!isEagerToolExecutionEnabledForBatch({ graph, metadata, agentContext })) {
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
const toolCalls = getStreamedReadyToolCalls({
|
|
463
|
+
graph,
|
|
464
|
+
stepKey,
|
|
465
|
+
toolCallChunks,
|
|
466
|
+
seal,
|
|
467
|
+
allowSequentialSeal,
|
|
468
|
+
sealAll,
|
|
469
|
+
});
|
|
470
|
+
if (toolCalls.length === 0) {
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
startEagerToolExecutions({
|
|
474
|
+
graph,
|
|
475
|
+
metadata,
|
|
476
|
+
agentContext,
|
|
477
|
+
toolCalls,
|
|
478
|
+
skipExisting: true,
|
|
479
|
+
});
|
|
480
|
+
}
|
|
56
481
|
function getChunkContent({ chunk, provider, reasoningKey, }) {
|
|
57
482
|
if ((provider === Providers.OPENAI || provider === Providers.AZURE) &&
|
|
58
483
|
chunk?.additional_kwargs?.reasoning?.summary?.[0]?.text != null &&
|
|
@@ -113,7 +538,9 @@ class ChatModelStreamHandler {
|
|
|
113
538
|
return;
|
|
114
539
|
}
|
|
115
540
|
this.handleReasoning(chunk, agentContext);
|
|
541
|
+
const stepKey = graph.getStepKey(metadata);
|
|
116
542
|
let hasToolCalls = false;
|
|
543
|
+
const hasToolCallChunks = (chunk.tool_call_chunks && chunk.tool_call_chunks.length > 0) ?? false;
|
|
117
544
|
if (chunk.tool_calls &&
|
|
118
545
|
chunk.tool_calls.length > 0 &&
|
|
119
546
|
chunk.tool_calls.every((tc) => tc.id != null &&
|
|
@@ -122,8 +549,19 @@ class ChatModelStreamHandler {
|
|
|
122
549
|
tc.name !== '')) {
|
|
123
550
|
hasToolCalls = true;
|
|
124
551
|
await handleToolCalls(chunk.tool_calls, metadata, graph);
|
|
552
|
+
if (hasFinalToolCallSignal(chunk)) {
|
|
553
|
+
startEagerToolExecutions({
|
|
554
|
+
graph,
|
|
555
|
+
metadata,
|
|
556
|
+
agentContext,
|
|
557
|
+
toolCalls: chunk.tool_calls,
|
|
558
|
+
skipExisting: true,
|
|
559
|
+
});
|
|
560
|
+
if (!hasToolCallChunks) {
|
|
561
|
+
pruneEagerToolCallChunkStates({ graph, stepKey, clearStep: true });
|
|
562
|
+
}
|
|
563
|
+
}
|
|
125
564
|
}
|
|
126
|
-
const hasToolCallChunks = (chunk.tool_call_chunks && chunk.tool_call_chunks.length > 0) ?? false;
|
|
127
565
|
const isEmptyContent = typeof content === 'undefined' ||
|
|
128
566
|
!content.length ||
|
|
129
567
|
(typeof content === 'string' && !content);
|
|
@@ -132,23 +570,45 @@ class ChatModelStreamHandler {
|
|
|
132
570
|
if (isEmptyChunk &&
|
|
133
571
|
(chunk.id ?? '') !== '' &&
|
|
134
572
|
!graph.prelimMessageIdsByStepKey.has(chunk.id ?? '')) {
|
|
135
|
-
const stepKey = graph.getStepKey(metadata);
|
|
136
573
|
graph.prelimMessageIdsByStepKey.set(stepKey, chunk.id ?? '');
|
|
137
574
|
}
|
|
138
575
|
else if (isEmptyChunk) {
|
|
139
576
|
return;
|
|
140
577
|
}
|
|
141
|
-
const stepKey = graph.getStepKey(metadata);
|
|
142
578
|
if (hasToolCallChunks &&
|
|
143
579
|
chunk.tool_call_chunks &&
|
|
144
580
|
chunk.tool_call_chunks.length &&
|
|
145
581
|
typeof chunk.tool_call_chunks[0]?.index === 'number') {
|
|
582
|
+
const streamedToolCallSeal = getStreamedToolCallSeal(chunk.response_metadata);
|
|
583
|
+
const allowSequentialSeal = canPrestartSequentialStreamedToolChunks(agentContext);
|
|
584
|
+
const canStreamEager = (allowSequentialSeal || hasExplicitStreamedToolCallSeals(chunk)) &&
|
|
585
|
+
!hasPotentialDirectToolInStreamContext({ graph, agentContext }) &&
|
|
586
|
+
isEagerToolExecutionEnabledForBatch({ graph, metadata, agentContext });
|
|
587
|
+
if (canStreamEager) {
|
|
588
|
+
recordEagerToolCallChunks({
|
|
589
|
+
graph,
|
|
590
|
+
stepKey,
|
|
591
|
+
toolCallChunks: chunk.tool_call_chunks,
|
|
592
|
+
});
|
|
593
|
+
}
|
|
146
594
|
await handleToolCallChunks({
|
|
147
595
|
graph,
|
|
148
596
|
stepKey,
|
|
149
597
|
toolCallChunks: chunk.tool_call_chunks,
|
|
150
598
|
metadata,
|
|
151
599
|
});
|
|
600
|
+
if (canStreamEager) {
|
|
601
|
+
startReadyStreamedEagerToolExecutions({
|
|
602
|
+
graph,
|
|
603
|
+
metadata,
|
|
604
|
+
agentContext,
|
|
605
|
+
stepKey,
|
|
606
|
+
toolCallChunks: chunk.tool_call_chunks,
|
|
607
|
+
seal: streamedToolCallSeal,
|
|
608
|
+
allowSequentialSeal,
|
|
609
|
+
sealAll: hasFinalToolCallSignal(chunk),
|
|
610
|
+
});
|
|
611
|
+
}
|
|
152
612
|
}
|
|
153
613
|
if (isEmptyContent) {
|
|
154
614
|
return;
|
|
@@ -199,7 +659,7 @@ hasToolCallChunks: ${hasToolCallChunks}
|
|
|
199
659
|
text: content,
|
|
200
660
|
},
|
|
201
661
|
],
|
|
202
|
-
});
|
|
662
|
+
}, metadata);
|
|
203
663
|
}
|
|
204
664
|
else if (agentContext.currentTokenType === 'think_and_text') {
|
|
205
665
|
const { text, thinking } = parseThinkingContent(content);
|
|
@@ -211,7 +671,7 @@ hasToolCallChunks: ${hasToolCallChunks}
|
|
|
211
671
|
think: thinking,
|
|
212
672
|
},
|
|
213
673
|
],
|
|
214
|
-
});
|
|
674
|
+
}, metadata);
|
|
215
675
|
}
|
|
216
676
|
if (text) {
|
|
217
677
|
agentContext.currentTokenType = ContentTypes.TEXT;
|
|
@@ -232,7 +692,7 @@ hasToolCallChunks: ${hasToolCallChunks}
|
|
|
232
692
|
text: text,
|
|
233
693
|
},
|
|
234
694
|
],
|
|
235
|
-
});
|
|
695
|
+
}, metadata);
|
|
236
696
|
}
|
|
237
697
|
}
|
|
238
698
|
else {
|
|
@@ -243,13 +703,13 @@ hasToolCallChunks: ${hasToolCallChunks}
|
|
|
243
703
|
think: content,
|
|
244
704
|
},
|
|
245
705
|
],
|
|
246
|
-
});
|
|
706
|
+
}, metadata);
|
|
247
707
|
}
|
|
248
708
|
}
|
|
249
709
|
else if (content.every((c) => c.type?.startsWith(ContentTypes.TEXT) ?? false)) {
|
|
250
710
|
await graph.dispatchMessageDelta(stepId, {
|
|
251
711
|
content,
|
|
252
|
-
});
|
|
712
|
+
}, metadata);
|
|
253
713
|
}
|
|
254
714
|
else if (content.every((c) => (c.type?.startsWith(ContentTypes.THINKING) ?? false) ||
|
|
255
715
|
(c.type?.startsWith(ContentTypes.REASONING) ?? false) ||
|
|
@@ -260,10 +720,11 @@ hasToolCallChunks: ${hasToolCallChunks}
|
|
|
260
720
|
type: ContentTypes.THINK,
|
|
261
721
|
think: c.thinking ??
|
|
262
722
|
c.reasoning ??
|
|
263
|
-
c.reasoningText
|
|
723
|
+
c.reasoningText
|
|
724
|
+
?.text ??
|
|
264
725
|
'',
|
|
265
726
|
})),
|
|
266
|
-
});
|
|
727
|
+
}, metadata);
|
|
267
728
|
}
|
|
268
729
|
}
|
|
269
730
|
handleReasoning(chunk, agentContext) {
|