@librechat/agents 3.0.24 → 3.0.26

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.
@@ -5,11 +5,17 @@ import {
5
5
  } from '@google/generative-ai';
6
6
  import { BindToolsInput } from '@langchain/core/language_models/chat_models';
7
7
 
8
+ /** New GoogleSearch tool for Gemini 2.0+ models */
9
+ export interface GoogleSearchTool {
10
+ googleSearch: Record<string, never>;
11
+ }
12
+
8
13
  export type GoogleGenerativeAIToolType =
9
14
  | BindToolsInput
10
15
  | GoogleGenerativeAIFunctionDeclarationsTool
11
16
  | CodeExecutionTool
12
- | GoogleSearchRetrievalTool;
17
+ | GoogleSearchRetrievalTool
18
+ | GoogleSearchTool;
13
19
 
14
20
  /** Enum for content modality types */
15
21
  enum Modality {
@@ -27,6 +33,13 @@ interface ModalityTokenCount {
27
33
  tokenCount: number;
28
34
  }
29
35
 
36
+ /** Interface for input token details with cache and tier tracking */
37
+ export interface InputTokenDetails {
38
+ cache_read?: number;
39
+ over_200k?: number;
40
+ cache_read_over_200k?: number;
41
+ }
42
+
30
43
  /** Main interface for Gemini API usage metadata */
31
44
  export interface GeminiApiUsageMetadata {
32
45
  promptTokenCount?: number;
@@ -12,6 +12,7 @@ import {
12
12
  type FunctionDeclarationsTool as GoogleGenerativeAIFunctionDeclarationsTool,
13
13
  } from '@google/generative-ai';
14
14
  import {
15
+ AIMessage,
15
16
  AIMessageChunk,
16
17
  BaseMessage,
17
18
  ChatMessage,
@@ -29,7 +30,7 @@ import {
29
30
  isDataContentBlock,
30
31
  } from '@langchain/core/messages';
31
32
  import { ChatGenerationChunk } from '@langchain/core/outputs';
32
- import type { ChatGeneration } from '@langchain/core/outputs';
33
+ import type { ChatGeneration, ChatResult } from '@langchain/core/outputs';
33
34
  import { isLangChainTool } from '@langchain/core/utils/function_calling';
34
35
  import { isOpenAITool } from '@langchain/core/language_models/base';
35
36
  import { ToolCallChunk } from '@langchain/core/messages/tool';
@@ -40,6 +41,20 @@ import {
40
41
  } from './zod_to_genai_parameters';
41
42
  import { GoogleGenerativeAIToolType } from '../types';
42
43
 
44
+ export const _FUNCTION_CALL_THOUGHT_SIGNATURES_MAP_KEY =
45
+ '__gemini_function_call_thought_signatures__';
46
+
47
+ const DUMMY_SIGNATURE =
48
+ 'ErYCCrMCAdHtim9kOoOkrPiCNVsmlpMIKd7ZMxgiFbVQOkgp7nlLcDMzVsZwIzvuT7nQROivoXA72ccC2lSDvR0Gh7dkWaGuj7ctv6t7ZceHnecx0QYa+ix8tYpRfjhyWozQ49lWiws6+YGjCt10KRTyWsZ2h6O7iHTYJwKIRwGUHRKy/qK/6kFxJm5ML00gLq4D8s5Z6DBpp2ZlR+uF4G8jJgeWQgyHWVdx2wGYElaceVAc66tZdPQRdOHpWtgYSI1YdaXgVI8KHY3/EfNc2YqqMIulvkDBAnuMhkAjV9xmBa54Tq+ih3Im4+r3DzqhGqYdsSkhS0kZMwte4Hjs65dZzCw9lANxIqYi1DJ639WNPYihp/DCJCos7o+/EeSPJaio5sgWDyUnMGkY1atsJZ+m7pj7DD5tvQ==';
49
+
50
+ /**
51
+ * Executes a function immediately and returns its result.
52
+ * Functional utility similar to an Immediately Invoked Function Expression (IIFE).
53
+ * @param fn The function to execute.
54
+ * @returns The result of invoking fn.
55
+ */
56
+ export const iife = <T>(fn: () => T): T => fn();
57
+
43
58
  export function getMessageAuthor(message: BaseMessage): string {
44
59
  const type = message._getType();
45
60
  if (ChatMessage.isInstance(message)) {
@@ -352,7 +367,8 @@ function _convertLangChainContentToPart(
352
367
  export function convertMessageContentToParts(
353
368
  message: BaseMessage,
354
369
  isMultimodalModel: boolean,
355
- previousMessages: BaseMessage[]
370
+ previousMessages: BaseMessage[],
371
+ model?: string
356
372
  ): Part[] {
357
373
  if (isToolMessage(message)) {
358
374
  const messageName =
@@ -409,13 +425,33 @@ export function convertMessageContentToParts(
409
425
  );
410
426
  }
411
427
 
412
- if (isAIMessage(message) && message.tool_calls?.length != null) {
413
- functionCalls = message.tool_calls.map((tc) => {
428
+ const functionThoughtSignatures = (
429
+ message.additional_kwargs as BaseMessage['additional_kwargs'] | undefined
430
+ )?.[_FUNCTION_CALL_THOUGHT_SIGNATURES_MAP_KEY] as
431
+ | Record<string, string>
432
+ | undefined;
433
+
434
+ if (isAIMessage(message) && (message.tool_calls?.length ?? 0) > 0) {
435
+ functionCalls = (message.tool_calls ?? []).map((tc) => {
436
+ const thoughtSignature = iife(() => {
437
+ if (tc.id != null && tc.id !== '') {
438
+ const signature = functionThoughtSignatures?.[tc.id];
439
+ if (signature != null && signature !== '') {
440
+ return signature;
441
+ }
442
+ }
443
+ if (model?.includes('gemini-3') === true) {
444
+ return DUMMY_SIGNATURE;
445
+ }
446
+ return '';
447
+ });
448
+
414
449
  return {
415
450
  functionCall: {
416
451
  name: tc.name,
417
452
  args: tc.args,
418
453
  },
454
+ ...(thoughtSignature ? { thoughtSignature } : {}),
419
455
  };
420
456
  });
421
457
  }
@@ -426,7 +462,9 @@ export function convertMessageContentToParts(
426
462
  export function convertBaseMessagesToContent(
427
463
  messages: BaseMessage[],
428
464
  isMultimodalModel: boolean,
429
- convertSystemMessageToHumanContent: boolean = false
465
+ convertSystemMessageToHumanContent: boolean = false,
466
+
467
+ model?: string
430
468
  ): Content[] | undefined {
431
469
  return messages.reduce<{
432
470
  content: Content[] | undefined;
@@ -456,7 +494,8 @@ export function convertBaseMessagesToContent(
456
494
  const parts = convertMessageContentToParts(
457
495
  message,
458
496
  isMultimodalModel,
459
- messages.slice(0, index)
497
+ messages.slice(0, index),
498
+ model
460
499
  );
461
500
 
462
501
  if (acc.mergeWithPreviousContent) {
@@ -505,11 +544,32 @@ export function convertResponseContentToChatGenerationChunk(
505
544
  if (!response.candidates || response.candidates.length === 0) {
506
545
  return null;
507
546
  }
508
- const functionCalls = response.functionCalls();
509
547
  const [candidate] = response.candidates as [
510
548
  Partial<GenerateContentCandidate> | undefined,
511
549
  ];
512
550
  const { content: candidateContent, ...generationInfo } = candidate ?? {};
551
+
552
+ // Extract function calls directly from parts to preserve thoughtSignature
553
+ const functionCalls =
554
+ (candidateContent?.parts as Part[] | undefined)?.reduce(
555
+ (acc, p) => {
556
+ if ('functionCall' in p && p.functionCall) {
557
+ acc.push({
558
+ ...p,
559
+ id:
560
+ 'id' in p.functionCall && typeof p.functionCall.id === 'string'
561
+ ? p.functionCall.id
562
+ : uuidv4(),
563
+ });
564
+ }
565
+ return acc;
566
+ },
567
+ [] as (
568
+ | undefined
569
+ | (FunctionCallPart & { id: string; thoughtSignature?: string })
570
+ )[]
571
+ ) ?? [];
572
+
513
573
  let content: MessageContent | undefined;
514
574
  // Checks if some parts do not have text. If false, it means that the content is a string.
515
575
  const reasoningParts: string[] = [];
@@ -529,27 +589,30 @@ export function convertResponseContentToChatGenerationChunk(
529
589
  }
530
590
  content = textParts.join('');
531
591
  } else if (candidateContent && Array.isArray(candidateContent.parts)) {
532
- content = candidateContent.parts.map((p) => {
533
- if ('text' in p && 'thought' in p && p.thought === true) {
534
- reasoningParts.push(p.text ?? '');
535
- } else if ('text' in p) {
536
- return {
537
- type: 'text',
538
- text: p.text,
539
- };
540
- } else if ('executableCode' in p) {
541
- return {
542
- type: 'executableCode',
543
- executableCode: p.executableCode,
544
- };
545
- } else if ('codeExecutionResult' in p) {
546
- return {
547
- type: 'codeExecutionResult',
548
- codeExecutionResult: p.codeExecutionResult,
549
- };
550
- }
551
- return p;
552
- });
592
+ content = candidateContent.parts
593
+ .map((p) => {
594
+ if ('text' in p && 'thought' in p && p.thought === true) {
595
+ reasoningParts.push(p.text ?? '');
596
+ return undefined;
597
+ } else if ('text' in p) {
598
+ return {
599
+ type: 'text',
600
+ text: p.text,
601
+ };
602
+ } else if ('executableCode' in p) {
603
+ return {
604
+ type: 'executableCode',
605
+ executableCode: p.executableCode,
606
+ };
607
+ } else if ('codeExecutionResult' in p) {
608
+ return {
609
+ type: 'codeExecutionResult',
610
+ codeExecutionResult: p.codeExecutionResult,
611
+ };
612
+ }
613
+ return p;
614
+ })
615
+ .filter((p) => p !== undefined);
553
616
  } else {
554
617
  // no content returned - likely due to abnormal stop reason, e.g. malformed function call
555
618
  content = [];
@@ -566,20 +629,36 @@ export function convertResponseContentToChatGenerationChunk(
566
629
  }
567
630
 
568
631
  const toolCallChunks: ToolCallChunk[] = [];
569
- if (functionCalls) {
632
+ if (functionCalls.length > 0) {
570
633
  toolCallChunks.push(
571
634
  ...functionCalls.map((fc) => ({
572
- ...fc,
573
- args: JSON.stringify(fc.args),
574
- // Un-commenting this causes LangChain to incorrectly merge tool calls together
575
- // index: extra.index,
576
635
  type: 'tool_call_chunk' as const,
577
- id: 'id' in fc && typeof fc.id === 'string' ? fc.id : uuidv4(),
636
+ id: fc?.id,
637
+ name: fc?.functionCall.name,
638
+ args: JSON.stringify(fc?.functionCall.args),
578
639
  }))
579
640
  );
580
641
  }
581
642
 
582
- const additional_kwargs: ChatGeneration['message']['additional_kwargs'] = {};
643
+ // Extract thought signatures from function calls for Gemini 3+
644
+ const functionThoughtSignatures = functionCalls.reduce(
645
+ (acc, fc) => {
646
+ if (
647
+ fc &&
648
+ 'thoughtSignature' in fc &&
649
+ typeof fc.thoughtSignature === 'string'
650
+ ) {
651
+ acc[fc.id] = fc.thoughtSignature;
652
+ }
653
+ return acc;
654
+ },
655
+ {} as Record<string, string>
656
+ );
657
+
658
+ const additional_kwargs: ChatGeneration['message']['additional_kwargs'] = {
659
+ [_FUNCTION_CALL_THOUGHT_SIGNATURES_MAP_KEY]: functionThoughtSignatures,
660
+ };
661
+
583
662
  if (reasoningParts.length > 0) {
584
663
  additional_kwargs.reasoning = reasoningParts.join('');
585
664
  }
@@ -608,6 +687,154 @@ export function convertResponseContentToChatGenerationChunk(
608
687
  });
609
688
  }
610
689
 
690
+ /**
691
+ * Maps a Google GenerateContentResult to a LangChain ChatResult
692
+ */
693
+ export function mapGenerateContentResultToChatResult(
694
+ response: EnhancedGenerateContentResponse,
695
+ extra?: {
696
+ usageMetadata: UsageMetadata | undefined;
697
+ }
698
+ ): ChatResult {
699
+ if (
700
+ !response.candidates ||
701
+ response.candidates.length === 0 ||
702
+ !response.candidates[0]
703
+ ) {
704
+ return {
705
+ generations: [],
706
+ llmOutput: {
707
+ filters: response.promptFeedback,
708
+ },
709
+ };
710
+ }
711
+ const [candidate] = response.candidates as [
712
+ Partial<GenerateContentCandidate> | undefined,
713
+ ];
714
+ const { content: candidateContent, ...generationInfo } = candidate ?? {};
715
+
716
+ // Extract function calls directly from parts to preserve thoughtSignature
717
+ const functionCalls =
718
+ candidateContent?.parts.reduce(
719
+ (acc, p) => {
720
+ if ('functionCall' in p && p.functionCall) {
721
+ acc.push({
722
+ ...p,
723
+ id:
724
+ 'id' in p.functionCall && typeof p.functionCall.id === 'string'
725
+ ? p.functionCall.id
726
+ : uuidv4(),
727
+ });
728
+ }
729
+ return acc;
730
+ },
731
+ [] as (FunctionCallPart & { id: string; thoughtSignature?: string })[]
732
+ ) ?? [];
733
+
734
+ let content: MessageContent | undefined;
735
+ const reasoningParts: string[] = [];
736
+ if (
737
+ Array.isArray(candidateContent?.parts) &&
738
+ candidateContent.parts.length === 1 &&
739
+ candidateContent.parts[0].text &&
740
+ !(
741
+ 'thought' in candidateContent.parts[0] &&
742
+ candidateContent.parts[0].thought === true
743
+ )
744
+ ) {
745
+ content = candidateContent.parts[0].text;
746
+ } else if (
747
+ Array.isArray(candidateContent?.parts) &&
748
+ candidateContent.parts.length > 0
749
+ ) {
750
+ content = candidateContent.parts
751
+ .map((p) => {
752
+ if ('text' in p && 'thought' in p && p.thought === true) {
753
+ reasoningParts.push(p.text ?? '');
754
+ return undefined;
755
+ } else if ('text' in p) {
756
+ return {
757
+ type: 'text',
758
+ text: p.text,
759
+ };
760
+ } else if ('executableCode' in p) {
761
+ return {
762
+ type: 'executableCode',
763
+ executableCode: p.executableCode,
764
+ };
765
+ } else if ('codeExecutionResult' in p) {
766
+ return {
767
+ type: 'codeExecutionResult',
768
+ codeExecutionResult: p.codeExecutionResult,
769
+ };
770
+ }
771
+ return p;
772
+ })
773
+ .filter((p) => p !== undefined);
774
+ } else {
775
+ content = [];
776
+ }
777
+ let text = '';
778
+ if (typeof content === 'string') {
779
+ text = content;
780
+ } else if (Array.isArray(content) && content.length > 0) {
781
+ const block = content.find((b) => 'text' in b) as
782
+ | { text: string }
783
+ | undefined;
784
+ text = block?.text ?? text;
785
+ }
786
+
787
+ const additional_kwargs: ChatGeneration['message']['additional_kwargs'] = {
788
+ ...generationInfo,
789
+ };
790
+ if (reasoningParts.length > 0) {
791
+ additional_kwargs.reasoning = reasoningParts.join('');
792
+ }
793
+
794
+ // Extract thought signatures from function calls for Gemini 3+
795
+ const functionThoughtSignatures = functionCalls.reduce(
796
+ (acc, fc) => {
797
+ if ('thoughtSignature' in fc && typeof fc.thoughtSignature === 'string') {
798
+ acc[fc.id] = fc.thoughtSignature;
799
+ }
800
+ return acc;
801
+ },
802
+ {} as Record<string, string>
803
+ );
804
+
805
+ const tool_calls = functionCalls.map((fc) => ({
806
+ type: 'tool_call' as const,
807
+ id: fc.id,
808
+ name: fc.functionCall.name,
809
+ args: fc.functionCall.args,
810
+ }));
811
+
812
+ // Store thought signatures map for later retrieval
813
+ additional_kwargs[_FUNCTION_CALL_THOUGHT_SIGNATURES_MAP_KEY] =
814
+ functionThoughtSignatures;
815
+
816
+ const generation: ChatGeneration = {
817
+ text,
818
+ message: new AIMessage({
819
+ content: content ?? '',
820
+ tool_calls,
821
+ additional_kwargs,
822
+ usage_metadata: extra?.usageMetadata,
823
+ }),
824
+ generationInfo,
825
+ };
826
+ return {
827
+ generations: [generation],
828
+ llmOutput: {
829
+ tokenUsage: {
830
+ promptTokens: extra?.usageMetadata?.input_tokens,
831
+ completionTokens: extra?.usageMetadata?.output_tokens,
832
+ totalTokens: extra?.usageMetadata?.total_tokens,
833
+ },
834
+ },
835
+ };
836
+ }
837
+
611
838
  export function convertToGenerativeAITools(
612
839
  tools: GoogleGenerativeAIToolType[]
613
840
  ): GoogleGenerativeAIFunctionDeclarationsTool[] {
@@ -121,12 +121,27 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
121
121
  );
122
122
  } catch (handlerError) {
123
123
  // eslint-disable-next-line no-console
124
- console.error(
125
- 'Error in errorHandler for tool',
126
- call.name,
127
- ':',
128
- handlerError
129
- );
124
+ console.error('Error in errorHandler:', {
125
+ toolName: call.name,
126
+ toolCallId: call.id,
127
+ toolArgs: call.args,
128
+ stepId: this.toolCallStepIds?.get(call.id!),
129
+ turn: this.toolUsageCount.get(call.name),
130
+ originalError: {
131
+ message: e.message,
132
+ stack: e.stack ?? undefined,
133
+ },
134
+ handlerError:
135
+ handlerError instanceof Error
136
+ ? {
137
+ message: handlerError.message,
138
+ stack: handlerError.stack ?? undefined,
139
+ }
140
+ : {
141
+ message: String(handlerError),
142
+ stack: undefined,
143
+ },
144
+ });
130
145
  }
131
146
  }
132
147
  return new ToolMessage({