@librechat/agents 3.0.25 → 3.0.27

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)) {
@@ -301,20 +316,6 @@ function _convertLangChainContentToPart(
301
316
  mimeType,
302
317
  },
303
318
  };
304
- } else if (
305
- content.type === 'document' ||
306
- content.type === 'audio' ||
307
- content.type === 'video'
308
- ) {
309
- if (!isMultimodalModel) {
310
- throw new Error(`This model does not support ${content.type}s`);
311
- }
312
- return {
313
- inlineData: {
314
- data: content.data,
315
- mimeType: content.mimeType,
316
- },
317
- };
318
319
  } else if (content.type === 'media') {
319
320
  return messageContentMedia(content);
320
321
  } else if (content.type === 'tool_use') {
@@ -352,7 +353,8 @@ function _convertLangChainContentToPart(
352
353
  export function convertMessageContentToParts(
353
354
  message: BaseMessage,
354
355
  isMultimodalModel: boolean,
355
- previousMessages: BaseMessage[]
356
+ previousMessages: BaseMessage[],
357
+ model?: string
356
358
  ): Part[] {
357
359
  if (isToolMessage(message)) {
358
360
  const messageName =
@@ -409,13 +411,33 @@ export function convertMessageContentToParts(
409
411
  );
410
412
  }
411
413
 
412
- if (isAIMessage(message) && message.tool_calls?.length != null) {
413
- functionCalls = message.tool_calls.map((tc) => {
414
+ const functionThoughtSignatures = (
415
+ message.additional_kwargs as BaseMessage['additional_kwargs'] | undefined
416
+ )?.[_FUNCTION_CALL_THOUGHT_SIGNATURES_MAP_KEY] as
417
+ | Record<string, string>
418
+ | undefined;
419
+
420
+ if (isAIMessage(message) && (message.tool_calls?.length ?? 0) > 0) {
421
+ functionCalls = (message.tool_calls ?? []).map((tc) => {
422
+ const thoughtSignature = iife(() => {
423
+ if (tc.id != null && tc.id !== '') {
424
+ const signature = functionThoughtSignatures?.[tc.id];
425
+ if (signature != null && signature !== '') {
426
+ return signature;
427
+ }
428
+ }
429
+ if (model?.includes('gemini-3') === true) {
430
+ return DUMMY_SIGNATURE;
431
+ }
432
+ return '';
433
+ });
434
+
414
435
  return {
415
436
  functionCall: {
416
437
  name: tc.name,
417
438
  args: tc.args,
418
439
  },
440
+ ...(thoughtSignature ? { thoughtSignature } : {}),
419
441
  };
420
442
  });
421
443
  }
@@ -426,7 +448,9 @@ export function convertMessageContentToParts(
426
448
  export function convertBaseMessagesToContent(
427
449
  messages: BaseMessage[],
428
450
  isMultimodalModel: boolean,
429
- convertSystemMessageToHumanContent: boolean = false
451
+ convertSystemMessageToHumanContent: boolean = false,
452
+
453
+ model?: string
430
454
  ): Content[] | undefined {
431
455
  return messages.reduce<{
432
456
  content: Content[] | undefined;
@@ -456,7 +480,8 @@ export function convertBaseMessagesToContent(
456
480
  const parts = convertMessageContentToParts(
457
481
  message,
458
482
  isMultimodalModel,
459
- messages.slice(0, index)
483
+ messages.slice(0, index),
484
+ model
460
485
  );
461
486
 
462
487
  if (acc.mergeWithPreviousContent) {
@@ -505,11 +530,32 @@ export function convertResponseContentToChatGenerationChunk(
505
530
  if (!response.candidates || response.candidates.length === 0) {
506
531
  return null;
507
532
  }
508
- const functionCalls = response.functionCalls();
509
533
  const [candidate] = response.candidates as [
510
534
  Partial<GenerateContentCandidate> | undefined,
511
535
  ];
512
536
  const { content: candidateContent, ...generationInfo } = candidate ?? {};
537
+
538
+ // Extract function calls directly from parts to preserve thoughtSignature
539
+ const functionCalls =
540
+ (candidateContent?.parts as Part[] | undefined)?.reduce(
541
+ (acc, p) => {
542
+ if ('functionCall' in p && p.functionCall) {
543
+ acc.push({
544
+ ...p,
545
+ id:
546
+ 'id' in p.functionCall && typeof p.functionCall.id === 'string'
547
+ ? p.functionCall.id
548
+ : uuidv4(),
549
+ });
550
+ }
551
+ return acc;
552
+ },
553
+ [] as (
554
+ | undefined
555
+ | (FunctionCallPart & { id: string; thoughtSignature?: string })
556
+ )[]
557
+ ) ?? [];
558
+
513
559
  let content: MessageContent | undefined;
514
560
  // Checks if some parts do not have text. If false, it means that the content is a string.
515
561
  const reasoningParts: string[] = [];
@@ -529,27 +575,30 @@ export function convertResponseContentToChatGenerationChunk(
529
575
  }
530
576
  content = textParts.join('');
531
577
  } 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
- });
578
+ content = candidateContent.parts
579
+ .map((p) => {
580
+ if ('text' in p && 'thought' in p && p.thought === true) {
581
+ reasoningParts.push(p.text ?? '');
582
+ return undefined;
583
+ } else if ('text' in p) {
584
+ return {
585
+ type: 'text',
586
+ text: p.text,
587
+ };
588
+ } else if ('executableCode' in p) {
589
+ return {
590
+ type: 'executableCode',
591
+ executableCode: p.executableCode,
592
+ };
593
+ } else if ('codeExecutionResult' in p) {
594
+ return {
595
+ type: 'codeExecutionResult',
596
+ codeExecutionResult: p.codeExecutionResult,
597
+ };
598
+ }
599
+ return p;
600
+ })
601
+ .filter((p) => p !== undefined);
553
602
  } else {
554
603
  // no content returned - likely due to abnormal stop reason, e.g. malformed function call
555
604
  content = [];
@@ -566,20 +615,36 @@ export function convertResponseContentToChatGenerationChunk(
566
615
  }
567
616
 
568
617
  const toolCallChunks: ToolCallChunk[] = [];
569
- if (functionCalls) {
618
+ if (functionCalls.length > 0) {
570
619
  toolCallChunks.push(
571
620
  ...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
621
  type: 'tool_call_chunk' as const,
577
- id: 'id' in fc && typeof fc.id === 'string' ? fc.id : uuidv4(),
622
+ id: fc?.id,
623
+ name: fc?.functionCall.name,
624
+ args: JSON.stringify(fc?.functionCall.args),
578
625
  }))
579
626
  );
580
627
  }
581
628
 
582
- const additional_kwargs: ChatGeneration['message']['additional_kwargs'] = {};
629
+ // Extract thought signatures from function calls for Gemini 3+
630
+ const functionThoughtSignatures = functionCalls.reduce(
631
+ (acc, fc) => {
632
+ if (
633
+ fc &&
634
+ 'thoughtSignature' in fc &&
635
+ typeof fc.thoughtSignature === 'string'
636
+ ) {
637
+ acc[fc.id] = fc.thoughtSignature;
638
+ }
639
+ return acc;
640
+ },
641
+ {} as Record<string, string>
642
+ );
643
+
644
+ const additional_kwargs: ChatGeneration['message']['additional_kwargs'] = {
645
+ [_FUNCTION_CALL_THOUGHT_SIGNATURES_MAP_KEY]: functionThoughtSignatures,
646
+ };
647
+
583
648
  if (reasoningParts.length > 0) {
584
649
  additional_kwargs.reasoning = reasoningParts.join('');
585
650
  }
@@ -608,6 +673,154 @@ export function convertResponseContentToChatGenerationChunk(
608
673
  });
609
674
  }
610
675
 
676
+ /**
677
+ * Maps a Google GenerateContentResult to a LangChain ChatResult
678
+ */
679
+ export function mapGenerateContentResultToChatResult(
680
+ response: EnhancedGenerateContentResponse,
681
+ extra?: {
682
+ usageMetadata: UsageMetadata | undefined;
683
+ }
684
+ ): ChatResult {
685
+ if (
686
+ !response.candidates ||
687
+ response.candidates.length === 0 ||
688
+ !response.candidates[0]
689
+ ) {
690
+ return {
691
+ generations: [],
692
+ llmOutput: {
693
+ filters: response.promptFeedback,
694
+ },
695
+ };
696
+ }
697
+ const [candidate] = response.candidates as [
698
+ Partial<GenerateContentCandidate> | undefined,
699
+ ];
700
+ const { content: candidateContent, ...generationInfo } = candidate ?? {};
701
+
702
+ // Extract function calls directly from parts to preserve thoughtSignature
703
+ const functionCalls =
704
+ candidateContent?.parts.reduce(
705
+ (acc, p) => {
706
+ if ('functionCall' in p && p.functionCall) {
707
+ acc.push({
708
+ ...p,
709
+ id:
710
+ 'id' in p.functionCall && typeof p.functionCall.id === 'string'
711
+ ? p.functionCall.id
712
+ : uuidv4(),
713
+ });
714
+ }
715
+ return acc;
716
+ },
717
+ [] as (FunctionCallPart & { id: string; thoughtSignature?: string })[]
718
+ ) ?? [];
719
+
720
+ let content: MessageContent | undefined;
721
+ const reasoningParts: string[] = [];
722
+ if (
723
+ Array.isArray(candidateContent?.parts) &&
724
+ candidateContent.parts.length === 1 &&
725
+ candidateContent.parts[0].text &&
726
+ !(
727
+ 'thought' in candidateContent.parts[0] &&
728
+ candidateContent.parts[0].thought === true
729
+ )
730
+ ) {
731
+ content = candidateContent.parts[0].text;
732
+ } else if (
733
+ Array.isArray(candidateContent?.parts) &&
734
+ candidateContent.parts.length > 0
735
+ ) {
736
+ content = candidateContent.parts
737
+ .map((p) => {
738
+ if ('text' in p && 'thought' in p && p.thought === true) {
739
+ reasoningParts.push(p.text ?? '');
740
+ return undefined;
741
+ } else if ('text' in p) {
742
+ return {
743
+ type: 'text',
744
+ text: p.text,
745
+ };
746
+ } else if ('executableCode' in p) {
747
+ return {
748
+ type: 'executableCode',
749
+ executableCode: p.executableCode,
750
+ };
751
+ } else if ('codeExecutionResult' in p) {
752
+ return {
753
+ type: 'codeExecutionResult',
754
+ codeExecutionResult: p.codeExecutionResult,
755
+ };
756
+ }
757
+ return p;
758
+ })
759
+ .filter((p) => p !== undefined);
760
+ } else {
761
+ content = [];
762
+ }
763
+ let text = '';
764
+ if (typeof content === 'string') {
765
+ text = content;
766
+ } else if (Array.isArray(content) && content.length > 0) {
767
+ const block = content.find((b) => 'text' in b) as
768
+ | { text: string }
769
+ | undefined;
770
+ text = block?.text ?? text;
771
+ }
772
+
773
+ const additional_kwargs: ChatGeneration['message']['additional_kwargs'] = {
774
+ ...generationInfo,
775
+ };
776
+ if (reasoningParts.length > 0) {
777
+ additional_kwargs.reasoning = reasoningParts.join('');
778
+ }
779
+
780
+ // Extract thought signatures from function calls for Gemini 3+
781
+ const functionThoughtSignatures = functionCalls.reduce(
782
+ (acc, fc) => {
783
+ if ('thoughtSignature' in fc && typeof fc.thoughtSignature === 'string') {
784
+ acc[fc.id] = fc.thoughtSignature;
785
+ }
786
+ return acc;
787
+ },
788
+ {} as Record<string, string>
789
+ );
790
+
791
+ const tool_calls = functionCalls.map((fc) => ({
792
+ type: 'tool_call' as const,
793
+ id: fc.id,
794
+ name: fc.functionCall.name,
795
+ args: fc.functionCall.args,
796
+ }));
797
+
798
+ // Store thought signatures map for later retrieval
799
+ additional_kwargs[_FUNCTION_CALL_THOUGHT_SIGNATURES_MAP_KEY] =
800
+ functionThoughtSignatures;
801
+
802
+ const generation: ChatGeneration = {
803
+ text,
804
+ message: new AIMessage({
805
+ content: content ?? '',
806
+ tool_calls,
807
+ additional_kwargs,
808
+ usage_metadata: extra?.usageMetadata,
809
+ }),
810
+ generationInfo,
811
+ };
812
+ return {
813
+ generations: [generation],
814
+ llmOutput: {
815
+ tokenUsage: {
816
+ promptTokens: extra?.usageMetadata?.input_tokens,
817
+ completionTokens: extra?.usageMetadata?.output_tokens,
818
+ totalTokens: extra?.usageMetadata?.total_tokens,
819
+ },
820
+ },
821
+ };
822
+ }
823
+
611
824
  export function convertToGenerativeAITools(
612
825
  tools: GoogleGenerativeAIToolType[]
613
826
  ): GoogleGenerativeAIFunctionDeclarationsTool[] {