@librechat/agents 3.2.63 → 3.2.65

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 (67) hide show
  1. package/dist/cjs/graphs/Graph.cjs +3 -0
  2. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  3. package/dist/cjs/llm/anthropic/index.cjs +73 -9
  4. package/dist/cjs/llm/anthropic/index.cjs.map +1 -1
  5. package/dist/cjs/llm/anthropic/types.cjs.map +1 -1
  6. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +41 -9
  7. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
  8. package/dist/cjs/llm/anthropic/utils/message_outputs.cjs +3 -1
  9. package/dist/cjs/llm/anthropic/utils/message_outputs.cjs.map +1 -1
  10. package/dist/cjs/llm/anthropic/utils/stream_events.cjs +337 -0
  11. package/dist/cjs/llm/anthropic/utils/stream_events.cjs.map +1 -0
  12. package/dist/cjs/llm/bedrock/utils/message_inputs.cjs +82 -34
  13. package/dist/cjs/llm/bedrock/utils/message_inputs.cjs.map +1 -1
  14. package/dist/cjs/tools/search/crw-scraper.cjs +165 -0
  15. package/dist/cjs/tools/search/crw-scraper.cjs.map +1 -0
  16. package/dist/cjs/tools/search/crw-search.cjs +105 -0
  17. package/dist/cjs/tools/search/crw-search.cjs.map +1 -0
  18. package/dist/cjs/tools/search/search.cjs +4 -2
  19. package/dist/cjs/tools/search/search.cjs.map +1 -1
  20. package/dist/cjs/tools/search/tool.cjs +15 -3
  21. package/dist/cjs/tools/search/tool.cjs.map +1 -1
  22. package/dist/esm/graphs/Graph.mjs +3 -0
  23. package/dist/esm/graphs/Graph.mjs.map +1 -1
  24. package/dist/esm/llm/anthropic/index.mjs +73 -9
  25. package/dist/esm/llm/anthropic/index.mjs.map +1 -1
  26. package/dist/esm/llm/anthropic/types.mjs.map +1 -1
  27. package/dist/esm/llm/anthropic/utils/message_inputs.mjs +41 -9
  28. package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
  29. package/dist/esm/llm/anthropic/utils/message_outputs.mjs +3 -2
  30. package/dist/esm/llm/anthropic/utils/message_outputs.mjs.map +1 -1
  31. package/dist/esm/llm/anthropic/utils/stream_events.mjs +337 -0
  32. package/dist/esm/llm/anthropic/utils/stream_events.mjs.map +1 -0
  33. package/dist/esm/llm/bedrock/utils/message_inputs.mjs +82 -34
  34. package/dist/esm/llm/bedrock/utils/message_inputs.mjs.map +1 -1
  35. package/dist/esm/tools/search/crw-scraper.mjs +163 -0
  36. package/dist/esm/tools/search/crw-scraper.mjs.map +1 -0
  37. package/dist/esm/tools/search/crw-search.mjs +103 -0
  38. package/dist/esm/tools/search/crw-search.mjs.map +1 -0
  39. package/dist/esm/tools/search/search.mjs +4 -2
  40. package/dist/esm/tools/search/search.mjs.map +1 -1
  41. package/dist/esm/tools/search/tool.mjs +15 -3
  42. package/dist/esm/tools/search/tool.mjs.map +1 -1
  43. package/dist/types/llm/anthropic/index.d.ts +2 -0
  44. package/dist/types/llm/anthropic/types.d.ts +2 -0
  45. package/dist/types/llm/anthropic/utils/message_outputs.d.ts +1 -2
  46. package/dist/types/llm/anthropic/utils/stream_events.d.ts +25 -0
  47. package/dist/types/tools/search/crw-scraper.d.ts +41 -0
  48. package/dist/types/tools/search/crw-search.d.ts +4 -0
  49. package/dist/types/tools/search/types.d.ts +88 -3
  50. package/package.json +1 -1
  51. package/src/graphs/Graph.ts +15 -0
  52. package/src/llm/anthropic/index.ts +134 -10
  53. package/src/llm/anthropic/inherited-content-utils.spec.ts +10 -5
  54. package/src/llm/anthropic/inherited-stream-events.spec.ts +513 -9
  55. package/src/llm/anthropic/llm.spec.ts +100 -16
  56. package/src/llm/anthropic/types.ts +3 -0
  57. package/src/llm/anthropic/utils/message_inputs.ts +60 -3
  58. package/src/llm/anthropic/utils/message_outputs.ts +10 -2
  59. package/src/llm/anthropic/utils/stream_events.ts +471 -0
  60. package/src/llm/bedrock/utils/message_inputs.test.ts +276 -1
  61. package/src/llm/bedrock/utils/message_inputs.ts +121 -73
  62. package/src/tools/search/crw-scraper.ts +244 -0
  63. package/src/tools/search/crw-search.ts +167 -0
  64. package/src/tools/search/crw.test.ts +836 -0
  65. package/src/tools/search/search.ts +7 -1
  66. package/src/tools/search/tool.ts +23 -3
  67. package/src/tools/search/types.ts +103 -3
