@librechat/agents 3.2.64 → 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 (61) 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/tools/search/crw-scraper.cjs +165 -0
  13. package/dist/cjs/tools/search/crw-scraper.cjs.map +1 -0
  14. package/dist/cjs/tools/search/crw-search.cjs +105 -0
  15. package/dist/cjs/tools/search/crw-search.cjs.map +1 -0
  16. package/dist/cjs/tools/search/search.cjs +4 -2
  17. package/dist/cjs/tools/search/search.cjs.map +1 -1
  18. package/dist/cjs/tools/search/tool.cjs +15 -3
  19. package/dist/cjs/tools/search/tool.cjs.map +1 -1
  20. package/dist/esm/graphs/Graph.mjs +3 -0
  21. package/dist/esm/graphs/Graph.mjs.map +1 -1
  22. package/dist/esm/llm/anthropic/index.mjs +73 -9
  23. package/dist/esm/llm/anthropic/index.mjs.map +1 -1
  24. package/dist/esm/llm/anthropic/types.mjs.map +1 -1
  25. package/dist/esm/llm/anthropic/utils/message_inputs.mjs +41 -9
  26. package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
  27. package/dist/esm/llm/anthropic/utils/message_outputs.mjs +3 -2
  28. package/dist/esm/llm/anthropic/utils/message_outputs.mjs.map +1 -1
  29. package/dist/esm/llm/anthropic/utils/stream_events.mjs +337 -0
  30. package/dist/esm/llm/anthropic/utils/stream_events.mjs.map +1 -0
  31. package/dist/esm/tools/search/crw-scraper.mjs +163 -0
  32. package/dist/esm/tools/search/crw-scraper.mjs.map +1 -0
  33. package/dist/esm/tools/search/crw-search.mjs +103 -0
  34. package/dist/esm/tools/search/crw-search.mjs.map +1 -0
  35. package/dist/esm/tools/search/search.mjs +4 -2
  36. package/dist/esm/tools/search/search.mjs.map +1 -1
  37. package/dist/esm/tools/search/tool.mjs +15 -3
  38. package/dist/esm/tools/search/tool.mjs.map +1 -1
  39. package/dist/types/llm/anthropic/index.d.ts +2 -0
  40. package/dist/types/llm/anthropic/types.d.ts +2 -0
  41. package/dist/types/llm/anthropic/utils/message_outputs.d.ts +1 -2
  42. package/dist/types/llm/anthropic/utils/stream_events.d.ts +25 -0
  43. package/dist/types/tools/search/crw-scraper.d.ts +41 -0
  44. package/dist/types/tools/search/crw-search.d.ts +4 -0
  45. package/dist/types/tools/search/types.d.ts +88 -3
  46. package/package.json +1 -1
  47. package/src/graphs/Graph.ts +15 -0
  48. package/src/llm/anthropic/index.ts +134 -10
  49. package/src/llm/anthropic/inherited-content-utils.spec.ts +10 -5
  50. package/src/llm/anthropic/inherited-stream-events.spec.ts +513 -9
  51. package/src/llm/anthropic/llm.spec.ts +100 -16
  52. package/src/llm/anthropic/types.ts +3 -0
  53. package/src/llm/anthropic/utils/message_inputs.ts +60 -3
  54. package/src/llm/anthropic/utils/message_outputs.ts +10 -2
  55. package/src/llm/anthropic/utils/stream_events.ts +471 -0
  56. package/src/tools/search/crw-scraper.ts +244 -0
  57. package/src/tools/search/crw-search.ts +167 -0
  58. package/src/tools/search/crw.test.ts +836 -0
  59. package/src/tools/search/search.ts +7 -1
  60. package/src/tools/search/tool.ts +23 -3
  61. package/src/tools/search/types.ts +103 -3
@@ -2,8 +2,8 @@ import type { RunnableConfig } from '@langchain/core/runnables';
2
2
  import type { Logger as WinstonLogger } from 'winston';
3
3
  import type { BaseReranker } from './rerankers';
