@librechat/agents 3.1.45 → 3.1.51

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 (45) hide show
  1. package/dist/cjs/events.cjs +9 -4
  2. package/dist/cjs/events.cjs.map +1 -1
  3. package/dist/cjs/graphs/Graph.cjs +142 -106
  4. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  5. package/dist/cjs/messages/format.cjs +8 -1
  6. package/dist/cjs/messages/format.cjs.map +1 -1
  7. package/dist/cjs/run.cjs +0 -4
  8. package/dist/cjs/run.cjs.map +1 -1
  9. package/dist/cjs/tools/ToolNode.cjs +100 -1
  10. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  11. package/dist/cjs/tools/handlers.cjs.map +1 -1
  12. package/dist/esm/events.mjs +9 -4
  13. package/dist/esm/events.mjs.map +1 -1
  14. package/dist/esm/graphs/Graph.mjs +138 -102
  15. package/dist/esm/graphs/Graph.mjs.map +1 -1
  16. package/dist/esm/messages/format.mjs +8 -1
  17. package/dist/esm/messages/format.mjs.map +1 -1
  18. package/dist/esm/run.mjs +1 -5
  19. package/dist/esm/run.mjs.map +1 -1
  20. package/dist/esm/tools/ToolNode.mjs +100 -1
  21. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  22. package/dist/esm/tools/handlers.mjs.map +1 -1
  23. package/dist/types/events.d.ts +10 -3
  24. package/dist/types/graphs/Graph.d.ts +0 -2
  25. package/dist/types/tools/ToolNode.d.ts +13 -1
  26. package/dist/types/tools/handlers.d.ts +2 -2
  27. package/package.json +1 -1
  28. package/src/events.ts +11 -14
  29. package/src/graphs/Graph.ts +181 -144
  30. package/src/messages/format.ts +12 -1
  31. package/src/messages/formatAgentMessages.test.ts +184 -0
  32. package/src/run.ts +0 -6
  33. package/src/scripts/simple.ts +1 -1
  34. package/src/specs/anthropic.simple.test.ts +3 -4
  35. package/src/specs/azure.simple.test.ts +1 -2
  36. package/src/specs/cache.simple.test.ts +1 -2
  37. package/src/specs/custom-event-await.test.ts +2 -4
  38. package/src/specs/deepseek.simple.test.ts +1 -2
  39. package/src/specs/moonshot.simple.test.ts +1 -2
  40. package/src/specs/openai.simple.test.ts +1 -2
  41. package/src/specs/openrouter.simple.test.ts +1 -2
  42. package/src/specs/reasoning.test.ts +1 -2
  43. package/src/specs/tool-error.test.ts +1 -2
  44. package/src/tools/ToolNode.ts +130 -1
  45. package/src/tools/handlers.ts +2 -2
@@ -16,8 +16,8 @@ import {
16
16
  createMetadataAggregator,
17
17
  } from '@/events';
18
18
  import { ContentTypes, GraphEvents, Providers, TitleMethod } from '@/common';
19
- import { ChatModelStreamHandler, createContentAggregator } from '@/stream';
20
19
  import { capitalizeFirstLetter } from './spec.utils';
20
+ import { createContentAggregator } from '@/stream';
21
21
  import { getLLMConfig } from '@/utils/llmConfig';
22
22
  import { Run } from '@/run';
23
23
 
