@librechat/agents 3.3.13 → 3.4.1

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 (192) hide show
  1. package/dist/cjs/graphs/Graph.cjs +121 -27
  2. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  3. package/dist/cjs/hooks/HookRegistry.cjs +83 -0
  4. package/dist/cjs/hooks/HookRegistry.cjs.map +1 -1
  5. package/dist/cjs/hooks/executeHooks.cjs +60 -14
  6. package/dist/cjs/hooks/executeHooks.cjs.map +1 -1
  7. package/dist/cjs/hooks/index.cjs.map +1 -1
  8. package/dist/cjs/hooks/types.cjs +2 -0
  9. package/dist/cjs/hooks/types.cjs.map +1 -1
  10. package/dist/cjs/llm/anthropic/index.cjs +35 -206
  11. package/dist/cjs/llm/anthropic/index.cjs.map +1 -1
  12. package/dist/cjs/llm/bedrock/index.cjs +121 -241
  13. package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
  14. package/dist/cjs/llm/google/index.cjs +19 -12
  15. package/dist/cjs/llm/google/index.cjs.map +1 -1
  16. package/dist/cjs/llm/init.cjs +3 -3
  17. package/dist/cjs/llm/invoke.cjs +2 -2
  18. package/dist/cjs/llm/mistral/index.cjs +26 -0
  19. package/dist/cjs/llm/mistral/index.cjs.map +1 -0
  20. package/dist/cjs/llm/openai/index.cjs +82 -80
  21. package/dist/cjs/llm/openai/index.cjs.map +1 -1
  22. package/dist/cjs/llm/openrouter/index.cjs.map +1 -1
  23. package/dist/cjs/llm/providers.cjs +19 -19
  24. package/dist/cjs/llm/providers.cjs.map +1 -1
  25. package/dist/cjs/llm/stream/chunkAdapters.cjs +198 -0
  26. package/dist/cjs/llm/stream/chunkAdapters.cjs.map +1 -0
  27. package/dist/cjs/llm/stream/smoother.cjs +369 -0
  28. package/dist/cjs/llm/stream/smoother.cjs.map +1 -0
  29. package/dist/cjs/llm/vertexai/index.cjs +13 -1
  30. package/dist/cjs/llm/vertexai/index.cjs.map +1 -1
  31. package/dist/cjs/main.cjs +25 -16
  32. package/dist/cjs/messages/format.cjs +40 -23
  33. package/dist/cjs/messages/format.cjs.map +1 -1
  34. package/dist/cjs/run.cjs +82 -15
  35. package/dist/cjs/run.cjs.map +1 -1
  36. package/dist/cjs/session/AgentSession.cjs +37 -2
  37. package/dist/cjs/session/AgentSession.cjs.map +1 -1
  38. package/dist/cjs/stream.cjs +1 -1
  39. package/dist/cjs/summarization/node.cjs +6 -3
  40. package/dist/cjs/summarization/node.cjs.map +1 -1
  41. package/dist/cjs/tools/BashExecutor.cjs +1 -1
  42. package/dist/cjs/tools/CodeExecutor.cjs +1 -1
  43. package/dist/cjs/tools/ProgrammaticToolCalling.cjs +1 -1
  44. package/dist/cjs/tools/ToolNode.cjs +316 -94
  45. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  46. package/dist/cjs/tools/ToolSearch.cjs +1 -1
  47. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +664 -80
  48. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
  49. package/dist/cjs/tools/subagent/SubagentReplay.cjs +175 -0
  50. package/dist/cjs/tools/subagent/SubagentReplay.cjs.map +1 -0
  51. package/dist/cjs/tools/toolOutputReferences.cjs +20 -0
  52. package/dist/cjs/tools/toolOutputReferences.cjs.map +1 -1
  53. package/dist/cjs/utils/index.cjs +2 -2
  54. package/dist/cjs/utils/tokens.cjs +76 -29
  55. package/dist/cjs/utils/tokens.cjs.map +1 -1
  56. package/dist/esm/graphs/Graph.mjs +121 -27
  57. package/dist/esm/graphs/Graph.mjs.map +1 -1
  58. package/dist/esm/hooks/HookRegistry.mjs +83 -0
  59. package/dist/esm/hooks/HookRegistry.mjs.map +1 -1
  60. package/dist/esm/hooks/executeHooks.mjs +60 -14
  61. package/dist/esm/hooks/executeHooks.mjs.map +1 -1
  62. package/dist/esm/hooks/index.mjs.map +1 -1
  63. package/dist/esm/hooks/types.mjs +2 -1
  64. package/dist/esm/hooks/types.mjs.map +1 -1
  65. package/dist/esm/llm/anthropic/index.mjs +34 -205
  66. package/dist/esm/llm/anthropic/index.mjs.map +1 -1
  67. package/dist/esm/llm/bedrock/index.mjs +120 -240
  68. package/dist/esm/llm/bedrock/index.mjs.map +1 -1
  69. package/dist/esm/llm/google/index.mjs +19 -12
  70. package/dist/esm/llm/google/index.mjs.map +1 -1
  71. package/dist/esm/llm/init.mjs +1 -1
  72. package/dist/esm/llm/invoke.mjs +2 -2
  73. package/dist/esm/llm/mistral/index.mjs +26 -0
  74. package/dist/esm/llm/mistral/index.mjs.map +1 -0
  75. package/dist/esm/llm/openai/index.mjs +82 -80
  76. package/dist/esm/llm/openai/index.mjs.map +1 -1
  77. package/dist/esm/llm/openrouter/index.mjs.map +1 -1
  78. package/dist/esm/llm/providers.mjs +3 -3
  79. package/dist/esm/llm/providers.mjs.map +1 -1
  80. package/dist/esm/llm/stream/chunkAdapters.mjs +195 -0
  81. package/dist/esm/llm/stream/chunkAdapters.mjs.map +1 -0
  82. package/dist/esm/llm/stream/smoother.mjs +365 -0
  83. package/dist/esm/llm/stream/smoother.mjs.map +1 -0
  84. package/dist/esm/llm/vertexai/index.mjs +13 -1
  85. package/dist/esm/llm/vertexai/index.mjs.map +1 -1
  86. package/dist/esm/main.mjs +13 -11
  87. package/dist/esm/messages/format.mjs +40 -23
  88. package/dist/esm/messages/format.mjs.map +1 -1
  89. package/dist/esm/run.mjs +83 -16
  90. package/dist/esm/run.mjs.map +1 -1
  91. package/dist/esm/session/AgentSession.mjs +37 -2
  92. package/dist/esm/session/AgentSession.mjs.map +1 -1
  93. package/dist/esm/stream.mjs +1 -1
  94. package/dist/esm/summarization/node.mjs +6 -3
  95. package/dist/esm/summarization/node.mjs.map +1 -1
  96. package/dist/esm/tools/BashExecutor.mjs +1 -1
  97. package/dist/esm/tools/CodeExecutor.mjs +1 -1
  98. package/dist/esm/tools/ProgrammaticToolCalling.mjs +1 -1
  99. package/dist/esm/tools/ToolNode.mjs +317 -95
  100. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  101. package/dist/esm/tools/ToolSearch.mjs +1 -1
  102. package/dist/esm/tools/subagent/SubagentExecutor.mjs +665 -81
  103. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
  104. package/dist/esm/tools/subagent/SubagentReplay.mjs +168 -0
  105. package/dist/esm/tools/subagent/SubagentReplay.mjs.map +1 -0
  106. package/dist/esm/tools/toolOutputReferences.mjs +20 -0
  107. package/dist/esm/tools/toolOutputReferences.mjs.map +1 -1
  108. package/dist/esm/utils/index.mjs +2 -2
  109. package/dist/esm/utils/tokens.mjs +76 -30
  110. package/dist/esm/utils/tokens.mjs.map +1 -1
  111. package/dist/types/graphs/Graph.d.ts +19 -1
  112. package/dist/types/hooks/HookRegistry.d.ts +10 -1
  113. package/dist/types/hooks/executeHooks.d.ts +5 -1
  114. package/dist/types/hooks/index.d.ts +2 -2
  115. package/dist/types/hooks/types.d.ts +12 -0
  116. package/dist/types/index.d.ts +3 -0
  117. package/dist/types/llm/google/index.d.ts +2 -0
  118. package/dist/types/llm/mistral/index.d.ts +11 -0
  119. package/dist/types/llm/openai/index.d.ts +20 -4
  120. package/dist/types/llm/openrouter/index.d.ts +4 -1
  121. package/dist/types/llm/stream/chunkAdapters.d.ts +48 -0
  122. package/dist/types/llm/stream/smoother.d.ts +95 -0
  123. package/dist/types/llm/vertexai/index.d.ts +2 -0
  124. package/dist/types/run.d.ts +4 -1
  125. package/dist/types/session/AgentSession.d.ts +1 -0
  126. package/dist/types/tools/ToolNode.d.ts +23 -15
  127. package/dist/types/tools/subagent/SubagentExecutor.d.ts +49 -7
  128. package/dist/types/tools/subagent/SubagentReplay.d.ts +81 -0
  129. package/dist/types/tools/toolOutputReferences.d.ts +12 -0
  130. package/dist/types/types/graph.d.ts +5 -5
  131. package/dist/types/types/hitl.d.ts +15 -0
  132. package/dist/types/types/llm.d.ts +21 -14
  133. package/dist/types/utils/tokens.d.ts +10 -0
  134. package/package.json +6 -2
  135. package/src/graphs/Graph.ts +257 -52
  136. package/src/graphs/__tests__/Graph.breakerLifecycle.test.ts +48 -9
  137. package/src/graphs/__tests__/Graph.contextOverflow.test.ts +1 -1
  138. package/src/graphs/__tests__/Graph.subagentResumeState.test.ts +80 -0
  139. package/src/hooks/HookRegistry.ts +151 -1
  140. package/src/hooks/__tests__/HookRegistry.test.ts +96 -0
  141. package/src/hooks/__tests__/executeHooks.test.ts +160 -0
  142. package/src/hooks/executeHooks.ts +133 -15
  143. package/src/hooks/index.ts +3 -1
  144. package/src/hooks/types.ts +16 -0
  145. package/src/index.ts +8 -0
  146. package/src/llm/anthropic/index.ts +85 -354
  147. package/src/llm/bedrock/index.ts +174 -390
  148. package/src/llm/bedrock/llm.spec.ts +2 -0
  149. package/src/llm/bedrock/streamSealDispatch.test.ts +76 -0
  150. package/src/llm/custom-chat-models.smoke.test.ts +16 -1
  151. package/src/llm/google/index.ts +17 -18
  152. package/src/llm/google/streamSmoothing.test.ts +121 -0
  153. package/src/llm/mistral/index.ts +33 -0
  154. package/src/llm/mistral/streamSmoothing.test.ts +97 -0
  155. package/src/llm/openai/deepseek.test.ts +56 -0
  156. package/src/llm/openai/index.ts +119 -126
  157. package/src/llm/openrouter/index.ts +4 -1
  158. package/src/llm/providers.ts +3 -3
  159. package/src/llm/stream/chunkAdapters.test.ts +202 -0
  160. package/src/llm/stream/chunkAdapters.ts +317 -0
  161. package/src/llm/stream/reassembly.test.ts +241 -0
  162. package/src/llm/stream/smoother.bench.test.ts +155 -0
  163. package/src/llm/stream/smoother.test.ts +519 -0
  164. package/src/llm/stream/smoother.ts +574 -0
  165. package/src/llm/vertexai/index.ts +17 -1
  166. package/src/llm/vertexai/streamSmoothing.test.ts +109 -0
  167. package/src/messages/format.ts +54 -26
  168. package/src/messages/formatAgentMessages.reducer.test.ts +162 -0
  169. package/src/messages/formatAgentMessages.steer.test.ts +16 -11
  170. package/src/messages/formatAgentMessages.test.ts +7 -2
  171. package/src/run.ts +164 -18
  172. package/src/session/AgentSession.ts +52 -2
  173. package/src/session/__tests__/JsonlSessionStore.test.ts +53 -0
  174. package/src/specs/subagent.test.ts +44 -0
  175. package/src/specs/tokens.test.ts +159 -14
  176. package/src/summarization/__tests__/node.test.ts +32 -14
  177. package/src/summarization/node.ts +15 -11
  178. package/src/tools/ToolNode.ts +631 -157
  179. package/src/tools/__tests__/SubagentExecutor.test.ts +590 -7
  180. package/src/tools/__tests__/SubagentReplay.test.ts +300 -0
  181. package/src/tools/__tests__/ToolNode.breakerSignal.test.ts +208 -2
  182. package/src/tools/__tests__/directToolHITLResumeScope.test.ts +486 -2
  183. package/src/tools/__tests__/hitl.test.ts +234 -5
  184. package/src/tools/__tests__/subagentHooks.test.ts +1040 -62
  185. package/src/tools/__tests__/toolOutputReferences.test.ts +19 -2
  186. package/src/tools/subagent/SubagentExecutor.ts +1438 -136
  187. package/src/tools/subagent/SubagentReplay.ts +575 -0
  188. package/src/tools/toolOutputReferences.ts +40 -1
  189. package/src/types/graph.ts +5 -5
  190. package/src/types/hitl.ts +16 -0
  191. package/src/types/llm.ts +53 -36
  192. package/src/utils/tokens.ts +115 -30
