@librechat/agents 3.7.18 → 3.7.19

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.
@@ -44,7 +44,7 @@ type LangfuseAttributeParams = AgentLangfuseHandlerParams & {
44
44
  };
45
45
  export declare function isExplicitLangfuseConfig(langfuse?: t.LangfuseConfig): boolean;
46
46
  export declare function createLibreChatTraceAttributes(attributes: LangfuseConfigTraceAttributes): LangfuseTraceAttributes;
47
- export declare function createLangfuseTraceMetadata({ messageId, parentMessageId, agentId, agentName, rootAgentId, rootAgentName, activeAgentId, activeAgentName, }: {
47
+ export declare function createLangfuseTraceMetadata({ messageId, parentMessageId, agentId, agentName, rootAgentId, rootAgentName, activeAgentId, activeAgentName, endpoint, model, provider, resolvedProvider, }: {
48
48
  messageId?: unknown;
49
49
  parentMessageId?: unknown;
50
50
  agentId?: unknown;
@@ -53,6 +53,10 @@ export declare function createLangfuseTraceMetadata({ messageId, parentMessageId
53
53
  rootAgentName?: unknown;
54
54
  activeAgentId?: unknown;
55
55
  activeAgentName?: unknown;
56
+ endpoint?: unknown;
57
+ model?: unknown;
58
+ provider?: unknown;
59
+ resolvedProvider?: unknown;
56
60
  }): LangfuseTraceMetadata;
57
61
  export declare function getLangfuseTraceName(traceMetadata?: LangfuseTraceMetadata, fallback?: string): string;
58
62
  export declare function shouldCreateLangfuseHandler(langfuse?: t.LangfuseConfig): boolean;
@@ -1,5 +1,5 @@
1
- import type { RunnableConfig } from '@langchain/core/runnables';
2
1
  import type { BaseMessage } from '@langchain/core/messages';
2
+ import type { RunnableConfig } from '@langchain/core/runnables';
3
3
  import type { ToolOutputReferenceRegistry } from '@/tools/toolOutputReferences';
4
4
  import type * as t from '@/types';
5
5
  declare const preparedProviderRequestBrand: unique symbol;
@@ -672,6 +672,8 @@ export interface LangfuseConfig {
672
672
  }
673
673
  interface AgentInputFields {
674
674
  agentId: string;
675
+ /** Logical endpoint selected by the host before provider resolution. */
676
+ endpoint?: string;
675
677
  /**
676
678
  * Partition key for transient code-session ids and file refs. Agents with
677
679
  * the same key share those refs; different execution profiles/scopes must
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@librechat/agents",
3
- "version": "3.7.18",
3
+ "version": "3.7.19",
4
4
  "reova": {
5
5
  "enabled": true,
6
6
  "endpoint": "https://telemetry.reo.dev/data"
@@ -104,6 +104,7 @@ export class AgentContext {
104
104
  ): AgentContext {
105
105
  const {
106
106
  agentId,
107
+ endpoint,
107
108
  codeSessionKey,
108
109
  name,
109
110
  provider,
@@ -135,6 +136,7 @@ export class AgentContext {
135
136
 
136
137
  const agentContext = new AgentContext({
137
138
  agentId,
139
+ endpoint,
138
140
  codeSessionKey,
139
141
  name: name ?? agentId,
140
142
  provider,
@@ -219,6 +221,8 @@ export class AgentContext {
219
221
 
220
222
  /** Agent identifier */
221
223
  agentId: string;
224
+ /** Logical endpoint selected by the host before provider resolution. */
225
+ endpoint: string;
222
226
  /** Partition for this agent's transient code session and file refs. */
223
227
  codeSessionKey?: string;
224
228
  /** Human-readable name for this agent (used in handoff context). Falls back to agentId if not provided. */
@@ -454,6 +458,7 @@ export class AgentContext {
454
458
 
455
459
  constructor({
456
460
  agentId,
461
+ endpoint,
457
462
  codeSessionKey,
458
463
  name,
459
464
  provider,
@@ -481,6 +486,7 @@ export class AgentContext {
481
486
  maxToolResultChars,
482
487
  }: {
483
488
  agentId: string;
489
+ endpoint?: string;
484
490
  codeSessionKey?: string;
485
491
  name?: string;
486
492
  provider: t.ProviderName;
@@ -508,6 +514,7 @@ export class AgentContext {
508
514
  maxToolResultChars?: number;
509
515
  }) {
510
516
  this.agentId = agentId;
517
+ this.endpoint = endpoint ?? provider;
511
518
  this.codeSessionKey = codeSessionKey;
512
519
  this.name = name;
513
520
  this.provider = provider;
@@ -151,6 +151,7 @@ import { shouldTraceToolNodeForLangfuse } from '@/langfuseToolOutputTracing';
151
151
  import { createLocalCodingToolBundle } from '@/tools/local/LocalCodingTools';
152
152
  import { SUBAGENT_REPLAY_CONTROLLER } from '@/tools/subagent/SubagentReplay';
153
153
  import { applyGraphRuntimeConfig } from '@/graphs/applyGraphRuntimeConfig';
154
+ import { isFadingTier, isInformativeFadingTier } from '@/messages/fading';
154
155
  import { createContextPressureMeter } from '@/llm/contextPressureMeter';
155
156
  import { safeDispatchCustomEvent, emitAgentLog } from '@/utils/events';
156
157
  import { prepareProviderRequest } from '@/llm/prepareProviderRequest';
@@ -163,7 +164,6 @@ import { initializeLangfuseTracing } from '@/instrumentation';
163
164
  import { shouldTriggerSummarization } from '@/summarization';
164
165
  import { isRunStepResumeState } from '@/tools/runStepResume';
165
166
  import { resolveLocalToolsForBinding } from '@/tools/local';
166
- import { isFadingTier, isInformativeFadingTier } from '@/messages/fading';
167
167
  import { createSummarizeNode } from '@/summarization/node';
168
168
  import { getTruncationStopReason } from '@/llm/truncation';
169
169
  import { messagesStateReducer } from '@/messages/reducer';
@@ -3992,6 +3992,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
3992
3992
  rootAgentName: this.agentContexts.get(this.defaultAgentId)?.name,
3993
3993
  activeAgentId: agentId,
3994
3994
  activeAgentName: agentContext.name,
3995
+ endpoint: agentContext.endpoint,
3995
3996
  });
3996
3997
  let langfuseHandler: CallbackEntry | undefined;
3997
3998
  let invokeConfig = {
package/src/langfuse.ts CHANGED
@@ -840,6 +840,10 @@ export function createLangfuseTraceMetadata({
840
840
  rootAgentName,
841
841
  activeAgentId,
842
842
  activeAgentName,
843
+ endpoint,
844
+ model,
845
+ provider,
846
+ resolvedProvider,
843
847
  }: {
844
848
  messageId?: unknown;
845
849
  parentMessageId?: unknown;
@@ -849,6 +853,10 @@ export function createLangfuseTraceMetadata({
849
853
  rootAgentName?: unknown;
850
854
  activeAgentId?: unknown;
851
855
  activeAgentName?: unknown;
856
+ endpoint?: unknown;
857
+ model?: unknown;
858
+ provider?: unknown;
859
+ resolvedProvider?: unknown;
852
860
  }): LangfuseTraceMetadata {
853
861
  return createTraceMetadata({
854
862
  messageId,
@@ -859,6 +867,10 @@ export function createLangfuseTraceMetadata({
859
867
  rootAgentName,
860
868
  activeAgentId,
861
869
  activeAgentName,
870
+ endpoint,
871
+ model,
872
+ provider,
873
+ resolvedProvider,
862
874
  });
863
875
  }
864
876
 
package/src/llm/invoke.ts CHANGED
@@ -692,11 +692,32 @@ export async function attemptInvoke(
692
692
  config?: RunnableConfig
693
693
  ): Promise<Partial<t.BaseGraphState>> {
694
694
  const provider = resolveAttemptProvider(params);
695
- const stampedConfig: RunnableConfig = {
695
+ const providerStampedConfig: RunnableConfig = {
696
696
  ...config,
697
697
  metadata: {
698
698
  ...(config?.metadata ?? {}),
699
699
  [Constants.INVOKED_PROVIDER]: provider,
700
+ provider,
701
+ resolvedProvider: provider,
702
+ },
703
+ };
704
+ const request = resolveAttemptRequest(params, providerStampedConfig);
705
+ const configuredModel = providerStampedConfig.metadata?.[
706
+ Constants.INVOKED_MODEL
707
+ ];
708
+ const modelId =
709
+ request.modelId ??
710
+ (typeof configuredModel === 'string' ? configuredModel : undefined);
711
+ const stampedConfig: RunnableConfig = {
712
+ ...providerStampedConfig,
713
+ metadata: {
714
+ ...providerStampedConfig.metadata,
715
+ ...(modelId == null
716
+ ? {}
717
+ : {
718
+ [Constants.INVOKED_MODEL]: modelId,
719
+ model: modelId,
720
+ }),
700
721
  /**
701
722
  * One `attemptInvoke` call is one model attempt; primary, fallback,
702
723
  * and retry attempts within a node otherwise share the same langgraph
@@ -727,7 +748,7 @@ export async function attemptInvoke(
727
748
  try {
728
749
  return await attemptInvokeBody(
729
750
  {
730
- request: resolveAttemptRequest(params, stampedConfig),
751
+ request,
731
752
  context: params.context,
732
753
  onChunk: params.onChunk,
733
754
  preemptAgentId: params.preemptAgentId,
@@ -1,5 +1,10 @@
1
+ import { Buffer } from 'node:buffer';
2
+ import type {
3
+ BaseMessage,
4
+ Data,
5
+ MessageContentComplex,
6
+ } from '@langchain/core/messages';
1
7
  import type { RunnableConfig } from '@langchain/core/runnables';
2
- import type { BaseMessage } from '@langchain/core/messages';
3
8
  import type { ToolOutputReferenceRegistry } from '@/tools/toolOutputReferences';
4
9
  import type * as t from '@/types';
5
10
  import {
@@ -20,14 +25,60 @@ import {
20
25
  import {
21
26
  stripAnthropicCacheControl,
22
27
  stripBedrockCacheControl,
28
+ cloneMessage,
23
29
  } from '@/messages/cache';
30
+ import {
31
+ isAnthropicLike,
32
+ isGoogleLike,
33
+ isOpenAILike,
34
+ } from '@/utils/llm';
24
35
  import { annotateMessagesForLLM } from '@/tools/toolOutputReferences';
25
36
  import { providerRequiresStrictAlternation } from '@/llm/providers';
26
- import { isAnthropicLike, isOpenAILike } from '@/utils/llm';
27
37
  import { getProviderFamily } from '@/llm/providerRegistry';
28
38
  import { Providers } from '@/common';
29
39
 
30
40
  const preparedProviderRequestBrand = Symbol('PreparedProviderRequest');
41
+ const OMITTED_ATTACHMENT_TEXT =
42
+ '[Attachment omitted because its binary format is unsupported by this provider.]';
43
+
44
+ const BEDROCK_DOCUMENT_MIME_TYPES: Readonly<Partial<Record<string, string>>> = {
45
+ csv: 'text/csv',
46
+ doc: 'application/msword',
47
+ docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
48
+ html: 'text/html',
49
+ md: 'text/markdown',
50
+ pdf: 'application/pdf',
51
+ txt: 'text/plain',
52
+ xls: 'application/vnd.ms-excel',
53
+ xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
54
+ };
55
+
56
+ interface SerializedBuffer {
57
+ type: 'Buffer';
58
+ data: number[];
59
+ }
60
+
61
+ interface BedrockDocumentBlock {
62
+ type: 'document';
63
+ document: {
64
+ name: string;
65
+ format: string;
66
+ source: {
67
+ bytes: Uint8Array | SerializedBuffer;
68
+ };
69
+ };
70
+ }
71
+
72
+ type StandardBase64FileBlock = Data.StandardFileBlock & {
73
+ source_type: 'base64';
74
+ mime_type: string;
75
+ data: string;
76
+ };
77
+
78
+ type ProviderContentBlock =
79
+ | MessageContentComplex
80
+ | BedrockDocumentBlock
81
+ | StandardBase64FileBlock;
31
82
 
32
83
  export type ProviderMessageProjectionMode =
33
84
  | 'chat-messages'
@@ -159,16 +210,216 @@ interface ProjectMessagesForProviderParams {
159
210
  callOptions?: unknown;
160
211
  }
161
212
 
213
+ function isSerializedBuffer(value: object): value is SerializedBuffer {
214
+ return (
215
+ 'type' in value &&
216
+ value.type === 'Buffer' &&
217
+ 'data' in value &&
218
+ Array.isArray(value.data)
219
+ );
220
+ }
221
+
222
+ function isBedrockDocumentBlock(
223
+ block: ProviderContentBlock
224
+ ): block is BedrockDocumentBlock {
225
+ if (block.type !== 'document' || !('document' in block)) {
226
+ return false;
227
+ }
228
+ const document = block.document;
229
+ if (typeof document !== 'object' || document == null) {
230
+ return false;
231
+ }
232
+ if (
233
+ !('name' in document) ||
234
+ typeof document.name !== 'string' ||
235
+ !('format' in document) ||
236
+ typeof document.format !== 'string' ||
237
+ !('source' in document) ||
238
+ typeof document.source !== 'object' ||
239
+ document.source == null ||
240
+ !('bytes' in document.source)
241
+ ) {
242
+ return false;
243
+ }
244
+ const bytes = document.source.bytes;
245
+ return (
246
+ bytes instanceof Uint8Array ||
247
+ (typeof bytes === 'object' && bytes != null && isSerializedBuffer(bytes))
248
+ );
249
+ }
250
+
251
+ function toStandardFileBlock(
252
+ block: BedrockDocumentBlock
253
+ ): StandardBase64FileBlock | undefined {
254
+ const mimeType = BEDROCK_DOCUMENT_MIME_TYPES[block.document.format];
255
+ if (mimeType == null) {
256
+ return undefined;
257
+ }
258
+ const source = block.document.source.bytes;
259
+ const bytes = source instanceof Uint8Array ? source : source.data;
260
+ return {
261
+ type: 'file',
262
+ source_type: 'base64',
263
+ mime_type: mimeType,
264
+ data: Buffer.from(bytes).toString('base64'),
265
+ metadata: { name: block.document.name },
266
+ };
267
+ }
268
+
269
+ function isStandardFileBlock(
270
+ block: ProviderContentBlock
271
+ ): block is StandardBase64FileBlock {
272
+ return (
273
+ block.type === 'file' &&
274
+ 'source_type' in block &&
275
+ block.source_type === 'base64' &&
276
+ 'mime_type' in block &&
277
+ typeof block.mime_type === 'string' &&
278
+ 'data' in block &&
279
+ typeof block.data === 'string'
280
+ );
281
+ }
282
+
283
+ function shouldRetainStandardFile(
284
+ provider: t.ProviderName,
285
+ mimeType: string
286
+ ): boolean {
287
+ const normalizedMimeType = mimeType.split(';', 1)[0].trim().toLowerCase();
288
+ if (
289
+ provider === Providers.ANTHROPIC ||
290
+ getProviderFamily(provider) === 'anthropic'
291
+ ) {
292
+ return (
293
+ normalizedMimeType === 'application/pdf' ||
294
+ normalizedMimeType === 'image/jpeg' ||
295
+ normalizedMimeType === 'image/png' ||
296
+ normalizedMimeType === 'image/gif' ||
297
+ normalizedMimeType === 'image/webp'
298
+ );
299
+ }
300
+ if (isOpenAILike(provider)) {
301
+ return normalizedMimeType === 'application/pdf';
302
+ }
303
+ return true;
304
+ }
305
+
306
+ function canProjectBedrockDocument(
307
+ provider: t.ProviderName,
308
+ mimeType: string
309
+ ): boolean {
310
+ if (isGoogleLike(provider)) {
311
+ return true;
312
+ }
313
+ if (
314
+ provider === Providers.ANTHROPIC ||
315
+ getProviderFamily(provider) === 'anthropic' ||
316
+ isOpenAILike(provider)
317
+ ) {
318
+ return mimeType === 'application/pdf';
319
+ }
320
+ return false;
321
+ }
322
+
323
+ function isBlankTextBlock(block: ProviderContentBlock): boolean {
324
+ return (
325
+ block.type === 'text' &&
326
+ 'text' in block &&
327
+ typeof block.text === 'string' &&
328
+ block.text.trim() === ''
329
+ );
330
+ }
331
+
332
+ function copyUsableContentPrefix(
333
+ sourceContent: ProviderContentBlock[],
334
+ end: number
335
+ ): ProviderContentBlock[] {
336
+ const content: ProviderContentBlock[] = [];
337
+ for (let index = 0; index < end; index++) {
338
+ const block = sourceContent[index];
339
+ if (!isBlankTextBlock(block)) {
340
+ content.push(block);
341
+ }
342
+ }
343
+ return content;
344
+ }
345
+
346
+ /** Reprojects persisted provider-native attachments without changing history. */
347
+ function projectAttachmentsForProvider(
348
+ messages: BaseMessage[],
349
+ provider: t.ProviderName
350
+ ): BaseMessage[] {
351
+ if (
352
+ provider === Providers.BEDROCK ||
353
+ getProviderFamily(provider) === 'bedrock'
354
+ ) {
355
+ return messages;
356
+ }
357
+ let projected: BaseMessage[] | undefined;
358
+ for (let messageIndex = 0; messageIndex < messages.length; messageIndex++) {
359
+ const message = messages[messageIndex];
360
+ if (!Array.isArray(message.content)) {
361
+ continue;
362
+ }
363
+ const sourceContent = message.content as ProviderContentBlock[];
364
+ let content: ProviderContentBlock[] | undefined;
365
+ for (let blockIndex = 0; blockIndex < sourceContent.length; blockIndex++) {
366
+ const block = sourceContent[blockIndex];
367
+ if (isStandardFileBlock(block)) {
368
+ if (shouldRetainStandardFile(provider, block.mime_type)) {
369
+ content?.push(block);
370
+ continue;
371
+ }
372
+ content ??= copyUsableContentPrefix(sourceContent, blockIndex);
373
+ continue;
374
+ }
375
+ if (!isBedrockDocumentBlock(block)) {
376
+ if (content != null && !isBlankTextBlock(block)) {
377
+ content.push(block);
378
+ }
379
+ continue;
380
+ }
381
+ content ??= copyUsableContentPrefix(sourceContent, blockIndex);
382
+ const mimeType = BEDROCK_DOCUMENT_MIME_TYPES[block.document.format];
383
+ if (
384
+ mimeType == null ||
385
+ !canProjectBedrockDocument(provider, mimeType)
386
+ ) {
387
+ continue;
388
+ }
389
+ const standardFile = toStandardFileBlock(block);
390
+ if (standardFile == null) {
391
+ continue;
392
+ }
393
+ content.push(standardFile);
394
+ }
395
+ if (content == null) {
396
+ continue;
397
+ }
398
+ if (content.length === 0) {
399
+ content.push({ type: 'text', text: OMITTED_ATTACHMENT_TEXT });
400
+ }
401
+ projected ??= [...messages];
402
+ projected[messageIndex] = cloneMessage(
403
+ message,
404
+ content as MessageContentComplex[]
405
+ );
406
+ }
407
+ return projected ?? messages;
408
+ }
409
+
162
410
  function projectMessagesForProviderMode(
163
411
  { messages, provider, maxToolResultChars }: ProjectMessagesForProviderParams,
164
412
  projectionMode: ProviderMessageProjectionMode
165
413
  ): BaseMessage[] {
166
414
  const providerFamily = getProviderFamily(provider);
167
415
  const nativeOpenAIResponses = projectionMode === 'openai-responses';
168
- const providerInputMessages = projectToolStreamContentForProvider(
169
- messages,
170
- nativeOpenAIResponses ? 'native' : 'fallback',
171
- maxToolResultChars
416
+ const providerInputMessages = projectAttachmentsForProvider(
417
+ projectToolStreamContentForProvider(
418
+ messages,
419
+ nativeOpenAIResponses ? 'native' : 'fallback',
420
+ maxToolResultChars
421
+ ),
422
+ provider
172
423
  );
173
424
  if (nativeOpenAIResponses) {
174
425
  return projectOpenAIResponsesToolMessageContent(
@@ -853,6 +853,8 @@ export interface LangfuseConfig {
853
853
 
854
854
  interface AgentInputFields {
855
855
  agentId: string;
856
+ /** Logical endpoint selected by the host before provider resolution. */
857
+ endpoint?: string;
856
858
  /**
857
859
  * Partition key for transient code-session ids and file refs. Agents with
858
860
  * the same key share those refs; different execution profiles/scopes must