@@ -83,7 +83,6 @@ describeIfAzure(`${capitalizeFirstLetter(provider)} Streaming Tests`, () => {
83
83
  > => ({
84
84
  [GraphEvents.TOOL_END]: new ToolEndHandler(),
85
85
  [GraphEvents.CHAT_MODEL_END]: new ModelEndHandler(collectedUsage),
86
- [GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler(),
87
86
  [GraphEvents.ON_RUN_STEP_COMPLETED]: {
88
87
  handle: (
89
88
  event: GraphEvents.ON_RUN_STEP_COMPLETED,
@@ -10,9 +10,9 @@ import {
10
10
  UsageMetadata,
11
11
  } from '@langchain/core/messages';
12
12
  import type * as t from '@/types';
13
- import { ChatModelStreamHandler, createContentAggregator } from '@/stream';
14
13
  import { ModelEndHandler, ToolEndHandler } from '@/events';
15
14
  import { capitalizeFirstLetter } from './spec.utils';
15
+ import { createContentAggregator } from '@/stream';
16
16
  import { GraphEvents, Providers } from '@/common';
17
17
  import { getLLMConfig } from '@/utils/llmConfig';
18
18
  import { getArgs } from '@/scripts/args';
@@ -36,7 +36,6 @@ describe('Prompt Caching Integration Tests', () => {
36
36
  const customHandlers: Record<string | GraphEvents, t.EventHandler> = {
37
37
  [GraphEvents.TOOL_END]: new ToolEndHandler(),
38
38
  [GraphEvents.CHAT_MODEL_END]: new ModelEndHandler(collectedUsage),
39
- [GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler(),
40
39
  [GraphEvents.ON_RUN_STEP_COMPLETED]: {
41
40
  handle: (
42
41
  event: GraphEvents.ON_RUN_STEP_COMPLETED,
@@ -1,8 +1,8 @@
1
1
  import { HumanMessage } from '@langchain/core/messages';
2
2
  import type * as t from '@/types';
3
- import { ToolEndHandler, ModelEndHandler } from '@/events';
4
3
  import { ContentTypes, GraphEvents, Providers } from '@/common';
5
- import { ChatModelStreamHandler, createContentAggregator } from '@/stream';
4
+ import { ToolEndHandler, ModelEndHandler } from '@/events';
5
+ import { createContentAggregator } from '@/stream';
6
6
  import { Run } from '@/run';
7
7
 
8
8
  describe('Custom event handler awaitHandlers behavior', () => {
@@ -39,7 +39,6 @@ describe('Custom event handler awaitHandlers behavior', () => {
39
39
  const customHandlers: Record<string | GraphEvents, t.EventHandler> = {
40
40
  [GraphEvents.TOOL_END]: new ToolEndHandler(),
41
41
  [GraphEvents.CHAT_MODEL_END]: new ModelEndHandler(),
42
- [GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler(),
43
42
  [GraphEvents.ON_RUN_STEP_COMPLETED]: {
44
43
  handle: (
45
44
  event: GraphEvents.ON_RUN_STEP_COMPLETED,
@@ -133,7 +132,6 @@ describe('Custom event handler awaitHandlers behavior', () => {
133
132
  const customHandlers: Record<string | GraphEvents, t.EventHandler> = {
134
133
  [GraphEvents.TOOL_END]: new ToolEndHandler(),
135
134
  [GraphEvents.CHAT_MODEL_END]: new ModelEndHandler(),
136
- [GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler(),
137
135
  [GraphEvents.ON_RUN_STEP_COMPLETED]: {
138
136
  handle: (
139
137
  event: GraphEvents.ON_RUN_STEP_COMPLETED,
@@ -11,8 +11,8 @@ import {
11
11
  import type * as t from '@/types';
12
12
  import { ToolEndHandler, ModelEndHandler } from '@/events';
13
13
  import { ContentTypes, GraphEvents, Providers } from '@/common';
14
- import { ChatModelStreamHandler, createContentAggregator } from '@/stream';
15
14
  import { capitalizeFirstLetter } from './spec.utils';
15
+ import { createContentAggregator } from '@/stream';
16
16
  import { getLLMConfig } from '@/utils/llmConfig';
17
17
  import { Run } from '@/run';
18
18
 
@@ -64,7 +64,6 @@ const skipTests = process.env.DEEPSEEK_API_KEY == null;
64
64
  > => ({
65
65
  [GraphEvents.TOOL_END]: new ToolEndHandler(),
66
66
  [GraphEvents.CHAT_MODEL_END]: new ModelEndHandler(collectedUsage),
67
- [GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler(),
68
67
  [GraphEvents.ON_RUN_STEP_COMPLETED]: {
69
68
  handle: (
70
69
  event: GraphEvents.ON_RUN_STEP_COMPLETED,
@@ -11,8 +11,8 @@ import {
11
11
  import type * as t from '@/types';
12
12
  import { ToolEndHandler, ModelEndHandler } from '@/events';
13
13
  import { ContentTypes, GraphEvents, Providers } from '@/common';
14
- import { ChatModelStreamHandler, createContentAggregator } from '@/stream';
15
14
  import { capitalizeFirstLetter } from './spec.utils';
15
+ import { createContentAggregator } from '@/stream';
16
16
  import { Run } from '@/run';
17
17
 
18
18
  const provider = Providers.MOONSHOT;
@@ -71,7 +71,6 @@ const skipTests = process.env.MOONSHOT_API_KEY == null;
71
71
  > => ({
72
72
  [GraphEvents.TOOL_END]: new ToolEndHandler(),
73
73
  [GraphEvents.CHAT_MODEL_END]: new ModelEndHandler(collectedUsage),
74
- [GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler(),
75
74
  [GraphEvents.ON_RUN_STEP_COMPLETED]: {
76
75
  handle: (
77
76
  event: GraphEvents.ON_RUN_STEP_COMPLETED,
@@ -16,8 +16,8 @@ import {
16
16
  createMetadataAggregator,
17
17
  } from '@/events';
18
18
  import { ContentTypes, GraphEvents, Providers, TitleMethod } from '@/common';
19
- import { ChatModelStreamHandler, createContentAggregator } from '@/stream';
20
19
  import { capitalizeFirstLetter } from './spec.utils';
20
+ import { createContentAggregator } from '@/stream';
21
21
  import { getLLMConfig } from '@/utils/llmConfig';
22
22
  import { getArgs } from '@/scripts/args';
23
23
  import { Run } from '@/run';
@@ -63,7 +63,6 @@ describe(`${capitalizeFirstLetter(provider)} Streaming Tests`, () => {
63
63
  > => ({
64
64
  [GraphEvents.TOOL_END]: new ToolEndHandler(),
65
65
  [GraphEvents.CHAT_MODEL_END]: new ModelEndHandler(collectedUsage),
66
- [GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler(),
67
66
  [GraphEvents.ON_RUN_STEP_COMPLETED]: {
68
67
  handle: (
69
68
  event: GraphEvents.ON_RUN_STEP_COMPLETED,
@@ -9,8 +9,8 @@ import {
9
9
  import type * as t from '@/types';
10
10
  import { ToolEndHandler, ModelEndHandler } from '@/events';
11
11
  import { ContentTypes, GraphEvents, Providers, TitleMethod } from '@/common';
12
- import { ChatModelStreamHandler, createContentAggregator } from '@/stream';
13
12
  import { capitalizeFirstLetter } from './spec.utils';
13
+ import { createContentAggregator } from '@/stream';
14
14
  import { getLLMConfig } from '@/utils/llmConfig';
15
15
  import { getArgs } from '@/scripts/args';
16
16
  import { Run } from '@/run';
@@ -54,7 +54,6 @@ describeIf(`${capitalizeFirstLetter(provider)} Streaming Tests`, () => {
54
54
  > => ({
55
55
  [GraphEvents.TOOL_END]: new ToolEndHandler(),
56
56
  [GraphEvents.CHAT_MODEL_END]: new ModelEndHandler(collectedUsage),
57
- [GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler(),
58
57
  });
59
58
 
60
59
  test(`${capitalizeFirstLetter(provider)}: simple stream + title`, async () => {
@@ -10,8 +10,8 @@ import {
10
10
  } from '@langchain/core/messages';
11
11
  import type { RunnableConfig } from '@langchain/core/runnables';
12
12
  import type * as t from '@/types';
13
- import { ChatModelStreamHandler, createContentAggregator } from '@/stream';
14
13
  import { capitalizeFirstLetter } from './spec.utils';
14
+ import { createContentAggregator } from '@/stream';
15
15
  import { GraphEvents, Providers } from '@/common';
16
16
  import { getLLMConfig } from '@/utils/llmConfig';
17
17
  import { getArgs } from '@/scripts/args';
@@ -97,7 +97,6 @@ describe(`${capitalizeFirstLetter(provider)} Streaming Tests`, () => {
97
97
  string | GraphEvents,
98
98
  t.EventHandler
99
99
  > => ({
100
- [GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler(),
101
100
  [GraphEvents.ON_RUN_STEP_COMPLETED]: {
102
101
  handle: (
103
102
  event: GraphEvents.ON_RUN_STEP_COMPLETED,
@@ -5,8 +5,8 @@ import { ToolCall } from '@langchain/core/messages/tool';
5
5
  import { HumanMessage, BaseMessage } from '@langchain/core/messages';
6
6
  import type { RunnableConfig } from '@langchain/core/runnables';
7
7
  import type * as t from '@/types';
8
- import { ChatModelStreamHandler, createContentAggregator } from '@/stream';
9
8
  import { ToolEndHandler, ModelEndHandler } from '@/events';
9
+ import { createContentAggregator } from '@/stream';
10
10
  import { GraphEvents, Providers } from '@/common';
11
11
  import { getLLMConfig } from '@/utils/llmConfig';
12
12
  import { getArgs } from '@/scripts/args';
@@ -83,7 +83,6 @@ describe('Tool Error Handling Tests', () => {
83
83
  > => ({
84
84
  [GraphEvents.TOOL_END]: new ToolEndHandler(),
85
85
  [GraphEvents.CHAT_MODEL_END]: new ModelEndHandler(),
86
- [GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler(),
87
86
  [GraphEvents.ON_RUN_STEP_COMPLETED]: {
88
87
  handle: (
89
88
  event: GraphEvents.ON_RUN_STEP_COMPLETED,
@@ -39,6 +39,8 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
39
39
  toolCallStepIds?: Map<string, string>;
40
40
  errorHandler?: t.ToolNodeConstructorParams['errorHandler'];
41
41
  private toolUsageCount: Map<string, number>;
42
+ /** Maps toolCallId → turn captured in runTool, used by handleRunToolCompletions */
43
+ private toolCallTurns: Map<string, number> = new Map();
42
44
  /** Tool registry for filtering (lazy computation of programmatic maps) */
43
45
  private toolRegistry?: t.LCToolRegistry;
44
46
  /** Cached programmatic tools (computed once on first PTC call) */
@@ -129,6 +131,9 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
129
131
  }
130
132
  const turn = this.toolUsageCount.get(call.name) ?? 0;
131
133
  this.toolUsageCount.set(call.name, turn + 1);
134
+ if (call.id != null && call.id !== '') {
135
+ this.toolCallTurns.set(call.id, turn);
136
+ }
132
137
  const args = call.args;
133
138
  const stepId = this.toolCallStepIds?.get(call.id!);
134
139
 
@@ -289,7 +294,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
289
294
 
290
295
  /**
291
296
  * Extracts code execution session context from tool results and stores in Graph.sessions.
292
- * Mirrors the session storage logic in Graph.handleToolCallCompleted() for direct execution.
297
+ * Mirrors the session storage logic in handleRunToolCompletions for direct execution.
293
298
  */
294
299
  private storeCodeSessionFromResults(
295
300
  results: t.ToolExecuteResult[],
@@ -350,6 +355,115 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
350
355
  }
351
356
  }
352
357
 
358
+ /**
359
+ * Post-processes standard runTool outputs: dispatches ON_RUN_STEP_COMPLETED
360
+ * and stores code session context. Mirrors the completion handling in
361
+ * dispatchToolEvents for the event-driven path.
362
+ *
363
+ * By handling completions here in graph context (rather than in the
364
+ * stream consumer via ToolEndHandler), the race between the stream
365
+ * consumer and graph execution is eliminated.
366
+ */
367
+ private handleRunToolCompletions(
368
+ calls: ToolCall[],
369
+ outputs: (BaseMessage | Command)[],
370
+ config: RunnableConfig
371
+ ): void {
372
+ for (let i = 0; i < calls.length; i++) {
373
+ const call = calls[i];
374
+ const output = outputs[i];
375
+ const turn = this.toolCallTurns.get(call.id!) ?? 0;
376
+
377
+ if (isCommand(output)) {
378
+ continue;
379
+ }
380
+
381
+ const toolMessage = output as ToolMessage;
382
+ const toolCallId = call.id ?? '';
383
+
384
+ // Skip error ToolMessages when errorHandler already dispatched ON_RUN_STEP_COMPLETED
385
+ // via handleToolCallErrorStatic. Without this check, errors would be double-dispatched.
386
+ if (toolMessage.status === 'error' && this.errorHandler != null) {
387
+ continue;
388
+ }
389
+
390
+ // Store code session context from tool results
391
+ if (
392
+ this.sessions &&
393
+ (call.name === Constants.EXECUTE_CODE ||
394
+ call.name === Constants.PROGRAMMATIC_TOOL_CALLING)
395
+ ) {
396
+ const artifact = toolMessage.artifact as
397
+ | t.CodeExecutionArtifact
398
+ | undefined;
399
+ if (artifact?.session_id != null && artifact.session_id !== '') {
400
+ const newFiles = artifact.files ?? [];
401
+ const existingSession = this.sessions.get(Constants.EXECUTE_CODE) as
402
+ | t.CodeSessionContext
403
+ | undefined;
404
+ const existingFiles = existingSession?.files ?? [];
405
+
406
+ if (newFiles.length > 0) {
407
+ const filesWithSession: t.FileRefs = newFiles.map((file) => ({
408
+ ...file,
409
+ session_id: artifact.session_id,
410
+ }));
411
+ const newFileNames = new Set(filesWithSession.map((f) => f.name));
412
+ const filteredExisting = existingFiles.filter(
413
+ (f) => !newFileNames.has(f.name)
414
+ );
415
+ this.sessions.set(Constants.EXECUTE_CODE, {
416
+ session_id: artifact.session_id,
417
+ files: [...filteredExisting, ...filesWithSession],
418
+ lastUpdated: Date.now(),
419
+ });
420
+ } else {
421
+ this.sessions.set(Constants.EXECUTE_CODE, {
422
+ session_id: artifact.session_id,
423
+ files: existingFiles,
424
+ lastUpdated: Date.now(),
425
+ });
426
+ }
427
+ }
428
+ }
429
+
430
+ // Dispatch ON_RUN_STEP_COMPLETED via custom event (same path as dispatchToolEvents)
431
+ const stepId = this.toolCallStepIds?.get(toolCallId) ?? '';
432
+ if (!stepId) {
433
+ continue;
434
+ }
435
+
436
+ const contentString =
437
+ typeof toolMessage.content === 'string'
438
+ ? toolMessage.content
439
+ : JSON.stringify(toolMessage.content);
440
+
441
+ const tool_call: t.ProcessedToolCall = {
442
+ args:
443
+ typeof call.args === 'string'
444
+ ? (call.args as string)
445
+ : JSON.stringify((call.args as unknown) ?? {}),
446
+ name: call.name,
447
+ id: toolCallId,
448
+ output: contentString,
449
+ progress: 1,
450
+ };
451
+
452
+ safeDispatchCustomEvent(
453
+ GraphEvents.ON_RUN_STEP_COMPLETED,
454
+ {
455
+ result: {
456
+ id: stepId,
457
+ index: turn,
458
+ type: 'tool_call' as const,
459
+ tool_call,
460
+ },
461
+ },
462
+ config
463
+ );
464
+ }
465
+ }
466
+
353
467
  /**
354
468
  * Dispatches tool calls to the host via ON_TOOL_EXECUTE event and returns raw ToolMessages.
355
469
  * Core logic for event-driven execution, separated from output shaping.
@@ -404,6 +518,14 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
404
518
  const request = requests.find((r) => r.id === result.toolCallId);
405
519
  const toolName = request?.name ?? 'unknown';
406
520
  const stepId = this.toolCallStepIds?.get(result.toolCallId) ?? '';
521
+ if (!stepId) {
522
+ // eslint-disable-next-line no-console
523
+ console.warn(
524
+ `[ToolNode] toolCallStepIds missing entry for toolCallId=${result.toolCallId} (tool=${toolName}). ` +
525
+ 'This indicates a race between the stream consumer and graph execution. ' +
526
+ `Map size: ${this.toolCallStepIds?.size ?? 0}`
527
+ );
528
+ }
407
529
 
408
530
  let toolMessage: ToolMessage;
409
531
  let contentString: string;
@@ -476,6 +598,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
476
598
 
477
599
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
478
600
  protected async run(input: any, config: RunnableConfig): Promise<T> {
601
+ this.toolCallTurns.clear();
479
602
  let outputs: (BaseMessage | Command)[];
480
603
 
481
604
  if (this.isSendInput(input)) {
@@ -484,6 +607,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
484
607
  return this.executeViaEvent([input.lg_tool_call], config, input);
485
608
  }
486
609
  outputs = [await this.runTool(input.lg_tool_call, config)];
610
+ this.handleRunToolCompletions([input.lg_tool_call], outputs, config);
487
611
  } else {
488
612
  let messages: BaseMessage[];
489
613
  if (Array.isArray(input)) {
@@ -557,6 +681,10 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
557
681
  )
558
682
  : [];
559
683
 
684
+ if (directCalls.length > 0 && directOutputs.length > 0) {
685
+ this.handleRunToolCompletions(directCalls, directOutputs, config);
686
+ }
687
+
560
688
  const eventOutputs: ToolMessage[] =
561
689
  eventCalls.length > 0
562
690
  ? await this.dispatchToolEvents(eventCalls, config)
@@ -567,6 +695,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
567
695
  outputs = await Promise.all(
568
696
  filteredCalls.map((call) => this.runTool(call, config))
569
697
  );
698
+ this.handleRunToolCompletions(filteredCalls, outputs, config);
570
699
  }
571
700
  }
572
701
 
@@ -4,7 +4,7 @@ import { nanoid } from 'nanoid';
4
4
  import { ToolMessage } from '@langchain/core/messages';
5
5
  import type { AnthropicWebSearchResultBlockParam } from '@/llm/anthropic/types';
6
6
  import type { ToolCall, ToolCallChunk } from '@langchain/core/messages/tool';
7
- import type { MultiAgentGraph, StandardGraph } from '@/graphs';
7
+ import type { Graph, MultiAgentGraph, StandardGraph } from '@/graphs';
8
8
  import type { AgentContext } from '@/agents/AgentContext';
9
9
  import type * as t from '@/types';
10
10
  import {
@@ -127,7 +127,7 @@ export async function handleToolCallChunks({
127
127
  export const handleToolCalls = async (
128
128
  toolCalls?: ToolCall[],
129
129
  metadata?: Record<string, unknown>,
130
- graph?: StandardGraph | MultiAgentGraph
130
+ graph?: Graph | StandardGraph | MultiAgentGraph
131
131
  ): Promise<void> => {
132
132
  if (!graph || !metadata) {
133
133
  console.warn('Graph or metadata not found in `handleToolCalls`');