@librechat/agents 3.9.1 → 3.9.3

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 (65) hide show
  1. package/dist/cjs/graphs/Graph.cjs +13 -4
  2. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  3. package/dist/cjs/graphs/MultiAgentGraph.cjs +97 -5
  4. package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
  5. package/dist/cjs/graphs/handoff.cjs +195 -0
  6. package/dist/cjs/graphs/handoff.cjs.map +1 -0
  7. package/dist/cjs/graphs/index.cjs +1 -0
  8. package/dist/cjs/main.cjs +3 -0
  9. package/dist/cjs/messages/contextPruning.cjs +2 -1
  10. package/dist/cjs/messages/contextPruning.cjs.map +1 -1
  11. package/dist/cjs/messages/prune.cjs +3 -3
  12. package/dist/cjs/messages/prune.cjs.map +1 -1
  13. package/dist/cjs/run.cjs +33 -3
  14. package/dist/cjs/run.cjs.map +1 -1
  15. package/dist/cjs/tools/ToolNode.cjs +4 -1
  16. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  17. package/dist/cjs/utils/toolContent.cjs +6 -6
  18. package/dist/cjs/utils/toolContent.cjs.map +1 -1
  19. package/dist/cjs/utils/truncation.cjs +14 -5
  20. package/dist/cjs/utils/truncation.cjs.map +1 -1
  21. package/dist/esm/graphs/Graph.mjs +13 -4
  22. package/dist/esm/graphs/Graph.mjs.map +1 -1
  23. package/dist/esm/graphs/MultiAgentGraph.mjs +97 -5
  24. package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
  25. package/dist/esm/graphs/handoff.mjs +193 -0
  26. package/dist/esm/graphs/handoff.mjs.map +1 -0
  27. package/dist/esm/graphs/index.mjs +1 -0
  28. package/dist/esm/main.mjs +3 -2
  29. package/dist/esm/messages/contextPruning.mjs +2 -1
  30. package/dist/esm/messages/contextPruning.mjs.map +1 -1
  31. package/dist/esm/messages/prune.mjs +4 -4
  32. package/dist/esm/messages/prune.mjs.map +1 -1
  33. package/dist/esm/run.mjs +33 -3
  34. package/dist/esm/run.mjs.map +1 -1
  35. package/dist/esm/tools/ToolNode.mjs +4 -1
  36. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  37. package/dist/esm/utils/toolContent.mjs +7 -7
  38. package/dist/esm/utils/toolContent.mjs.map +1 -1
  39. package/dist/esm/utils/truncation.mjs +14 -6
  40. package/dist/esm/utils/truncation.mjs.map +1 -1
  41. package/dist/types/graphs/Graph.d.ts +2 -0
  42. package/dist/types/graphs/MultiAgentGraph.d.ts +3 -0
  43. package/dist/types/graphs/handoff.d.ts +28 -0
  44. package/dist/types/graphs/index.d.ts +1 -0
  45. package/dist/types/run.d.ts +4 -0
  46. package/dist/types/tools/ToolNode.d.ts +2 -1
  47. package/dist/types/types/graph.d.ts +46 -1
  48. package/dist/types/types/run.d.ts +3 -1
  49. package/dist/types/types/tools.d.ts +3 -0
  50. package/dist/types/utils/toolContent.d.ts +1 -1
  51. package/dist/types/utils/truncation.d.ts +2 -0
  52. package/package.json +1 -1
  53. package/src/graphs/Graph.ts +8 -0
  54. package/src/graphs/MultiAgentGraph.ts +142 -4
  55. package/src/graphs/handoff.ts +263 -0
  56. package/src/graphs/index.ts +2 -0
  57. package/src/messages/contextPruning.ts +8 -2
  58. package/src/messages/prune.ts +15 -10
  59. package/src/run.ts +54 -2
  60. package/src/tools/ToolNode.ts +14 -6
  61. package/src/types/graph.ts +44 -1
  62. package/src/types/run.ts +3 -1
  63. package/src/types/tools.ts +3 -0
  64. package/src/utils/toolContent.ts +23 -7
  65. package/src/utils/truncation.ts +31 -6
