@librechat/agents 3.1.86 → 3.1.88
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/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 +475 -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/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 +476 -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 +2571 -0
- 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 +756 -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,431 @@ 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
|
+
// Anthropic seals each prior streamed tool-use block when the next indexed
|
|
140
|
+
// tool-use block begins. Live Kimi/Moonshot streams can still revise prior
|
|
141
|
+
// args after advancing to the next index, so keep those on the final
|
|
142
|
+
// tool-call path unless they grow an explicit adapter seal.
|
|
143
|
+
return agentContext?.provider === Providers.ANTHROPIC;
|
|
144
|
+
}
|
|
145
|
+
function hasExplicitStreamedToolCallSeals(chunk) {
|
|
146
|
+
return (getStreamedToolCallAdapter(chunk.response_metadata) != null);
|
|
147
|
+
}
|
|
148
|
+
function hasDirectToolCallInBatch(args) {
|
|
149
|
+
const { graph, agentContext, toolCalls } = args;
|
|
150
|
+
return toolCalls.some((toolCall) => toolCall.name !== '' &&
|
|
151
|
+
(isDirectGraphTool(toolCall.name, agentContext) ||
|
|
152
|
+
isDirectLocalTool(toolCall.name, graph)));
|
|
153
|
+
}
|
|
154
|
+
function hasPotentialDirectToolInStreamContext(args) {
|
|
155
|
+
const { graph, agentContext } = args;
|
|
156
|
+
if (graph.toolExecution?.engine === 'local') {
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
if ((agentContext?.graphTools?.length ?? 0) > 0) {
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
return (agentContext?.toolDefinitions?.some((toolDefinition) => toolDefinition.name.startsWith(Constants.LC_TRANSFER_TO_)) === true);
|
|
163
|
+
}
|
|
164
|
+
function createEagerToolExecutionPlan(args) {
|
|
165
|
+
const { graph, metadata, agentContext, toolCalls, skipExisting = false, } = args;
|
|
166
|
+
if (!isEagerToolExecutionEnabledForBatch({
|
|
167
|
+
graph,
|
|
168
|
+
metadata,
|
|
169
|
+
agentContext,
|
|
170
|
+
})) {
|
|
171
|
+
return undefined;
|
|
172
|
+
}
|
|
173
|
+
if (hasDirectToolCallInBatch({ graph, agentContext, toolCalls })) {
|
|
174
|
+
return undefined;
|
|
175
|
+
}
|
|
176
|
+
const candidateToolCalls = skipExisting
|
|
177
|
+
? toolCalls.filter((toolCall) => {
|
|
178
|
+
if (toolCall.id == null || toolCall.id === '') {
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
return !graph.eagerEventToolExecutions.has(toolCall.id);
|
|
182
|
+
})
|
|
183
|
+
: toolCalls;
|
|
184
|
+
if (candidateToolCalls.length === 0) {
|
|
185
|
+
return [];
|
|
186
|
+
}
|
|
187
|
+
// Eager execution must preserve ToolNode batch semantics exactly for every
|
|
188
|
+
// unstarted call. If any candidate cannot be planned, fall back for that
|
|
189
|
+
// candidate set.
|
|
190
|
+
if (candidateToolCalls.some((toolCall) => toolCall.id == null ||
|
|
191
|
+
toolCall.id === '' ||
|
|
192
|
+
toolCall.name === '' ||
|
|
193
|
+
(!skipExisting && graph.eagerEventToolExecutions.has(toolCall.id)))) {
|
|
194
|
+
return undefined;
|
|
195
|
+
}
|
|
196
|
+
const plan = buildToolExecutionRequestPlan({
|
|
197
|
+
toolCalls: candidateToolCalls.map((toolCall) => ({
|
|
198
|
+
id: toolCall.id,
|
|
199
|
+
name: toolCall.name,
|
|
200
|
+
args: toolCall.args,
|
|
201
|
+
stepId: graph.toolCallStepIds.get(toolCall.id) ?? '',
|
|
202
|
+
codeSessionContext: getCodeSessionContext(graph, toolCall.name),
|
|
203
|
+
})),
|
|
204
|
+
usageCount: graph.getEagerEventToolUsageCount(agentContext?.agentId),
|
|
205
|
+
});
|
|
206
|
+
if (plan == null) {
|
|
207
|
+
return undefined;
|
|
208
|
+
}
|
|
209
|
+
return plan.requests.map((request) => ({
|
|
210
|
+
id: request.id,
|
|
211
|
+
toolName: request.name,
|
|
212
|
+
coercedArgs: request.args,
|
|
213
|
+
request,
|
|
214
|
+
}));
|
|
215
|
+
}
|
|
216
|
+
function startEagerToolExecutions(args) {
|
|
217
|
+
const { graph, metadata, agentContext, toolCalls, skipExisting } = args;
|
|
218
|
+
const entries = createEagerToolExecutionPlan({
|
|
219
|
+
graph,
|
|
220
|
+
metadata,
|
|
221
|
+
agentContext,
|
|
222
|
+
toolCalls,
|
|
223
|
+
skipExisting,
|
|
224
|
+
});
|
|
225
|
+
if (entries == null || entries.length === 0) {
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
const promise = new Promise((resolve, reject) => {
|
|
229
|
+
let dispatchSettled = false;
|
|
230
|
+
let resultSettled = false;
|
|
231
|
+
let settledResults;
|
|
232
|
+
const maybeResolve = () => {
|
|
233
|
+
if (dispatchSettled && resultSettled) {
|
|
234
|
+
resolve(settledResults ?? []);
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
const batchRequest = {
|
|
238
|
+
toolCalls: entries.map((entry) => entry.request),
|
|
239
|
+
userId: graph.config?.configurable?.user_id,
|
|
240
|
+
agentId: agentContext?.agentId,
|
|
241
|
+
configurable: graph.config?.configurable,
|
|
242
|
+
metadata,
|
|
243
|
+
resolve: (results) => {
|
|
244
|
+
resultSettled = true;
|
|
245
|
+
settledResults = results;
|
|
246
|
+
maybeResolve();
|
|
247
|
+
},
|
|
248
|
+
reject,
|
|
249
|
+
};
|
|
250
|
+
void safeDispatchCustomEvent(GraphEvents.ON_TOOL_EXECUTE, batchRequest, graph.config)
|
|
251
|
+
.then(() => {
|
|
252
|
+
dispatchSettled = true;
|
|
253
|
+
maybeResolve();
|
|
254
|
+
})
|
|
255
|
+
.catch(reject);
|
|
256
|
+
}).then((results) => ({ results }), (error) => ({
|
|
257
|
+
error: normalizeError(error),
|
|
258
|
+
}));
|
|
259
|
+
for (const entry of entries) {
|
|
260
|
+
graph.eagerEventToolExecutions.set(entry.id, {
|
|
261
|
+
toolCallId: entry.id,
|
|
262
|
+
toolName: entry.toolName,
|
|
263
|
+
args: entry.coercedArgs,
|
|
264
|
+
request: entry.request,
|
|
265
|
+
promise,
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
function getEagerToolChunkKey(stepKey, toolCallChunk) {
|
|
270
|
+
let chunkKey;
|
|
271
|
+
if (typeof toolCallChunk.index === 'number') {
|
|
272
|
+
chunkKey = String(toolCallChunk.index);
|
|
273
|
+
}
|
|
274
|
+
else if (toolCallChunk.id != null && toolCallChunk.id !== '') {
|
|
275
|
+
chunkKey = toolCallChunk.id;
|
|
276
|
+
}
|
|
277
|
+
if (chunkKey == null) {
|
|
278
|
+
return undefined;
|
|
279
|
+
}
|
|
280
|
+
return `${stepKey}\u0000${chunkKey}`;
|
|
281
|
+
}
|
|
282
|
+
function getEagerToolChunkIndex(toolCallChunk) {
|
|
283
|
+
return typeof toolCallChunk.index === 'number'
|
|
284
|
+
? toolCallChunk.index
|
|
285
|
+
: undefined;
|
|
286
|
+
}
|
|
287
|
+
function pruneEagerToolCallChunkStates(args) {
|
|
288
|
+
const { graph, stepKey, toolCallIds, clearStep = false } = args;
|
|
289
|
+
const prefix = `${stepKey}\u0000`;
|
|
290
|
+
for (const [key, state] of graph.eagerEventToolCallChunks) {
|
|
291
|
+
if (!key.startsWith(prefix)) {
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
if (clearStep ||
|
|
295
|
+
(state.id != null && toolCallIds?.has(state.id) === true)) {
|
|
296
|
+
graph.eagerEventToolCallChunks.delete(key);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
function isEagerToolChunkStateComplete(state) {
|
|
301
|
+
return (state.id != null &&
|
|
302
|
+
state.id !== '' &&
|
|
303
|
+
state.name != null &&
|
|
304
|
+
state.name !== '' &&
|
|
305
|
+
coerceRecordArgs(state.argsText) != null);
|
|
306
|
+
}
|
|
307
|
+
function mergeToolCallArgsText(existing, incoming) {
|
|
308
|
+
if (incoming === '') {
|
|
309
|
+
return existing;
|
|
310
|
+
}
|
|
311
|
+
if (existing === '') {
|
|
312
|
+
return incoming;
|
|
313
|
+
}
|
|
314
|
+
if (incoming === existing) {
|
|
315
|
+
try {
|
|
316
|
+
JSON.parse(incoming);
|
|
317
|
+
return incoming;
|
|
318
|
+
}
|
|
319
|
+
catch {
|
|
320
|
+
return `${existing}${incoming}`;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
if (incoming.startsWith(existing)) {
|
|
324
|
+
return incoming;
|
|
325
|
+
}
|
|
326
|
+
if (existing.startsWith(incoming)) {
|
|
327
|
+
return existing;
|
|
328
|
+
}
|
|
329
|
+
try {
|
|
330
|
+
JSON.parse(existing);
|
|
331
|
+
JSON.parse(incoming);
|
|
332
|
+
return incoming;
|
|
333
|
+
}
|
|
334
|
+
catch {
|
|
335
|
+
// Fall through to delta concatenation.
|
|
336
|
+
}
|
|
337
|
+
for (let overlap = Math.min(existing.length, incoming.length); overlap >= 8; overlap -= 1) {
|
|
338
|
+
if (existing.endsWith(incoming.slice(0, overlap))) {
|
|
339
|
+
return `${existing}${incoming.slice(overlap)}`;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
return `${existing}${incoming}`;
|
|
343
|
+
}
|
|
344
|
+
function recordEagerToolCallChunks(args) {
|
|
345
|
+
const { graph, stepKey, toolCallChunks } = args;
|
|
346
|
+
if (toolCallChunks == null || toolCallChunks.length === 0) {
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
// Streamed args can be cumulative and parseable before the provider has
|
|
350
|
+
// sealed the call. Recording stays separate from dispatch so the boundary
|
|
351
|
+
// logic can wait for either a later tool index or the final tool-call signal.
|
|
352
|
+
for (const toolCallChunk of toolCallChunks) {
|
|
353
|
+
const key = getEagerToolChunkKey(stepKey, toolCallChunk);
|
|
354
|
+
if (key == null) {
|
|
355
|
+
continue;
|
|
356
|
+
}
|
|
357
|
+
const incomingId = toolCallChunk.id != null && toolCallChunk.id !== ''
|
|
358
|
+
? toolCallChunk.id
|
|
359
|
+
: undefined;
|
|
360
|
+
const incomingName = toolCallChunk.name != null && toolCallChunk.name !== ''
|
|
361
|
+
? toolCallChunk.name
|
|
362
|
+
: undefined;
|
|
363
|
+
const previous = graph.eagerEventToolCallChunks.get(key);
|
|
364
|
+
const shouldReset = previous != null &&
|
|
365
|
+
((incomingId != null &&
|
|
366
|
+
previous.id != null &&
|
|
367
|
+
incomingId !== previous.id) ||
|
|
368
|
+
(incomingName != null &&
|
|
369
|
+
previous.name != null &&
|
|
370
|
+
incomingName !== previous.name));
|
|
371
|
+
const existing = previous == null || shouldReset
|
|
372
|
+
? {
|
|
373
|
+
argsText: '',
|
|
374
|
+
}
|
|
375
|
+
: previous;
|
|
376
|
+
const id = incomingId ?? existing.id;
|
|
377
|
+
const name = incomingName ?? existing.name;
|
|
378
|
+
const incomingArgs = toolCallChunk.args ?? '';
|
|
379
|
+
const isRepeatedObservedFragment = incomingArgs !== '' &&
|
|
380
|
+
incomingArgs.length > 1 &&
|
|
381
|
+
incomingArgs === existing.lastArgsFragment;
|
|
382
|
+
const argsText = isRepeatedObservedFragment
|
|
383
|
+
? existing.argsText
|
|
384
|
+
: mergeToolCallArgsText(existing.argsText, incomingArgs);
|
|
385
|
+
const next = {
|
|
386
|
+
id,
|
|
387
|
+
name,
|
|
388
|
+
argsText,
|
|
389
|
+
index: getEagerToolChunkIndex(toolCallChunk) ?? existing.index,
|
|
390
|
+
lastArgsFragment: incomingArgs !== '' ? incomingArgs : existing.lastArgsFragment,
|
|
391
|
+
};
|
|
392
|
+
graph.eagerEventToolCallChunks.set(key, next);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
function getStreamedReadyToolCalls(args) {
|
|
396
|
+
const { graph, stepKey, toolCallChunks, seal, allowSequentialSeal = false, sealAll = false, } = args;
|
|
397
|
+
const currentIndices = new Set();
|
|
398
|
+
for (const toolCallChunk of toolCallChunks ?? []) {
|
|
399
|
+
const index = getEagerToolChunkIndex(toolCallChunk);
|
|
400
|
+
if (index != null) {
|
|
401
|
+
currentIndices.add(index);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
const highestCurrentIndex = currentIndices.size > 0 ? Math.max(...currentIndices) : undefined;
|
|
405
|
+
const prefix = `${stepKey}\u0000`;
|
|
406
|
+
const readyEntries = [];
|
|
407
|
+
for (const [key, state] of graph.eagerEventToolCallChunks) {
|
|
408
|
+
if (!key.startsWith(prefix)) {
|
|
409
|
+
continue;
|
|
410
|
+
}
|
|
411
|
+
if (state.id != null && graph.eagerEventToolExecutions.has(state.id)) {
|
|
412
|
+
graph.eagerEventToolCallChunks.delete(key);
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
if (!isEagerToolChunkStateComplete(state)) {
|
|
416
|
+
continue;
|
|
417
|
+
}
|
|
418
|
+
const isSealedByLaterChunk = allowSequentialSeal &&
|
|
419
|
+
highestCurrentIndex != null &&
|
|
420
|
+
state.index != null &&
|
|
421
|
+
state.index < highestCurrentIndex &&
|
|
422
|
+
!currentIndices.has(state.index);
|
|
423
|
+
const isSealedExplicitly = seal?.kind === 'single' &&
|
|
424
|
+
((seal.id != null && state.id === seal.id) ||
|
|
425
|
+
(seal.index != null && state.index === seal.index));
|
|
426
|
+
if (sealAll ||
|
|
427
|
+
seal?.kind === 'all' ||
|
|
428
|
+
isSealedByLaterChunk ||
|
|
429
|
+
isSealedExplicitly) {
|
|
430
|
+
readyEntries.push({ key, state });
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
pruneEagerToolCallChunkStates({
|
|
434
|
+
graph,
|
|
435
|
+
stepKey,
|
|
436
|
+
toolCallIds: new Set(readyEntries
|
|
437
|
+
.map(({ state }) => state.id)
|
|
438
|
+
.filter((id) => id != null && id !== '')),
|
|
439
|
+
});
|
|
440
|
+
if (sealAll) {
|
|
441
|
+
pruneEagerToolCallChunkStates({ graph, stepKey, clearStep: true });
|
|
442
|
+
}
|
|
443
|
+
return readyEntries
|
|
444
|
+
.sort((left, right) => (left.state.index ?? 0) - (right.state.index ?? 0))
|
|
445
|
+
.flatMap(({ state }) => {
|
|
446
|
+
const args = coerceRecordArgs(state.argsText);
|
|
447
|
+
if (args == null) {
|
|
448
|
+
return [];
|
|
449
|
+
}
|
|
450
|
+
return [
|
|
451
|
+
{
|
|
452
|
+
id: state.id,
|
|
453
|
+
name: state.name ?? '',
|
|
454
|
+
args,
|
|
455
|
+
},
|
|
456
|
+
];
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
function startReadyStreamedEagerToolExecutions(args) {
|
|
460
|
+
const { graph, metadata, agentContext, stepKey, toolCallChunks, seal, allowSequentialSeal, sealAll, } = args;
|
|
461
|
+
if (hasPotentialDirectToolInStreamContext({ graph, agentContext }) ||
|
|
462
|
+
!isEagerToolExecutionEnabledForBatch({ graph, metadata, agentContext })) {
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
const toolCalls = getStreamedReadyToolCalls({
|
|
466
|
+
graph,
|
|
467
|
+
stepKey,
|
|
468
|
+
toolCallChunks,
|
|
469
|
+
seal,
|
|
470
|
+
allowSequentialSeal,
|
|
471
|
+
sealAll,
|
|
472
|
+
});
|
|
473
|
+
if (toolCalls.length === 0) {
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
startEagerToolExecutions({
|
|
477
|
+
graph,
|
|
478
|
+
metadata,
|
|
479
|
+
agentContext,
|
|
480
|
+
toolCalls,
|
|
481
|
+
skipExisting: true,
|
|
482
|
+
});
|
|
483
|
+
}
|
|
56
484
|
function getChunkContent({ chunk, provider, reasoningKey, }) {
|
|
57
485
|
if ((provider === Providers.OPENAI || provider === Providers.AZURE) &&
|
|
58
486
|
chunk?.additional_kwargs?.reasoning?.summary?.[0]?.text != null &&
|
|
@@ -113,7 +541,9 @@ class ChatModelStreamHandler {
|
|
|
113
541
|
return;
|
|
114
542
|
}
|
|
115
543
|
this.handleReasoning(chunk, agentContext);
|
|
544
|
+
const stepKey = graph.getStepKey(metadata);
|
|
116
545
|
let hasToolCalls = false;
|
|
546
|
+
const hasToolCallChunks = (chunk.tool_call_chunks && chunk.tool_call_chunks.length > 0) ?? false;
|
|
117
547
|
if (chunk.tool_calls &&
|
|
118
548
|
chunk.tool_calls.length > 0 &&
|
|
119
549
|
chunk.tool_calls.every((tc) => tc.id != null &&
|
|
@@ -122,8 +552,19 @@ class ChatModelStreamHandler {
|
|
|
122
552
|
tc.name !== '')) {
|
|
123
553
|
hasToolCalls = true;
|
|
124
554
|
await handleToolCalls(chunk.tool_calls, metadata, graph);
|
|
555
|
+
if (hasFinalToolCallSignal(chunk)) {
|
|
556
|
+
startEagerToolExecutions({
|
|
557
|
+
graph,
|
|
558
|
+
metadata,
|
|
559
|
+
agentContext,
|
|
560
|
+
toolCalls: chunk.tool_calls,
|
|
561
|
+
skipExisting: true,
|
|
562
|
+
});
|
|
563
|
+
if (!hasToolCallChunks) {
|
|
564
|
+
pruneEagerToolCallChunkStates({ graph, stepKey, clearStep: true });
|
|
565
|
+
}
|
|
566
|
+
}
|
|
125
567
|
}
|
|
126
|
-
const hasToolCallChunks = (chunk.tool_call_chunks && chunk.tool_call_chunks.length > 0) ?? false;
|
|
127
568
|
const isEmptyContent = typeof content === 'undefined' ||
|
|
128
569
|
!content.length ||
|
|
129
570
|
(typeof content === 'string' && !content);
|
|
@@ -132,23 +573,45 @@ class ChatModelStreamHandler {
|
|
|
132
573
|
if (isEmptyChunk &&
|
|
133
574
|
(chunk.id ?? '') !== '' &&
|
|
134
575
|
!graph.prelimMessageIdsByStepKey.has(chunk.id ?? '')) {
|
|
135
|
-
const stepKey = graph.getStepKey(metadata);
|
|
136
576
|
graph.prelimMessageIdsByStepKey.set(stepKey, chunk.id ?? '');
|
|
137
577
|
}
|
|
138
578
|
else if (isEmptyChunk) {
|
|
139
579
|
return;
|
|
140
580
|
}
|
|
141
|
-
const stepKey = graph.getStepKey(metadata);
|
|
142
581
|
if (hasToolCallChunks &&
|
|
143
582
|
chunk.tool_call_chunks &&
|
|
144
583
|
chunk.tool_call_chunks.length &&
|
|
145
584
|
typeof chunk.tool_call_chunks[0]?.index === 'number') {
|
|
585
|
+
const streamedToolCallSeal = getStreamedToolCallSeal(chunk.response_metadata);
|
|
586
|
+
const allowSequentialSeal = canPrestartSequentialStreamedToolChunks(agentContext);
|
|
587
|
+
const canStreamEager = (allowSequentialSeal || hasExplicitStreamedToolCallSeals(chunk)) &&
|
|
588
|
+
!hasPotentialDirectToolInStreamContext({ graph, agentContext }) &&
|
|
589
|
+
isEagerToolExecutionEnabledForBatch({ graph, metadata, agentContext });
|
|
590
|
+
if (canStreamEager) {
|
|
591
|
+
recordEagerToolCallChunks({
|
|
592
|
+
graph,
|
|
593
|
+
stepKey,
|
|
594
|
+
toolCallChunks: chunk.tool_call_chunks,
|
|
595
|
+
});
|
|
596
|
+
}
|
|
146
597
|
await handleToolCallChunks({
|
|
147
598
|
graph,
|
|
148
599
|
stepKey,
|
|
149
600
|
toolCallChunks: chunk.tool_call_chunks,
|
|
150
601
|
metadata,
|
|
151
602
|
});
|
|
603
|
+
if (canStreamEager) {
|
|
604
|
+
startReadyStreamedEagerToolExecutions({
|
|
605
|
+
graph,
|
|
606
|
+
metadata,
|
|
607
|
+
agentContext,
|
|
608
|
+
stepKey,
|
|
609
|
+
toolCallChunks: chunk.tool_call_chunks,
|
|
610
|
+
seal: streamedToolCallSeal,
|
|
611
|
+
allowSequentialSeal,
|
|
612
|
+
sealAll: hasFinalToolCallSignal(chunk),
|
|
613
|
+
});
|
|
614
|
+
}
|
|
152
615
|
}
|
|
153
616
|
if (isEmptyContent) {
|
|
154
617
|
return;
|
|
@@ -199,7 +662,7 @@ hasToolCallChunks: ${hasToolCallChunks}
|
|
|
199
662
|
text: content,
|
|
200
663
|
},
|
|
201
664
|
],
|
|
202
|
-
});
|
|
665
|
+
}, metadata);
|
|
203
666
|
}
|
|
204
667
|
else if (agentContext.currentTokenType === 'think_and_text') {
|
|
205
668
|
const { text, thinking } = parseThinkingContent(content);
|
|
@@ -211,7 +674,7 @@ hasToolCallChunks: ${hasToolCallChunks}
|
|
|
211
674
|
think: thinking,
|
|
212
675
|
},
|
|
213
676
|
],
|
|
214
|
-
});
|
|
677
|
+
}, metadata);
|
|
215
678
|
}
|
|
216
679
|
if (text) {
|
|
217
680
|
agentContext.currentTokenType = ContentTypes.TEXT;
|
|
@@ -232,7 +695,7 @@ hasToolCallChunks: ${hasToolCallChunks}
|
|
|
232
695
|
text: text,
|
|
233
696
|
},
|
|
234
697
|
],
|
|
235
|
-
});
|
|
698
|
+
}, metadata);
|
|
236
699
|
}
|
|
237
700
|
}
|
|
238
701
|
else {
|
|
@@ -243,13 +706,13 @@ hasToolCallChunks: ${hasToolCallChunks}
|
|
|
243
706
|
think: content,
|
|
244
707
|
},
|
|
245
708
|
],
|
|
246
|
-
});
|
|
709
|
+
}, metadata);
|
|
247
710
|
}
|
|
248
711
|
}
|
|
249
712
|
else if (content.every((c) => c.type?.startsWith(ContentTypes.TEXT) ?? false)) {
|
|
250
713
|
await graph.dispatchMessageDelta(stepId, {
|
|
251
714
|
content,
|
|
252
|
-
});
|
|
715
|
+
}, metadata);
|
|
253
716
|
}
|
|
254
717
|
else if (content.every((c) => (c.type?.startsWith(ContentTypes.THINKING) ?? false) ||
|
|
255
718
|
(c.type?.startsWith(ContentTypes.REASONING) ?? false) ||
|
|
@@ -260,10 +723,11 @@ hasToolCallChunks: ${hasToolCallChunks}
|
|
|
260
723
|
type: ContentTypes.THINK,
|
|
261
724
|
think: c.thinking ??
|
|
262
725
|
c.reasoning ??
|
|
263
|
-
c.reasoningText
|
|
726
|
+
c.reasoningText
|
|
727
|
+
?.text ??
|
|
264
728
|
'',
|
|
265
729
|
})),
|
|
266
|
-
});
|
|
730
|
+
}, metadata);
|
|
267
731
|
}
|
|
268
732
|
}
|
|
269
733
|
handleReasoning(chunk, agentContext) {
|