@@ -33,6 +33,7 @@ import {
33
33
  import type { CallbackManagerForLLMRun } from '@langchain/core/callbacks/manager';
34
34
  import type { BaseMessage, ResponseMetadata } from '@langchain/core/messages';
35
35
  import type { ChatBedrockConverseInput } from '@langchain/aws';
36
+ import type { SmoothItem } from '@/llm/stream/smoother';
36
37
  import type { ContentBlockDeltaEvent } from './types';
37
38
  import {
38
39
  convertToConverseMessages,
@@ -46,6 +47,7 @@ import {
46
47
  supportsBedrockToolCache,
47
48
  type PromptCacheTtl,
48
49
  } from '@/messages/cache';
50
+ import { smoothStream, resolveStreamDelay, isSignalAborted } from '@/llm/stream/smoother';
49
51
  import { linkStreamLimitCanonical } from '@/llm/streamLimits';
50
52
  import { applyCachePointsToConversePayload } from './cachePoints';
51
53
  import { insertBedrockToolCachePoint } from './toolCache';
@@ -60,50 +62,12 @@ export type ServiceTierType = 'priority' | 'default' | 'flex' | 'reserved';
60
62
  export type CustomGuardrailConfiguration = GuardrailConfiguration &
61
63
  Pick<GuardrailStreamConfiguration, 'streamProcessingMode'>;
62
64
 
63
- const MAX_STREAM_QUEUE_CHUNKS = 256;
64
- const MAX_STREAM_QUEUE_TEXT_CHARS = 8192;
65
- const STREAM_CHUNK_MIN_SIZE = 4;
66
- const STREAM_BOUNDARIES = new Set([' ', '.', ',', '!', '?', ';', ':']);
67
-
68
- type QueuedGenerationChunk = {
65
+ type BedrockEmittedChunk = {
69
66
  chunk: ChatGenerationChunk;
70
67
  callbackChunk?: ChatGenerationChunk;
71
68
  callbackToken: string;
72
- smooth: boolean;
73
- textLength: number;
74
69
  };
75
70
 
76
- function findStreamChunkBoundary(text: string, minSize: number): number {
77
- if (minSize >= text.length) {
78
- return text.length;
79
- }
80
-
81
- for (let position = minSize; position < text.length; position++) {
82
- if (STREAM_BOUNDARIES.has(text[position])) {
83
- return position + 1;
84
- }
85
- }
86
-
87
- return text.length;
88
- }
89
-
90
- function splitStreamToken(text: string): string[] {
91
- const chunks: string[] = [];
92
- let currentIndex = 0;
93
-
94
- while (currentIndex < text.length) {
95
- const remainingText = text.slice(currentIndex);
96
- const chunkSize = findStreamChunkBoundary(
97
- remainingText,
98
- STREAM_CHUNK_MIN_SIZE
99
- );
100
- chunks.push(text.slice(currentIndex, currentIndex + chunkSize));
101
- currentIndex += chunkSize;
102
- }
103
-
104
- return chunks;
105
- }
106
-
107
71
  /**
108
72
  * Resolves the text a delta contributes to the smoothing cadence, preferring a
109
73
  * text delta over a reasoning delta and ignoring non-string payloads.
@@ -120,52 +84,6 @@ function resolveVisibleText(text?: string, reasoningText?: string): string {
120
84
  return '';
121
85
  }
122
86
 
123
- function getCadencedStreamDelay({
124
- targetDelay,
125
- lastVisibleContentAt,
126
- now,
127
- }: {
128
- targetDelay: number;
129
- lastVisibleContentAt?: number;
130
- now: number;
131
- }): number {
132
- if (targetDelay <= 0 || lastVisibleContentAt == null) {
133
- return 0;
134
- }
135
- return Math.max(0, targetDelay - (now - lastVisibleContentAt));
136
- }
137
-
138
- async function waitForStreamDelay(
139
- delay: number,
140
- signal?: AbortSignal
141
- ): Promise<void> {
142
- if (delay <= 0 || isSignalAborted(signal)) {
143
- return;
144
- }
145
- await new Promise<void>((resolve) => {
146
- const timeoutRef: { current?: ReturnType<typeof setTimeout> } = {};
147
- const onAbort = (): void => {
148
- if (timeoutRef.current) {
149
- clearTimeout(timeoutRef.current);
150
- }
151
- signal?.removeEventListener('abort', onAbort);
152
- resolve();
153
- };
154
- timeoutRef.current = setTimeout(() => {
155
- signal?.removeEventListener('abort', onAbort);
156
- resolve();
157
- }, delay);
158
- signal?.addEventListener('abort', onAbort, { once: true });
159
- if (isSignalAborted(signal)) {
160
- onAbort();
161
- }
162
- });
163
- }
164
-
165
- function isSignalAborted(signal?: AbortSignal): boolean {
166
- return signal?.aborted === true;
167
- }
168
-
169
87
  /**
170
88
  * Extended input interface with additional features:
171
89
  * - applicationInferenceProfile: Use an inference profile ARN instead of model ID
@@ -267,7 +185,7 @@ export class CustomChatBedrockConverse extends ChatBedrockConverse {
267
185
  super(fields);
268
186
  this.promptCache = fields?.promptCache;
269
187
  this.promptCacheTtl = fields?.promptCacheTtl;
270
- this._lc_stream_delay = Math.max(0, fields?._lc_stream_delay ?? 0);
188
+ this._lc_stream_delay = resolveStreamDelay(fields?._lc_stream_delay);
271
189
  this.applicationInferenceProfile = fields?.applicationInferenceProfile;
272
190
  this.serviceTier = fields?.serviceTier;
273
191
  // `super(fields)` initializes `this.model` to LangChain's default Claude
@@ -398,15 +316,6 @@ export class CustomChatBedrockConverse extends ChatBedrockConverse {
398
316
 
399
317
  const seenBlockIndices = new Set<number>();
400
318
  const toolUseBlockIndices = new Set<number>();
401
- const queuedChunks: QueuedGenerationChunk[] = [];
402
- const producerState: { done: boolean; error?: unknown } = { done: false };
403
- let queuedChunkIndex = 0;
404
- let bufferedTextLength = 0;
405
- let consumerClosed = false;
406
- let notifyConsumer: (() => void) | undefined;
407
- let notifyProducer: (() => void) | undefined;
408
- let hasEmittedVisibleContent = false;
409
- let lastVisibleContentAt: number | undefined;
410
319
 
411
320
  /**
412
321
  * Guardrails can reject an already-streamed toolUse block at
@@ -417,325 +326,200 @@ export class CustomChatBedrockConverse extends ChatBedrockConverse {
417
326
  const sealToolUseOnStop =
418
327
  options.guardrailConfig == null && this.guardrailConfig == null;
419
328
 
420
- const notifyConsumerForChunk = (): void => {
421
- notifyConsumer?.();
422
- notifyConsumer = undefined;
423
- };
424
-
425
- const notifyProducerForSpace = (): void => {
426
- notifyProducer?.();
427
- notifyProducer = undefined;
428
- };
429
-
430
- const hasQueuedChunks = (): boolean =>
431
- queuedChunkIndex < queuedChunks.length;
432
-
433
- const getQueuedChunkCount = (): number =>
434
- queuedChunks.length - queuedChunkIndex;
435
-
436
- const isQueueAtCapacity = (): boolean =>
437
- getQueuedChunkCount() >= MAX_STREAM_QUEUE_CHUNKS ||
438
- bufferedTextLength >= MAX_STREAM_QUEUE_TEXT_CHARS;
439
-
440
- const waitForNextChunk = async (): Promise<void> => {
441
- if (
442
- hasQueuedChunks() ||
443
- producerState.done ||
444
- producerState.error != null
445
- ) {
446
- return;
447
- }
448
- await new Promise<void>((resolve) => {
449
- notifyConsumer = resolve;
450
- });
451
- };
452
-
453
- const waitForQueueSpace = async (): Promise<void> => {
454
- while (
455
- isQueueAtCapacity() &&
456
- !consumerClosed &&
457
- !isSignalAborted(options.signal)
458
- ) {
459
- await new Promise<void>((resolve) => {
460
- const signal = options.signal;
461
- const onAbort = (): void => {
462
- signal?.removeEventListener('abort', onAbort);
463
- resolve();
464
- };
465
- const onSpace = (): void => {
466
- signal?.removeEventListener('abort', onAbort);
467
- resolve();
468
- };
469
- notifyProducer = onSpace;
470
- signal?.addEventListener('abort', onAbort, { once: true });
471
- if (isSignalAborted(signal)) {
472
- onAbort();
473
- }
474
- });
475
- }
476
- };
477
-
478
- const dequeue = (): QueuedGenerationChunk | undefined => {
479
- if (!hasQueuedChunks()) {
480
- return undefined;
481
- }
482
- const queuedChunk = queuedChunks[queuedChunkIndex];
483
- queuedChunkIndex++;
484
- if (
485
- queuedChunkIndex > 128 &&
486
- queuedChunkIndex * 2 >= queuedChunks.length
487
- ) {
488
- queuedChunks.splice(0, queuedChunkIndex);
489
- queuedChunkIndex = 0;
490
- }
491
- return queuedChunk;
492
- };
493
-
494
- const enqueue = async (
495
- queuedChunk: QueuedGenerationChunk
496
- ): Promise<void> => {
497
- await waitForQueueSpace();
498
- if (consumerClosed || isSignalAborted(options.signal)) {
499
- abortStream();
500
- throw new Error('AbortError: User aborted the request.');
501
- }
502
- queuedChunks.push(queuedChunk);
503
- if (queuedChunk.smooth) {
504
- bufferedTextLength += queuedChunk.textLength;
505
- }
506
- notifyConsumerForChunk();
507
- };
508
-
509
- const enqueueChunk = async ({
510
- chunk,
511
- callbackChunk,
512
- callbackToken = '',
513
- smooth = false,
514
- textLength = 0,
515
- }: {
516
- chunk: ChatGenerationChunk;
517
- callbackChunk?: ChatGenerationChunk;
518
- callbackToken?: string;
519
- smooth?: boolean;
520
- textLength?: number;
521
- }): Promise<void> => {
522
- await enqueue({
523
- chunk,
524
- callbackChunk,
525
- callbackToken,
526
- smooth,
527
- textLength: smooth ? textLength : 0,
528
- });
529
- };
530
-
531
- const enqueueDelta = async (
532
- contentBlockDelta: ContentBlockDeltaEvent
533
- ): Promise<void> => {
329
+ /**
330
+ * Builds the emission for one piece of a delta, reproducing the exact
331
+ * per-piece pipeline (sliced delta → chunk → enrichment → stream-limit
332
+ * link). `indicesSnapshot` is the arrival-time copy of the seen block
333
+ * indices, so lazily emitted pieces observe the same enrichment
334
+ * decisions the delta saw when it arrived.
335
+ */
336
+ const buildDeltaEmission = (
337
+ contentBlockDelta: ContentBlockDeltaEvent,
338
+ token: string,
339
+ indicesSnapshot: Set<number>
340
+ ): BedrockEmittedChunk => {
534
341
  const delta = contentBlockDelta.delta;
535
- if (delta == null) {
536
- throw new Error('No delta found in content block.');
537
- }
538
-
539
- const idx = contentBlockDelta.contentBlockIndex;
540
- if (idx != null) {
541
- seenBlockIndices.add(idx);
542
- }
543
-
544
- const text = delta.text;
545
- const reasoningContent = delta.reasoningContent;
342
+ const text = delta?.text;
343
+ const reasoningContent = delta?.reasoningContent;
546
344
  const reasoningText = reasoningContent?.text;
547
- const visibleText = resolveVisibleText(text, reasoningText);
548
- const smooth = this._lc_stream_delay > 0 && visibleText !== '';
549
- const tokenChunks = smooth
550
- ? splitStreamToken(visibleText)
551
- : [visibleText];
552
-
553
- for (const token of tokenChunks) {
554
- let splitDelta = contentBlockDelta;
555
- if (typeof text === 'string') {
556
- splitDelta = {
557
- ...contentBlockDelta,
558
- delta: { text: token },
559
- };
560
- } else if (
561
- typeof reasoningText === 'string' &&
562
- reasoningContent != null
563
- ) {
564
- splitDelta = {
565
- ...contentBlockDelta,
566
- delta: {
567
- reasoningContent: {
568
- ...reasoningContent,
569
- text: token,
570
- },
345
+
346
+ let splitDelta = contentBlockDelta;
347
+ if (typeof text === 'string') {
348
+ splitDelta = {
349
+ ...contentBlockDelta,
350
+ delta: { text: token },
351
+ };
352
+ } else if (
353
+ typeof reasoningText === 'string' &&
354
+ reasoningContent != null
355
+ ) {
356
+ splitDelta = {
357
+ ...contentBlockDelta,
358
+ delta: {
359
+ reasoningContent: {
360
+ ...reasoningContent,
361
+ text: token,
571
362
  },
572
- };
573
- }
363
+ },
364
+ };
365
+ }
574
366
 
575
- const deltaChunk = handleConverseStreamContentBlockDelta(splitDelta);
576
- const enrichedChunk = this.enrichChunk(deltaChunk, seenBlockIndices);
577
- if (enrichedChunk !== deltaChunk) {
578
- /** The callback copy is the same emission as the enriched yield;
579
- * without the link, stream-limit accounting charges both message
580
- * objects and Bedrock tool arguments falsely trip near half the
581
- * cap. Linked on the messages, which are what the accounting
582
- * observes. */
583
- linkStreamLimitCanonical(deltaChunk.message, enrichedChunk.message);
584
- }
585
- await enqueueChunk({
586
- chunk: enrichedChunk,
587
- callbackChunk: deltaChunk,
588
- callbackToken: deltaChunk.text,
589
- smooth,
590
- textLength: token.length,
591
- });
367
+ const deltaChunk = handleConverseStreamContentBlockDelta(splitDelta);
368
+ const enrichedChunk = this.enrichChunk(deltaChunk, indicesSnapshot);
369
+ if (enrichedChunk !== deltaChunk) {
370
+ /** The callback copy is the same emission as the enriched yield;
371
+ * without the link, stream-limit accounting charges both message
372
+ * objects and Bedrock tool arguments falsely trip near half the
373
+ * cap. Linked on the messages, which are what the accounting
374
+ * observes. */
375
+ linkStreamLimitCanonical(deltaChunk.message, enrichedChunk.message);
592
376
  }
377
+ return {
378
+ chunk: enrichedChunk,
379
+ callbackChunk: deltaChunk,
380
+ callbackToken: deltaChunk.text,
381
+ };
593
382
  };
594
383
 
595
- const producer = (async (): Promise<void> => {
596
- try {
597
- for await (const event of stream) {
598
- if (isSignalAborted(options.signal)) {
599
- abortStream();
600
- throw new Error('AbortError: User aborted the request.');
601
- }
602
-
603
- if (event.contentBlockStart != null) {
604
- const startChunk = handleConverseStreamContentBlockStart(
605
- event.contentBlockStart
606
- );
607
- if (startChunk != null) {
608
- const idx = event.contentBlockStart.contentBlockIndex;
609
- if (idx != null) {
610
- seenBlockIndices.add(idx);
611
- if (event.contentBlockStart.start?.toolUse != null) {
612
- toolUseBlockIndices.add(idx);
613
- }
384
+ const enrichChunk = this.enrichChunk.bind(this);
385
+ const source = (async function* (): AsyncGenerator<
386
+ SmoothItem<BedrockEmittedChunk>
387
+ > {
388
+ for await (const event of stream) {
389
+ if (event.contentBlockStart != null) {
390
+ const startChunk = handleConverseStreamContentBlockStart(
391
+ event.contentBlockStart
392
+ );
393
+ if (startChunk != null) {
394
+ const idx = event.contentBlockStart.contentBlockIndex;
395
+ if (idx != null) {
396
+ seenBlockIndices.add(idx);
397
+ if (event.contentBlockStart.start?.toolUse != null) {
398
+ toolUseBlockIndices.add(idx);
614
399
  }
615
- const enrichedStart = this.enrichChunk(
616
- startChunk,
617
- seenBlockIndices
400
+ }
401
+ const enrichedStart = enrichChunk(startChunk, seenBlockIndices);
402
+ if (enrichedStart !== startChunk) {
403
+ linkStreamLimitCanonical(
404
+ startChunk.message,
405
+ enrichedStart.message
618
406
  );
619
- if (enrichedStart !== startChunk) {
620
- linkStreamLimitCanonical(
621
- startChunk.message,
622
- enrichedStart.message
623
- );
624
- }
625
- await enqueueChunk({
407
+ }
408
+ yield {
409
+ text: '',
410
+ smooth: false,
411
+ emit: (): BedrockEmittedChunk => ({
626
412
  chunk: enrichedStart,
627
413
  callbackChunk: startChunk,
628
414
  callbackToken: startChunk.text,
629
- });
630
- }
631
- } else if (event.contentBlockDelta != null) {
632
- await enqueueDelta(event.contentBlockDelta);
633
- } else if (event.metadata != null) {
634
- await enqueueChunk({
635
- chunk: handleConverseStreamMetadata(event.metadata, {
636
- streamUsage,
637
415
  }),
638
- });
639
- } else if (event.contentBlockStop != null) {
640
- const stopIdx = event.contentBlockStop.contentBlockIndex;
641
- if (stopIdx != null) {
642
- seenBlockIndices.add(stopIdx);
643
- if (sealToolUseOnStop && toolUseBlockIndices.has(stopIdx)) {
644
- const sealChunk = createConverseToolUseStopChunk(stopIdx);
645
- await enqueueChunk({
646
- chunk: sealChunk,
647
- callbackChunk: sealChunk,
648
- callbackToken: sealChunk.text,
649
- });
650
- }
651
- }
652
- } else {
653
- await enqueueChunk({
654
- chunk: new ChatGenerationChunk({
655
- text: '',
656
- message: new AIMessageChunk({
657
- content: '',
658
- response_metadata: { ...event } as ResponseMetadata,
659
- }),
660
- }),
661
- });
416
+ };
662
417
  }
663
- }
664
- } catch (error) {
665
- producerState.error = error;
666
- } finally {
667
- producerState.done = true;
668
- notifyConsumerForChunk();
669
- }
670
- })();
671
-
672
- try {
673
- let keepStreaming = true;
674
- while (keepStreaming) {
675
- if (isSignalAborted(options.signal)) {
676
- abortStream();
677
- throw new Error('AbortError: User aborted the request.');
678
- }
679
-
680
- await waitForNextChunk();
681
- const queuedChunk = dequeue();
682
-
683
- if (!queuedChunk) {
684
- if (producerState.error != null) {
685
- throw producerState.error;
418
+ } else if (event.contentBlockDelta != null) {
419
+ const contentBlockDelta = event.contentBlockDelta;
420
+ const delta = contentBlockDelta.delta;
421
+ if (delta == null) {
422
+ throw new Error('No delta found in content block.');
686
423
  }
687
- if (producerState.done) {
688
- keepStreaming = false;
424
+
425
+ const idx = contentBlockDelta.contentBlockIndex;
426
+ if (idx != null) {
427
+ seenBlockIndices.add(idx);
689
428
  }
690
- continue;
691
- }
692
429
 
693
- if (queuedChunk.smooth) {
694
- bufferedTextLength = Math.max(
695
- 0,
696
- bufferedTextLength - queuedChunk.textLength
430
+ const visibleText = resolveVisibleText(
431
+ delta.text,
432
+ delta.reasoningContent?.text
697
433
  );
698
- notifyProducerForSpace();
699
- await waitForStreamDelay(
700
- getCadencedStreamDelay({
701
- targetDelay: hasEmittedVisibleContent
702
- ? this._lc_stream_delay
703
- : 0,
704
- lastVisibleContentAt,
705
- now: Date.now(),
706
- }),
707
- options.signal
708
- );
709
- if (isSignalAborted(options.signal)) {
710
- abortStream();
711
- throw new Error('AbortError: User aborted the request.');
434
+ const indicesSnapshot = new Set(seenBlockIndices);
435
+
436
+ if (visibleText === '') {
437
+ yield {
438
+ text: '',
439
+ smooth: false,
440
+ emit: (): BedrockEmittedChunk =>
441
+ buildDeltaEmission(
442
+ contentBlockDelta,
443
+ visibleText,
444
+ indicesSnapshot
445
+ ),
446
+ };
447
+ continue;
448
+ }
449
+
450
+ yield {
451
+ text: visibleText,
452
+ smooth: true,
453
+ emit: (piece): BedrockEmittedChunk =>
454
+ buildDeltaEmission(
455
+ contentBlockDelta,
456
+ piece.text,
457
+ indicesSnapshot
458
+ ),
459
+ };
460
+ } else if (event.metadata != null) {
461
+ const metadataChunk = handleConverseStreamMetadata(event.metadata, {
462
+ streamUsage,
463
+ });
464
+ yield {
465
+ text: '',
466
+ smooth: false,
467
+ emit: (): BedrockEmittedChunk => ({ chunk: metadataChunk, callbackToken: '' }),
468
+ };
469
+ } else if (event.contentBlockStop != null) {
470
+ const stopIdx = event.contentBlockStop.contentBlockIndex;
471
+ if (stopIdx != null) {
472
+ seenBlockIndices.add(stopIdx);
473
+ if (sealToolUseOnStop && toolUseBlockIndices.has(stopIdx)) {
474
+ const sealChunk = createConverseToolUseStopChunk(stopIdx);
475
+ yield {
476
+ text: '',
477
+ smooth: false,
478
+ emit: (): BedrockEmittedChunk => ({
479
+ chunk: sealChunk,
480
+ callbackChunk: sealChunk,
481
+ callbackToken: sealChunk.text,
482
+ }),
483
+ };
484
+ }
712
485
  }
713
- hasEmittedVisibleContent = true;
714
- lastVisibleContentAt = Date.now();
715
486
  } else {
716
- notifyProducerForSpace();
487
+ const eventChunk = new ChatGenerationChunk({
488
+ text: '',
489
+ message: new AIMessageChunk({
490
+ content: '',
491
+ response_metadata: { ...event } as ResponseMetadata,
492
+ }),
493
+ });
494
+ yield {
495
+ text: '',
496
+ smooth: false,
497
+ emit: (): BedrockEmittedChunk => ({ chunk: eventChunk, callbackToken: '' }),
498
+ };
717
499
  }
500
+ }
501
+ })();
718
502
 
719
- yield queuedChunk.chunk;
503
+ const smoothed = smoothStream({
504
+ source,
505
+ delayMs: this._lc_stream_delay,
506
+ signal: options.signal,
507
+ abortUpstream: abortStream,
508
+ });
720
509
 
721
- if (queuedChunk.callbackChunk != null) {
722
- await runManager?.handleLLMNewToken(
723
- queuedChunk.callbackToken,
724
- undefined,
725
- undefined,
726
- undefined,
727
- undefined,
728
- { chunk: queuedChunk.callbackChunk }
729
- );
730
- }
731
- }
732
- } finally {
733
- consumerClosed = true;
734
- if (!producerState.done) {
735
- abortStream();
736
- notifyProducerForSpace();
510
+ for await (const emitted of smoothed) {
511
+ yield emitted.chunk;
512
+
513
+ if (emitted.callbackChunk != null) {
514
+ await runManager?.handleLLMNewToken(
515
+ emitted.callbackToken,
516
+ undefined,
517
+ undefined,
518
+ undefined,
519
+ undefined,
520
+ { chunk: emitted.callbackChunk }
521
+ );
737
522
  }
738
- await producer;
739
523
  }
740
524
  } finally {
741
525
  options.signal?.removeEventListener('abort', abortStream);
@@ -233,6 +233,7 @@ describe('CustomChatBedrockConverse', () => {
233
233
  model: 'anthropic.claude-3-haiku-20240307-v1:0',
234
234
  applicationInferenceProfile: testArn,
235
235
  client: mockClient,
236
+ _lc_stream_delay: 0,
236
237
  });
237
238
 
238
239
  let chunks = 0;
@@ -281,6 +282,7 @@ describe('CustomChatBedrockConverse', () => {
281
282
  model: 'anthropic.claude-3-haiku-20240307-v1:0',
282
283
  guardrailConfig,
283
284
  client: mockClient,
285
+ _lc_stream_delay: 0,
284
286
  });
285
287
 
286
288
  let chunks = 0;