@@ -3,6 +3,7 @@ import { ToolMessage, type BaseMessage } from '@langchain/core/messages';
3
3
  import {
4
4
  HARD_MAX_TOTAL_TOOL_OUTPUT_SIZE,
5
5
  truncateToolResultContent,
6
+ sliceWithoutSplittingSurrogates,
6
7
  } from './truncation';
7
8
 
8
9
  type ToolContent = BaseMessage['content'];
@@ -421,7 +422,7 @@ type SegmentedStringBuffer = {
421
422
  export type BoundedStructuredSerialization = {
422
423
  /** Provider-facing head/tail preview, bounded by `maxChars`. */
423
424
  content: string;
424
- /** Exact serialized prefix up to the defensive traversal-work ceiling. */
425
+ /** Code-point-aligned serialized prefix up to the traversal-work ceiling. */
425
426
  prefix: string;
426
427
  /** Exact length, or `Number.MAX_SAFE_INTEGER` when traversal was capped. */
427
428
  originalChars: number;
@@ -505,6 +506,10 @@ function materializeSegmentedStringBuffer(
505
506
  return segments.join('');
506
507
  }
507
508
 
509
+ /**
510
+ * Retains exact code-unit prefixes internally. Trimming surrogate edges here
511
+ * would let later chunks fill the gap and corrupt the prefix; trim on exposure.
512
+ */
508
513
  function appendSerializedChunk(
509
514
  collector: StructuredSerializationCollector,
510
515
  chunk: string
@@ -953,7 +958,7 @@ function formatBoundedStructuredContent(
953
958
  suffix: string
954
959
  ): string {
955
960
  if (collector.totalChars <= maxChars) {
956
- return prefix.slice(0, collector.totalChars);
961
+ return sliceWithoutSplittingSurrogates(prefix, 0, collector.totalChars);
957
962
  }
958
963
 
959
964
  const indicator = collector.work.exceeded
@@ -962,10 +967,13 @@ function formatBoundedStructuredContent(
962
967
  `${maxChars} limit] …\n\n`;
963
968
  const available = maxChars - indicator.length;
964
969
  if (available <= 0) {
965
- return prefix.slice(0, maxChars);
970
+ return sliceWithoutSplittingSurrogates(prefix, 0, maxChars);
966
971
  }
967
972
  if (available < 200) {
968
- return prefix.slice(0, available) + indicator.trimEnd();
973
+ return (
974
+ sliceWithoutSplittingSurrogates(prefix, 0, available) +
975
+ indicator.trimEnd()
976
+ );
969
977
  }
970
978
 
971
979
  const headSize = Math.ceil(available * 0.7);
@@ -982,7 +990,11 @@ function formatBoundedStructuredContent(
982
990
  tailStart = tailNewline + 1;
983
991
  }
984
992
 
985
- return prefix.slice(0, headEnd) + indicator + suffix.slice(tailStart);
993
+ return (
994
+ sliceWithoutSplittingSurrogates(prefix, 0, headEnd) +
995
+ indicator +
996
+ sliceWithoutSplittingSurrogates(suffix, tailStart)
997
+ );
986
998
  }
987
999
 
988
1000
  /**
@@ -1036,7 +1048,7 @@ export function serializeStructuredValueBounded(
1036
1048
  prefix,
1037
1049
  suffix
1038
1050
  ),
1039
- prefix: prefix.slice(0, normalizedPrefixChars),
1051
+ prefix: sliceWithoutSplittingSurrogates(prefix, 0, normalizedPrefixChars),
1040
1052
  originalChars,
1041
1053
  truncated:
1042
1054
  collector.work.exceeded || collector.totalChars > normalizedMaxChars,
@@ -1931,7 +1943,11 @@ function serializeDenseTextBlocksWithinLimit(
1931
1943
  }
1932
1944
  preview += textBlocks[i].text.slice(0, available - preview.length);
1933
1945
  }
1934
- return (preview + indicator).slice(0, normalizedMaxChars);
1946
+ return sliceWithoutSplittingSurrogates(
1947
+ sliceWithoutSplittingSurrogates(preview, 0) + indicator,
1948
+ 0,
1949
+ normalizedMaxChars
1950
+ );
1935
1951
  }
1936
1952
 
1937
1953
  export type CompactToolContentResult = {
@@ -5,6 +5,22 @@
5
5
  * consuming the entire context window.
6
6
  */
7
7
 
8
+ /** Slices by UTF-16 code units, dropping orphaned surrogate halves at either edge. */
9
+ export function sliceWithoutSplittingSurrogates(
10
+ value: string,
11
+ start: number,
12
+ end?: number
13
+ ): string {
14
+ const sliced = value.slice(start, end);
15
+ const first = sliced.charCodeAt(0);
16
+ const last = sliced.charCodeAt(sliced.length - 1);
17
+ const trimStart = first >= 0xdc00 && first <= 0xdfff;
18
+ const trimEnd = last >= 0xd800 && last <= 0xdbff;
19
+ return trimStart || trimEnd
20
+ ? sliced.slice(trimStart ? 1 : 0, sliced.length - (trimEnd ? 1 : 0))
21
+ : sliced;
22
+ }
23
+
8
24
  /**
9
25
  * Absolute hard cap on tool result length (characters).
10
26
  * Even if the model has a 1M-token context, a single tool result
@@ -85,7 +101,9 @@ export function truncateToolInput(
85
101
 
86
102
  if (available < 100) {
87
103
  return {
88
- _truncated: serialized.slice(0, maxChars) + indicator.trimEnd(),
104
+ _truncated:
105
+ sliceWithoutSplittingSurrogates(serialized, 0, maxChars) +
106
+ indicator.trimEnd(),
89
107
  _originalChars: serialized.length,
90
108
  };
91
109
  }
@@ -95,9 +113,9 @@ export function truncateToolInput(
95
113
 
96
114
  return {
97
115
  _truncated:
98
- serialized.slice(0, headSize) +
116
+ sliceWithoutSplittingSurrogates(serialized, 0, headSize) +
99
117
  indicator +
100
- serialized.slice(serialized.length - tailSize),
118
+ sliceWithoutSplittingSurrogates(serialized, serialized.length - tailSize),
101
119
  _originalChars: serialized.length,
102
120
  };
103
121
  }
@@ -126,12 +144,15 @@ export function truncateToolResultContent(
126
144
  const indicator = `\n\n… [truncated: ${content.length} chars exceeded ${maxChars} limit] …\n\n`;
127
145
  const available = maxChars - indicator.length;
128
146
  if (available <= 0) {
129
- return content.slice(0, maxChars);
147
+ return sliceWithoutSplittingSurrogates(content, 0, maxChars);
130
148
  }
131
149
 
132
150
  // When budget is too small for a meaningful tail, fall back to head-only
133
151
  if (available < 200) {
134
- return content.slice(0, available) + indicator.trimEnd();
152
+ return (
153
+ sliceWithoutSplittingSurrogates(content, 0, available) +
154
+ indicator.trimEnd()
155
+ );
135
156
  }
136
157
 
137
158
  const headSize = Math.ceil(available * 0.7);
@@ -150,7 +171,11 @@ export function truncateToolResultContent(
150
171
  tailStart = tailNewline + 1;
151
172
  }
152
173
 
153
- return content.slice(0, headEnd) + indicator + content.slice(tailStart);
174
+ return (
175
+ sliceWithoutSplittingSurrogates(content, 0, headEnd) +
176
+ indicator +
177
+ sliceWithoutSplittingSurrogates(content, tailStart)
178
+ );
154
179
  }
155
180
 
156
181
  /** Absolute hard cap on a single tool-call input (characters). */