@@ -0,0 +1,471 @@
1
+ /**
2
+ * Converts a raw Anthropic SSE event stream into LangChain ChatModelStreamEvents.
3
+ *
4
+ * @module
5
+ */
6
+
7
+ import type {
8
+ ChatModelStreamEvent,
9
+ ContentBlockDelta,
10
+ FinishReason,
11
+ } from '@langchain/core/language_models/event';
12
+ import type { ContentBlock, UsageMetadata } from '@langchain/core/messages';
13
+ import type {
14
+ AnthropicCompactionBlock,
15
+ AnthropicCompactionContentBlockDelta,
16
+ AnthropicMessageStreamEvent,
17
+ } from '../types';
18
+ import type { AnthropicUsageData } from './message_outputs';
19
+ import { getAnthropicUsageMetadata } from './message_outputs';
20
+
21
+ type AnthropicContentBlock =
22
+ | Extract<
23
+ AnthropicMessageStreamEvent,
24
+ { type: 'content_block_start' }
25
+ >['content_block']
26
+ | AnthropicCompactionBlock;
27
+
28
+ type AnthropicContentDelta =
29
+ | Extract<
30
+ AnthropicMessageStreamEvent,
31
+ { type: 'content_block_delta' }
32
+ >['delta']
33
+ | AnthropicCompactionContentBlockDelta;
34
+
35
+ interface AnthropicStreamErrorEvent {
36
+ type: 'error';
37
+ error: {
38
+ type: string;
39
+ message: string;
40
+ };
41
+ request_id?: string | null;
42
+ }
43
+
44
+ type AnthropicStreamInputEvent =
45
+ | AnthropicMessageStreamEvent
46
+ | AnthropicStreamErrorEvent;
47
+
48
+ type BlockAccumulator = Record<string, unknown>;
49
+
50
+ interface AnthropicEventUsage {
51
+ inputTokens: number;
52
+ cacheCreationInputTokens: number;
53
+ cacheReadInputTokens: number;
54
+ outputTokens: number;
55
+ }
56
+
57
+ // ─── Public API ─────────────────────────────────────────────────
58
+
59
+ export interface ConvertAnthropicStreamOptions {
60
+ streamUsage?: boolean;
61
+ }
62
+
63
+ /**
64
+ * Convert an async iterable of raw Anthropic stream events into
65
+ * LangChain `ChatModelStreamEvent`s with typed deltas.
66
+ */
67
+ export async function* convertAnthropicStream(
68
+ source: AsyncIterable<AnthropicStreamInputEvent>,
69
+ options: ConvertAnthropicStreamOptions = {}
70
+ ): AsyncGenerator<ChatModelStreamEvent> {
71
+ const shouldStreamUsage = options.streamUsage ?? true;
72
+
73
+ // Track accumulated state per content block (for finalization)
74
+ const blockAccumulators = new Map<number, BlockAccumulator>();
75
+ let usageSnapshot: UsageMetadata | undefined;
76
+ let eventUsage: AnthropicEventUsage | undefined;
77
+ let stopReason: string | null = null;
78
+
79
+ for await (const data of source) {
80
+ switch (data.type) {
81
+ // ── Message lifecycle ──────────────────────────────────
82
+ case 'message_start': {
83
+ const { usage, id, model } = data.message;
84
+ if (shouldStreamUsage) {
85
+ eventUsage = {
86
+ inputTokens: usage.input_tokens,
87
+ cacheCreationInputTokens: usage.cache_creation_input_tokens ?? 0,
88
+ cacheReadInputTokens: usage.cache_read_input_tokens ?? 0,
89
+ outputTokens: usage.output_tokens,
90
+ };
91
+ usageSnapshot = buildUsageSnapshot(eventUsage);
92
+ }
93
+ yield {
94
+ event: 'message-start' as const,
95
+ id,
96
+ ...(usageSnapshot ? { usage: usageSnapshot } : {}),
97
+ };
98
+ yield {
99
+ event: 'provider' as const,
100
+ provider: 'anthropic',
101
+ name: 'message_start',
102
+ payload: { model, id },
103
+ };
104
+ break;
105
+ }
106
+
107
+ case 'message_delta': {
108
+ stopReason = data.delta.stop_reason;
109
+ if (shouldStreamUsage) {
110
+ eventUsage = updateEventUsage(eventUsage, data.usage);
111
+ usageSnapshot = buildUsageSnapshot(eventUsage);
112
+ yield { event: 'usage' as const, usage: usageSnapshot };
113
+ }
114
+ if (
115
+ 'context_management' in data.delta &&
116
+ data.delta.context_management != null
117
+ ) {
118
+ yield {
119
+ event: 'provider' as const,
120
+ provider: 'anthropic',
121
+ name: 'context_management',
122
+ payload: data.delta.context_management,
123
+ };
124
+ }
125
+ break;
126
+ }
127
+
128
+ case 'message_stop': {
129
+ const finishEvent = {
130
+ event: 'message-finish' as const,
131
+ reason: mapStopReason(stopReason),
132
+ ...(usageSnapshot ? { usage: usageSnapshot } : {}),
133
+ responseMetadata: { model_provider: 'anthropic' },
134
+ };
135
+ yield finishEvent;
136
+ break;
137
+ }
138
+
139
+ case 'error': {
140
+ const streamError = new Error(data.error.message);
141
+ streamError.name = data.error.type;
142
+ throw streamError;
143
+ }
144
+
145
+ // ── Content block lifecycle ───────────────────────────
146
+ case 'content_block_start': {
147
+ const { index, content_block } = data;
148
+ const mapped = mapBlockToContentBlock(content_block, index);
149
+ blockAccumulators.set(index, { ...mapped });
150
+ yield {
151
+ event: 'content-block-start' as const,
152
+ index,
153
+ content: mapped as unknown as ContentBlock,
154
+ };
155
+ break;
156
+ }
157
+
158
+ case 'content_block_delta': {
159
+ const { index, delta } = data;
160
+ const acc = blockAccumulators.get(index);
161
+ if (!acc) break;
162
+
163
+ const { contentDelta, accumulated } = applyAnthropicDelta(acc, delta);
164
+ blockAccumulators.set(index, accumulated);
165
+
166
+ yield {
167
+ event: 'content-block-delta' as const,
168
+ index,
169
+ delta: contentDelta,
170
+ };
171
+ break;
172
+ }
173
+
174
+ case 'content_block_stop': {
175
+ const { index } = data;
176
+ const acc = blockAccumulators.get(index);
177
+ if (!acc) break;
178
+
179
+ const finalized = finalizeBlock(acc);
180
+ yield {
181
+ event: 'content-block-finish' as const,
182
+ index,
183
+ content: finalized,
184
+ };
185
+ blockAccumulators.delete(index);
186
+ break;
187
+ }
188
+
189
+ // ── Unhandled → provider passthrough ───────────────────
190
+ default: {
191
+ const providerData = data as AnthropicMessageStreamEvent;
192
+ yield {
193
+ event: 'provider' as const,
194
+ provider: 'anthropic',
195
+ name: providerData.type,
196
+ payload: providerData,
197
+ };
198
+ break;
199
+ }
200
+ }
201
+ }
202
+ }
203
+
204
+ // ─── Internal helpers ───────────────────────────────────────────
205
+
206
+ function mapStopReason(stopReason: string | null | undefined): FinishReason {
207
+ switch (stopReason) {
208
+ case 'end_turn':
209
+ case 'stop_sequence':
210
+ return 'stop';
211
+ case 'tool_use':
212
+ return 'tool_use';
213
+ case 'max_tokens':
214
+ case 'model_context_window_exceeded':
215
+ return 'length';
216
+ case 'refusal':
217
+ return 'content_filter';
218
+ default:
219
+ return 'stop';
220
+ }
221
+ }
222
+
223
+ function updateEventUsage(
224
+ previous: AnthropicEventUsage | undefined,
225
+ current: AnthropicUsageData
226
+ ): AnthropicEventUsage {
227
+ return {
228
+ inputTokens: getCumulativeUsageValue(
229
+ current.input_tokens,
230
+ previous?.inputTokens
231
+ ),
232
+ cacheCreationInputTokens: getCumulativeUsageValue(
233
+ current.cache_creation_input_tokens,
234
+ previous?.cacheCreationInputTokens
235
+ ),
236
+ cacheReadInputTokens: getCumulativeUsageValue(
237
+ current.cache_read_input_tokens,
238
+ previous?.cacheReadInputTokens
239
+ ),
240
+ outputTokens: getCumulativeUsageValue(
241
+ current.output_tokens,
242
+ previous?.outputTokens
243
+ ),
244
+ };
245
+ }
246
+
247
+ function getCumulativeUsageValue(
248
+ current: number | null | undefined,
249
+ previous: number | undefined
250
+ ): number {
251
+ return Math.max(previous ?? 0, current ?? 0);
252
+ }
253
+
254
+ function buildUsageSnapshot(usage: AnthropicEventUsage): UsageMetadata {
255
+ const metadata = getAnthropicUsageMetadata({
256
+ input_tokens: usage.inputTokens,
257
+ output_tokens: usage.outputTokens,
258
+ cache_creation_input_tokens: usage.cacheCreationInputTokens,
259
+ cache_read_input_tokens: usage.cacheReadInputTokens,
260
+ });
261
+ if (metadata == null) {
262
+ throw new Error('Anthropic usage metadata was not created');
263
+ }
264
+ return metadata;
265
+ }
266
+
267
+ function getStringField(record: BlockAccumulator, key: string): string {
268
+ const value = record[key];
269
+ return typeof value === 'string' ? value : '';
270
+ }
271
+
272
+ function getArrayField(record: BlockAccumulator, key: string): unknown[] {
273
+ const value = record[key];
274
+ return Array.isArray(value) ? value : [];
275
+ }
276
+
277
+ function mapBlockToContentBlock(
278
+ block: AnthropicContentBlock,
279
+ index: number
280
+ ): BlockAccumulator {
281
+ switch (block.type) {
282
+ case 'text':
283
+ return {
284
+ type: 'text' as const,
285
+ text: block.text,
286
+ ...(block.citations != null ? { citations: block.citations } : {}),
287
+ index,
288
+ };
289
+ case 'thinking':
290
+ return {
291
+ type: 'reasoning' as const,
292
+ reasoning: block.thinking,
293
+ index,
294
+ };
295
+ case 'redacted_thinking':
296
+ return { ...block, index };
297
+ case 'tool_use':
298
+ return {
299
+ type: 'tool_call_chunk' as const,
300
+ id: block.id,
301
+ name: block.name,
302
+ args: '',
303
+ index,
304
+ };
305
+ case 'server_tool_use':
306
+ return {
307
+ type: 'server_tool_call_chunk' as const,
308
+ id: block.id,
309
+ name: block.name,
310
+ args: '',
311
+ index,
312
+ };
313
+ case 'web_search_tool_result':
314
+ return { ...block, index };
315
+ case 'compaction':
316
+ return { ...block, index };
317
+ default:
318
+ return { type: 'non_standard' as const, value: { ...block }, index };
319
+ }
320
+ }
321
+
322
+ /**
323
+ * Map an Anthropic content_block_delta to a content block delta
324
+ * and update the accumulated state.
325
+ */
326
+ function applyAnthropicDelta(
327
+ accumulated: BlockAccumulator,
328
+ delta: AnthropicContentDelta
329
+ ): {
330
+ contentDelta: ContentBlockDelta;
331
+ accumulated: BlockAccumulator;
332
+ } {
333
+ const rawDelta = delta as unknown as Record<string, unknown>;
334
+ switch (delta.type) {
335
+ case 'text_delta':
336
+ return {
337
+ contentDelta: { type: 'text-delta' as const, text: delta.text },
338
+ accumulated: {
339
+ ...accumulated,
340
+ text: getStringField(accumulated, 'text') + delta.text,
341
+ },
342
+ };
343
+
344
+ case 'thinking_delta':
345
+ return {
346
+ contentDelta: {
347
+ type: 'reasoning-delta' as const,
348
+ reasoning: delta.thinking,
349
+ },
350
+ accumulated: {
351
+ ...accumulated,
352
+ reasoning: getStringField(accumulated, 'reasoning') + delta.thinking,
353
+ },
354
+ };
355
+
356
+ case 'input_json_delta': {
357
+ const newArgs = getStringField(accumulated, 'args') + delta.partial_json;
358
+ return {
359
+ contentDelta: {
360
+ type: 'block-delta' as const,
361
+ fields: {
362
+ type: getStringField(accumulated, 'type'),
363
+ args: newArgs,
364
+ },
365
+ },
366
+ accumulated: { ...accumulated, args: newArgs },
367
+ };
368
+ }
369
+
370
+ case 'citations_delta': {
371
+ const citations = [
372
+ ...getArrayField(accumulated, 'citations'),
373
+ delta.citation,
374
+ ];
375
+ return {
376
+ contentDelta: {
377
+ type: 'block-delta' as const,
378
+ fields: {
379
+ type: getStringField(accumulated, 'type'),
380
+ citations,
381
+ },
382
+ },
383
+ accumulated: {
384
+ ...accumulated,
385
+ citations,
386
+ },
387
+ };
388
+ }
389
+
390
+ case 'signature_delta':
391
+ return {
392
+ contentDelta: {
393
+ type: 'block-delta' as const,
394
+ fields: {
395
+ type: getStringField(accumulated, 'type'),
396
+ signature: delta.signature,
397
+ },
398
+ },
399
+ accumulated: { ...accumulated, signature: delta.signature },
400
+ };
401
+
402
+ case 'compaction_delta': {
403
+ const previousContent = accumulated.content;
404
+ const content =
405
+ delta.content == null
406
+ ? (previousContent ?? null)
407
+ : getStringField(accumulated, 'content') + delta.content;
408
+ return {
409
+ contentDelta: {
410
+ type: 'block-delta' as const,
411
+ fields: {
412
+ type: 'compaction',
413
+ content,
414
+ encrypted_content: delta.encrypted_content,
415
+ },
416
+ },
417
+ accumulated: {
418
+ ...accumulated,
419
+ content,
420
+ encrypted_content: delta.encrypted_content,
421
+ },
422
+ };
423
+ }
424
+
425
+ default:
426
+ return {
427
+ contentDelta: {
428
+ type: 'block-delta' as const,
429
+ fields: {
430
+ type: getStringField(accumulated, 'type'),
431
+ ...rawDelta,
432
+ },
433
+ },
434
+ accumulated,
435
+ };
436
+ }
437
+ }
438
+
439
+ function finalizeBlock(accumulated: BlockAccumulator): ContentBlock {
440
+ if (
441
+ accumulated.type === 'tool_call_chunk' ||
442
+ accumulated.type === 'server_tool_call_chunk'
443
+ ) {
444
+ const finalType =
445
+ accumulated.type === 'tool_call_chunk'
446
+ ? ('tool_call' as const)
447
+ : ('server_tool_call' as const);
448
+ const args = getStringField(accumulated, 'args');
449
+ let parsedArgs: unknown;
450
+ try {
451
+ parsedArgs = JSON.parse(args || '{}');
452
+ } catch {
453
+ return {
454
+ type: 'invalid_tool_call' as const,
455
+ id: getStringField(accumulated, 'id'),
456
+ name: getStringField(accumulated, 'name'),
457
+ args,
458
+ error: 'Failed to parse tool call arguments as JSON',
459
+ } as ContentBlock.Tools.InvalidToolCall;
460
+ }
461
+ return {
462
+ type: finalType,
463
+ id: getStringField(accumulated, 'id'),
464
+ name: getStringField(accumulated, 'name'),
465
+ args: parsedArgs,
466
+ } as ContentBlock;
467
+ }
468
+
469
+ const { index: _index, ...rest } = accumulated;
470
+ return rest as ContentBlock;
471
+ }