4
4
  import { DATE_RANGE } from './schema';
5
- export type SearchProvider = 'serper' | 'searxng' | 'tavily' | 'keenable';
6
- export type ScraperProvider = 'firecrawl' | 'serper' | 'tavily';
5
+ export type SearchProvider = 'serper' | 'searxng' | 'tavily' | 'keenable' | 'crw';
6
+ export type ScraperProvider = 'firecrawl' | 'serper' | 'tavily' | 'crw';
7
7
  export type RerankerType = 'infinity' | 'jina' | 'cohere' | 'none';
8
8
  export interface Highlight {
9
9
  score: number;
@@ -91,6 +91,48 @@ export interface TavilySearchPayload {
91
91
  include_favicon?: boolean;
92
92
  chunks_per_source?: number;
93
93
  }
94
+ export interface CrwSearchOptions {
95
+ /** Max results to request (maps to `limit`; clamped 1..20). */
96
+ maxResults?: number;
97
+ /** Add 'images' to the sources array. */
98
+ includeImages?: boolean;
99
+ timeout?: number;
100
+ }
101
+ export type CrwSearchSource = 'web' | 'images' | 'news';
102
+ export interface CrwSearchPayload {
103
+ query: string;
104
+ limit: number;
105
+ sources?: CrwSearchSource[];
106
+ tbs?: string;
107
+ }
108
+ export interface CrwSearchResult {
109
+ title?: string;
110
+ url?: string;
111
+ description?: string;
112
+ snippet?: string;
113
+ position?: number;
114
+ category?: string;
115
+ }
116
+ export interface CrwImageSearchResult extends CrwSearchResult {
117
+ imageUrl?: string;
118
+ imageFormat?: string;
119
+ }
120
+ export interface CrwNewsSearchResult extends CrwSearchResult {
121
+ publishedDate?: string;
122
+ }
123
+ export interface CrwSearchGroups {
124
+ web?: CrwSearchResult[];
125
+ images?: CrwImageSearchResult[];
126
+ news?: CrwNewsSearchResult[];
127
+ }
128
+ export interface CrwSearchResponse {
129
+ success: boolean;
130
+ data?: (CrwSearchGroups & {
131
+ results?: CrwSearchGroups;
132
+ }) | CrwSearchResult[];
133
+ error?: string;
134
+ error_code?: string;
135
+ }
94
136
  export interface SearchConfig {
95
137
  searchProvider?: SearchProvider;
96
138
  serperApiKey?: string;
@@ -103,6 +145,9 @@ export interface SearchConfig {
103
145
  keenableApiKey?: string;
104
146
  keenableApiUrl?: string;
105
147
  keenableSearchOptions?: KeenableSearchOptions;
148
+ crwApiKey?: string;
149
+ crwApiUrl?: string;
150
+ crwSearchOptions?: CrwSearchOptions;
106
151
  }
107
152
  export interface KeenableSearchOptions {
108
153
  maxResults?: number;
@@ -224,6 +269,7 @@ export type SafeSearchLevel = 0 | 1 | 2;
224
269
  export type Logger = WinstonLogger;
225
270
  export interface SearchToolConfig extends SearchConfig, ProcessSourcesConfig, FirecrawlConfig {
226
271
  tavilyScraperOptions?: TavilyScraperConfig;
272
+ crwScraperOptions?: CrwScraperConfig;
227
273
  /** Max chars of highlight content this tool feeds the MODEL per search (the
228
274
  * dominant, otherwise-unbounded part of the output). Distinct from
229
275
  * `maxContentLength`, which caps scraped/reranked content per source — full
@@ -255,7 +301,7 @@ export type UsedReferences = {
255
301
  originalIndex: number;
256
302
  reference: MediaReference;
257
303
  }[];
258
- export type AnyScraperResponse = FirecrawlScrapeResponse | SerperScrapeResponse | TavilyScrapeResponse;
304
+ export type AnyScraperResponse = FirecrawlScrapeResponse | SerperScrapeResponse | TavilyScrapeResponse | CrwScrapeResponse;
259
305
  /** Base Scraper Interface */
260
306
  export interface BaseScraper {
261
307
  scrapeUrl(url: string, options?: unknown): Promise<[string, AnyScraperResponse]>;
@@ -265,6 +311,24 @@ export interface BaseScraper {
265
311
  }
266
312
  /** Firecrawl */
267
313
  export type FirecrawlScrapeOptions = Omit<FirecrawlScraperConfig, 'apiKey' | 'apiUrl' | 'version' | 'logger'>;
314
+ export interface CrwScraperConfig {
315
+ apiKey?: string;
316
+ apiUrl?: string;
317
+ formats?: string[];
318
+ timeout?: number;
319
+ logger?: Logger;
320
+ onlyMainContent?: boolean;
321
+ includeTags?: string[];
322
+ excludeTags?: string[];
323
+ waitFor?: number;
324
+ headers?: Record<string, string>;
325
+ renderJs?: boolean | null;
326
+ cssSelector?: string;
327
+ xpath?: string;
328
+ proxy?: string;
329
+ stealth?: boolean;
330
+ }
331
+ export type CrwScrapeOptions = Omit<CrwScraperConfig, 'apiKey' | 'apiUrl' | 'logger'>;
268
332
  export type SerperScrapeOptions = Omit<SerperScraperConfig, 'apiKey' | 'apiUrl' | 'logger'>;
269
333
  export type TavilyScrapeOptions = Omit<TavilyScraperConfig, 'apiKey' | 'apiUrl' | 'logger'>;
270
334
  export interface TavilyExtractPayload {
@@ -341,6 +405,27 @@ export interface FirecrawlScrapeResponse {
341
405
  };
342
406
  error?: string;
343
407
  }
408
+ export interface CrwScrapeData {
409
+ markdown?: string;
410
+ html?: string;
411
+ rawHtml?: string;
412
+ plainText?: string;
413
+ screenshot?: string;
414
+ links?: string[];
415
+ metadata?: ScrapeMetadata;
416
+ }
417
+ export interface CrwScrapeResponse {
418
+ success: boolean;
419
+ data?: CrwScrapeData;
420
+ error?: string;
421
+ error_code?: string;
422
+ }
423
+ export interface CrwRawScrapeResponse extends CrwScrapeData {
424
+ success?: boolean;
425
+ data?: CrwScrapeData;
426
+ error?: string;
427
+ error_code?: string;
428
+ }
344
429
  export interface SerperScrapeResponse {
345
430
  success: boolean;
346
431
  data?: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@librechat/agents",
3
- "version": "3.2.64",
3
+ "version": "3.2.65",
4
4
  "main": "./dist/cjs/main.cjs",
5
5
  "module": "./dist/esm/main.mjs",
6
6
  "types": "./dist/types/index.d.ts",
@@ -1130,6 +1130,9 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1130
1130
  /* Misc.*/
1131
1131
 
1132
1132
  getRunMessages(): BaseMessage[] | undefined {
1133
+ if (this.messages == null) {
1134
+ return this.cachedRunMessages;
1135
+ }
1133
1136
  if (this.messages.length === 0 && this.cachedRunMessages != null) {
1134
1137
  return this.cachedRunMessages;
1135
1138
  }
@@ -1137,6 +1140,12 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1137
1140
  }
1138
1141
 
1139
1142
  getContentParts(): t.MessageContentComplex[] | undefined {
1143
+ // `messages` can be null/undefined on a graph that has been disposed
1144
+ // (clearHeavyState) but is still reachable via a cache (e.g. RedisJobStore's
1145
+ // WeakRef) during a HITL resume/reconnect. Guard instead of dereferencing null.
1146
+ if (this.messages == null) {
1147
+ return undefined;
1148
+ }
1140
1149
  return convertMessagesToContent(this.messages.slice(this.startIndex));
1141
1150
  }
1142
1151
 
@@ -1169,6 +1178,12 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1169
1178
  * Get all run steps, optionally filtered by agent ID
1170
1179
  */
1171
1180
  getRunSteps(agentId?: string): t.RunStep[] {
1181
+ // `contentData` can be null/undefined on a disposed-but-cached graph during a
1182
+ // HITL resume/reconnect; without this guard `[...this.contentData]` throws
1183
+ // "this.contentData is not iterable".
1184
+ if (this.contentData == null) {
1185
+ return [];
1186
+ }
1172
1187
  if (agentId == null || agentId === '') {
1173
1188
  return [...this.contentData];
1174
1189
  }
@@ -6,6 +6,7 @@ import type {
6
6
  BaseMessage,
7
7
  MessageContentComplex,
8
8
  } from '@langchain/core/messages';
9
+ import type { ChatModelStreamEvent } from '@langchain/core/language_models/event';
9
10
  import type { CallbackManagerForLLMRun } from '@langchain/core/callbacks/manager';
10
11
  import type { AnthropicInput } from '@langchain/anthropic';
11
12
  import type { Anthropic } from '@anthropic-ai/sdk';
@@ -18,12 +19,15 @@ import type {
18
19
  AnthropicMCPServerURLDefinition,
19
20
  AnthropicContextManagementConfigParam,
20
21
  AnthropicRequestOptions,
22
+ AnthropicMessageStreamEvent,
21
23
  } from '@/llm/anthropic/types';
24
+ import type { AnthropicUsageData } from './utils/message_outputs';
22
25
  import {
23
26
  _convertMessagesToAnthropicPayload,
24
27
  stripUnsupportedAssistantPrefill,
25
28
  } from './utils/message_inputs';
26
29
  import { _makeMessageChunkFromAnthropicEvent } from './utils/message_outputs';
30
+ import { convertAnthropicStream } from './utils/stream_events';
27
31
  import { handleToolChoice } from './utils/tools';
28
32
 
29
33
  const DEFAULT_STREAM_DELAY = 25;
@@ -34,6 +38,23 @@ const STREAM_BOUNDARIES = new Set([' ', '.', ',', '!', '?', ';', ':']);
34
38
 
35
39
  type StreamTokenType = 'string' | 'input' | 'content';
36
40
 
41
+ interface AnthropicStreamUsage {
42
+ inputTokens: number;
43
+ cacheCreationInputTokens: number;
44
+ cacheReadInputTokens: number;
45
+ outputTokens: number;
46
+ }
47
+
48
+ interface CumulativeUsageValue {
49
+ cumulative: number;
50
+ increment: number;
51
+ }
52
+
53
+ interface AnthropicEventStream
54
+ extends AsyncIterable<AnthropicMessageStreamEvent> {
55
+ controller?: { abort: () => void };
56
+ }
57
+
37
58
  const ANTHROPIC_TOOL_BETAS: Partial<Record<string, AnthropicBeta>> = {
38
59
  tool_search_tool_regex_20251119: 'advanced-tool-use-2025-11-20',
39
60
  tool_search_tool_bm25_20251119: 'advanced-tool-use-2025-11-20',
@@ -324,6 +345,19 @@ function isSignalAborted(signal?: AbortSignal): boolean {
324
345
  return signal?.aborted === true;
325
346
  }
326
347
 
348
+ async function* abortableAnthropicStream(
349
+ source: AnthropicEventStream,
350
+ signal?: AbortSignal
351
+ ): AsyncGenerator<AnthropicMessageStreamEvent> {
352
+ for await (const data of source) {
353
+ if (isSignalAborted(signal)) {
354
+ source.controller?.abort();
355
+ return;
356
+ }
357
+ yield data;
358
+ }
359
+ }
360
+
327
361
  function extractToken(
328
362
  chunk: AIMessageChunk
329
363
  ): [string, StreamTokenType] | [undefined] {
@@ -392,27 +426,77 @@ function cloneChunk(
392
426
  return chunk;
393
427
  }
394
428
 
429
+ function resolveCumulativeUsageValue(
430
+ current: number | null | undefined,
431
+ previous: number
432
+ ): CumulativeUsageValue {
433
+ if (current == null) {
434
+ return { cumulative: previous, increment: 0 };
435
+ }
436
+ const cumulative = Math.max(previous, current);
437
+ return { cumulative, increment: cumulative - previous };
438
+ }
439
+
395
440
  function withIncrementalMessageDeltaUsage(
396
441
  chunk: AIMessageChunk,
397
- previousOutputTokens: number
398
- ): { chunk: AIMessageChunk; outputTokens: number } {
442
+ previousUsage: AnthropicStreamUsage,
443
+ cumulativeUsage: AnthropicUsageData
444
+ ): { chunk: AIMessageChunk; usage: AnthropicStreamUsage } {
399
445
  const usage = chunk.usage_metadata;
400
446
  if (usage == null) {
401
- return { chunk, outputTokens: previousOutputTokens };
447
+ return { chunk, usage: previousUsage };
402
448
  }
403
449
 
404
- const outputTokens = Math.max(0, usage.output_tokens - previousOutputTokens);
450
+ const inputTokens = resolveCumulativeUsageValue(
451
+ cumulativeUsage.input_tokens,
452
+ previousUsage.inputTokens
453
+ );
454
+ const cacheCreationInputTokens = resolveCumulativeUsageValue(
455
+ cumulativeUsage.cache_creation_input_tokens,
456
+ previousUsage.cacheCreationInputTokens
457
+ );
458
+ const cacheReadInputTokens = resolveCumulativeUsageValue(
459
+ cumulativeUsage.cache_read_input_tokens,
460
+ previousUsage.cacheReadInputTokens
461
+ );
462
+ const outputTokens = resolveCumulativeUsageValue(
463
+ cumulativeUsage.output_tokens,
464
+ previousUsage.outputTokens
465
+ );
466
+ const incrementalInputTokens =
467
+ inputTokens.increment +
468
+ cacheCreationInputTokens.increment +
469
+ cacheReadInputTokens.increment;
470
+ const hasCacheUsage =
471
+ cumulativeUsage.cache_creation_input_tokens != null ||
472
+ cumulativeUsage.cache_read_input_tokens != null;
473
+ const inputTokenDetails = {
474
+ ...(cumulativeUsage.cache_creation_input_tokens != null && {
475
+ cache_creation: cacheCreationInputTokens.increment,
476
+ }),
477
+ ...(cumulativeUsage.cache_read_input_tokens != null && {
478
+ cache_read: cacheReadInputTokens.increment,
479
+ }),
480
+ };
481
+
405
482
  return {
406
483
  chunk: new AIMessageChunk(
407
484
  Object.assign({}, chunk, {
408
485
  usage_metadata: {
409
486
  ...usage,
410
- output_tokens: outputTokens,
411
- total_tokens: usage.input_tokens + outputTokens,
487
+ input_tokens: incrementalInputTokens,
488
+ output_tokens: outputTokens.increment,
489
+ total_tokens: incrementalInputTokens + outputTokens.increment,
490
+ input_token_details: hasCacheUsage ? inputTokenDetails : undefined,
412
491
  },
413
492
  })
414
493
  ),
415
- outputTokens: usage.output_tokens,
494
+ usage: {
495
+ inputTokens: inputTokens.cumulative,
496
+ cacheCreationInputTokens: cacheCreationInputTokens.cumulative,
497
+ cacheReadInputTokens: cacheReadInputTokens.cumulative,
498
+ outputTokens: outputTokens.cumulative,
499
+ },
416
500
  };
417
501
  }
418
502
 
@@ -626,6 +710,29 @@ export class CustomAnthropic extends ChatAnthropicMessages {
626
710
  );
627
711
  }
628
712
 
713
+ async *_streamChatModelEvents(
714
+ messages: BaseMessage[],
715
+ options: this['ParsedCallOptions'],
716
+ _runManager?: CallbackManagerForLLMRun
717
+ ): AsyncGenerator<ChatModelStreamEvent> {
718
+ const params = this.invocationParams(options);
719
+ const formattedMessages = _convertMessagesToAnthropicPayload(messages);
720
+ const payload = stripUnsupportedAssistantPrefill({
721
+ ...params,
722
+ ...formattedMessages,
723
+ stream: true,
724
+ } as const);
725
+ const stream = await this.createStreamWithRetry(payload, {
726
+ headers: options.headers,
727
+ signal: options.signal,
728
+ });
729
+ const shouldStreamUsage = options.streamUsage ?? this.streamUsage;
730
+ yield* convertAnthropicStream(
731
+ abortableAnthropicStream(stream, options.signal),
732
+ { streamUsage: shouldStreamUsage }
733
+ );
734
+ }
735
+
629
736
  async *_streamResponseChunks(
630
737
  messages: BaseMessage[],
631
738
  options: this['ParsedCallOptions'],
@@ -651,7 +758,12 @@ export class CustomAnthropic extends ChatAnthropicMessages {
651
758
  });
652
759
 
653
760
  const shouldStreamUsage = options.streamUsage ?? this.streamUsage;
654
- let messageDeltaOutputTokens = 0;
761
+ let streamUsage: AnthropicStreamUsage = {
762
+ inputTokens: 0,
763
+ cacheCreationInputTokens: 0,
764
+ cacheReadInputTokens: 0,
765
+ outputTokens: 0,
766
+ };
655
767
  const queuedChunks: QueuedGenerationChunk[] = [];
656
768
  const producerState: {
657
769
  done: boolean;
@@ -833,13 +945,25 @@ export class CustomAnthropic extends ChatAnthropicMessages {
833
945
  }
834
946
 
835
947
  let { chunk } = result;
948
+ if (data.type === 'message_start') {
949
+ streamUsage = {
950
+ ...streamUsage,
951
+ inputTokens: data.message.usage.input_tokens,
952
+ outputTokens: data.message.usage.output_tokens,
953
+ cacheCreationInputTokens:
954
+ data.message.usage.cache_creation_input_tokens ?? 0,
955
+ cacheReadInputTokens:
956
+ data.message.usage.cache_read_input_tokens ?? 0,
957
+ };
958
+ }
836
959
  if (data.type === 'message_delta') {
837
960
  const incremental = withIncrementalMessageDeltaUsage(
838
961
  chunk,
839
- messageDeltaOutputTokens
962
+ streamUsage,
963
+ data.usage
840
964
  );
841
965
  chunk = incremental.chunk;
842
- messageDeltaOutputTokens = incremental.outputTokens;
966
+ streamUsage = incremental.usage;
843
967
  }
844
968
 
845
969
  const [token = '', tokenType] = extractToken(chunk);
@@ -185,7 +185,7 @@ describe('_makeMessageChunkFromAnthropicEvent (ported from message_outputs.test.
185
185
  expect(usage.input_token_details?.cache_read).toBe(1000);
186
186
  });
187
187
 
188
- it('message_delta chunk has input_tokens=0 and no cache token details', () => {
188
+ it('message_delta chunk preserves cumulative input and cache usage', () => {
189
189
  const event = {
190
190
  type: 'message_delta' as const,
191
191
  delta: { stop_reason: 'end_turn' as const, stop_sequence: null },
@@ -202,10 +202,15 @@ describe('_makeMessageChunkFromAnthropicEvent (ported from message_outputs.test.
202
202
  expect(result).not.toBeNull();
203
203
 
204
204
  const usage = result!.chunk.usage_metadata!;
205
- expect(usage.output_tokens).toBe(42);
206
- expect(usage.input_tokens).toBe(0);
207
- expect(usage.input_token_details?.cache_creation).toBeUndefined();
208
- expect(usage.input_token_details?.cache_read).toBeUndefined();
205
+ expect(usage).toEqual({
206
+ input_tokens: 1600,
207
+ output_tokens: 42,
208
+ total_tokens: 1642,
209
+ input_token_details: {
210
+ cache_creation: 500,
211
+ cache_read: 1000,
212
+ },
213
+ });
209
214
  });
210
215
  });
211
216