@librechat/agents 3.1.67-dev.4 → 3.1.68-dev.0

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.
@@ -1,5 +1,51 @@
1
1
  import type { SummarizationTrigger } from '@/types';
2
2
 
3
+ const VALID_TRIGGER_TYPES = [
4
+ 'token_ratio',
5
+ 'remaining_tokens',
6
+ 'messages_to_refine',
7
+ ] as const;
8
+
9
+ /**
10
+ * Upper bound on the dedup set for unrecognized trigger types. Bounds memory in
11
+ * case a caller threads dynamic/user-provided strings through `trigger.type`.
12
+ * Well above the handful of legit misconfigurations a process would ever see.
13
+ */
14
+ const MAX_WARNED_TRIGGER_TYPES = 32;
15
+
16
+ const warnedUnrecognizedTriggerTypes = new Set<string>();
17
+
18
+ /**
19
+ * Warn (once per process, per unrecognized type) when the configured trigger
20
+ * type is something the runtime does not evaluate. Without this, a misconfigured
21
+ * `trigger.type` silently disables summarization with no visible signal.
22
+ *
23
+ * The dedup set is size-capped; on overflow we evict the oldest entry (Set
24
+ * preserves insertion order) so we keep bounded memory and still warn on
25
+ * recently-seen types.
26
+ */
27
+ function warnUnrecognizedTriggerType(type: string): void {
28
+ if (warnedUnrecognizedTriggerTypes.has(type)) {
29
+ return;
30
+ }
31
+ if (warnedUnrecognizedTriggerTypes.size >= MAX_WARNED_TRIGGER_TYPES) {
32
+ const oldest = warnedUnrecognizedTriggerTypes.values().next().value;
33
+ if (oldest !== undefined) {
34
+ warnedUnrecognizedTriggerTypes.delete(oldest);
35
+ }
36
+ }
37
+ warnedUnrecognizedTriggerTypes.add(type);
38
+ console.warn(
39
+ `[shouldTriggerSummarization] Unrecognized trigger.type: "${type}". ` +
40
+ `Summarization will not fire. Valid values: ${VALID_TRIGGER_TYPES.join(', ')}.`
41
+ );
42
+ }
43
+
44
+ /** For tests only. Resets the dedup set so warnings can be observed again. */
45
+ export function _resetUnrecognizedTriggerWarnings(): void {
46
+ warnedUnrecognizedTriggerTypes.clear();
47
+ }
48
+
3
49
  /**
4
50
  * Determines whether summarization should be triggered based on the configured trigger
5
51
  * and current context state.
@@ -98,5 +144,6 @@ export function shouldTriggerSummarization(params: {
98
144
  }
99
145
 
100
146
  // Unrecognized trigger type: cannot evaluate, do not fire.
147
+ warnUnrecognizedTriggerType(trigger.type);
101
148
  return false;
102
149
  }
@@ -366,6 +366,122 @@ type LogFn = (
366
366
  data?: Record<string, unknown>
367
367
  ) => void;
368
368
 
369
+ /**
370
+ * Extracts an HTTP status code from a thrown LLM-provider error. Returns
371
+ * `undefined` for non-object values (including `null` or `undefined`, both
372
+ * valid `throw` targets in JS) so callers never dereference a nullish
373
+ * value.
374
+ */
375
+ function extractHttpStatus(err: unknown): number | undefined {
376
+ if (err == null || typeof err !== 'object') {
377
+ return undefined;
378
+ }
379
+ const errRecord = err as Record<string, unknown>;
380
+ const direct = errRecord.status;
381
+ if (typeof direct === 'number') {
382
+ return direct;
383
+ }
384
+ const statusCode = errRecord.statusCode;
385
+ if (typeof statusCode === 'number') {
386
+ return statusCode;
387
+ }
388
+ const response = errRecord.response;
389
+ if (response != null && typeof response === 'object') {
390
+ const nested = (response as Record<string, unknown>).status;
391
+ if (typeof nested === 'number') {
392
+ return nested;
393
+ }
394
+ }
395
+ return undefined;
396
+ }
397
+
398
+ /**
399
+ * Formats a provider-level error for logging. Returns both a human-readable
400
+ * suffix (safe to include in the message string so it survives any host-side
401
+ * formatter) and a structured metadata bag for rich log backends.
402
+ */
403
+ function describeProviderError(
404
+ err: unknown,
405
+ provider: string,
406
+ modelName?: string
407
+ ): { suffix: string; data: Record<string, unknown> } {
408
+ const providerLabel = `${provider}/${modelName ?? '(no-model)'}`;
409
+ const errMsg = err instanceof Error ? err.message : String(err);
410
+
411
+ const data: Record<string, unknown> = {
412
+ provider,
413
+ model: modelName,
414
+ };
415
+ if (err instanceof Error) {
416
+ data.errorName = err.name;
417
+ data.errorStack = err.stack;
418
+ }
419
+
420
+ const status = extractHttpStatus(err);
421
+ const statusSuffix = status != null ? ` (HTTP ${status})` : '';
422
+ if (status != null) {
423
+ data.status = status;
424
+ }
425
+
426
+ return {
427
+ suffix: `[${providerLabel}]${statusSuffix}: ${errMsg}`,
428
+ data,
429
+ };
430
+ }
431
+
432
+ /**
433
+ * Formats an exhausted-fallback error. `tryFallbackProviders` throws the
434
+ * last fallback provider's error, which may be from any of the configured
435
+ * fallbacks — not the primary — so we label the log with the list of
436
+ * fallback providers attempted rather than mis-attributing to the primary.
437
+ *
438
+ * Entries in `fallbacks` are normally strongly typed, but we defend against
439
+ * malformed runtime config (null/undefined entries, missing `provider`
440
+ * field) so a recoverable summarization failure is never promoted to an
441
+ * uncaught exception from inside the logging path.
442
+ */
443
+ function describeFallbackError(
444
+ err: unknown,
445
+ fallbacks: unknown
446
+ ): { suffix: string; data: Record<string, unknown> } {
447
+ const errMsg = err instanceof Error ? err.message : String(err);
448
+ const list: ReadonlyArray<unknown> = Array.isArray(fallbacks)
449
+ ? fallbacks
450
+ : [];
451
+ const providerNames = list
452
+ .map((f) => {
453
+ if (f == null || typeof f !== 'object') {
454
+ return undefined;
455
+ }
456
+ const raw = (f as { provider?: unknown }).provider;
457
+ return raw != null ? String(raw) : undefined;
458
+ })
459
+ .filter((p): p is string => typeof p === 'string');
460
+ const label =
461
+ providerNames.length > 0
462
+ ? `fallbacks=[${providerNames.join(',')}]`
463
+ : 'no-fallbacks';
464
+
465
+ const data: Record<string, unknown> = {
466
+ fallbackProviders: providerNames,
467
+ fallbackCount: list.length,
468
+ };
469
+ if (err instanceof Error) {
470
+ data.errorName = err.name;
471
+ data.errorStack = err.stack;
472
+ }
473
+ const status = extractHttpStatus(err);
474
+ const statusSuffix = status != null ? ` (HTTP ${status})` : '';
475
+ if (status != null) {
476
+ data.status = status;
477
+ }
478
+
479
+ return {
480
+ suffix: `[${label}]${statusSuffix}: ${errMsg}`,
481
+ data,
482
+ };
483
+ }
484
+
369
485
  /**
370
486
  * Runs the summarization LLM call with primary + fallback providers,
371
487
  * falling back to a metadata stub when all calls fail.
@@ -389,18 +505,23 @@ async function executeSummarizationWithFallback(params: {
389
505
  log,
390
506
  } = params;
391
507
 
392
- const summarizationModel = initializeModel({
393
- provider: clientConfig.provider as Providers,
394
- clientOptions: clientConfig.clientOptions as t.ClientOptions,
395
- tools: agentContext.getToolsForBinding(),
396
- }) as t.ChatModel;
397
-
398
508
  const priorSummaryText = agentContext.getSummaryText()?.trim() ?? '';
399
509
 
400
510
  let summaryText = '';
401
511
  let summaryUsage: Partial<UsageMetadata> | undefined;
402
512
 
403
513
  try {
514
+ /**
515
+ * Initialize inside the try so that a misconfigured provider
516
+ * (e.g. an unrecognized summarization.provider) surfaces through the
517
+ * `log('error', ...)` path below rather than bubbling up silently.
518
+ */
519
+ const summarizationModel = initializeModel({
520
+ provider: clientConfig.provider as Providers,
521
+ clientOptions: clientConfig.clientOptions as t.ClientOptions,
522
+ tools: agentContext.getToolsForBinding(),
523
+ }) as t.ChatModel;
524
+
404
525
  const result = await summarizeWithCacheHit({
405
526
  model: summarizationModel,
406
527
  messages,
@@ -417,19 +538,20 @@ async function executeSummarizationWithFallback(params: {
417
538
  summaryText = result.text;
418
539
  summaryUsage = result.usage;
419
540
  } catch (primaryError) {
420
- log('error', 'Summarization LLM call failed', {
421
- error:
422
- primaryError instanceof Error
423
- ? primaryError.message
424
- : String(primaryError),
425
- provider: clientConfig.provider,
426
- model: clientConfig.modelName,
541
+ const primaryDescribed = describeProviderError(
542
+ primaryError,
543
+ clientConfig.provider,
544
+ clientConfig.modelName
545
+ );
546
+ log('error', `Summarization LLM call failed ${primaryDescribed.suffix}`, {
547
+ ...primaryDescribed.data,
427
548
  messagesToRefineCount: messages.length,
428
549
  });
429
550
 
430
- const fallbacks =
431
- (clientConfig.clientOptions as unknown as t.LLMConfig | undefined)
432
- ?.fallbacks ?? [];
551
+ const rawFallbacks = (
552
+ clientConfig.clientOptions as unknown as t.LLMConfig | undefined
553
+ )?.fallbacks;
554
+ const fallbacks = Array.isArray(rawFallbacks) ? rawFallbacks : [];
433
555
  if (fallbacks.length > 0) {
434
556
  try {
435
557
  const onChunk = createSummarizationChunkHandler({
@@ -462,18 +584,21 @@ async function executeSummarizationWithFallback(params: {
462
584
  );
463
585
  }
464
586
  } catch (fbErr) {
465
- log('warn', 'Fallback providers also failed', {
466
- error: fbErr instanceof Error ? fbErr.message : String(fbErr),
587
+ const fbDescribed = describeFallbackError(fbErr, fallbacks);
588
+ log('warn', `Fallback providers also failed ${fbDescribed.suffix}`, {
589
+ ...fbDescribed.data,
467
590
  });
468
591
  }
469
592
  }
470
593
  if (!summaryText) {
471
- log('warn', 'Summarization failed, falling back to metadata stub', {
472
- error:
473
- primaryError instanceof Error
474
- ? primaryError.message
475
- : String(primaryError),
476
- });
594
+ log(
595
+ 'warn',
596
+ `Summarization failed, falling back to metadata stub ${primaryDescribed.suffix}`,
597
+ {
598
+ ...primaryDescribed.data,
599
+ messagesToRefineCount: messages.length,
600
+ }
601
+ );
477
602
  summaryText = generateMetadataStub(messages);
478
603
  }
479
604
  }