@librechat/agents 3.1.71-dev.0 → 3.1.71-dev.1

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.
@@ -10,7 +10,7 @@ import { RunnableCallable } from '../utils/run.mjs';
10
10
  import 'ai-tokenizer';
11
11
  import 'zod-to-json-schema';
12
12
  import { executeHooks } from '../hooks/executeHooks.mjs';
13
- import { ToolOutputReferenceRegistry, annotateToolOutputWithReference, buildReferenceKey } from './toolOutputReferences.mjs';
13
+ import { ToolOutputReferenceRegistry, buildReferenceKey } from './toolOutputReferences.mjs';
14
14
 
15
15
  /**
16
16
  * Helper to check if a value is a Send object
@@ -300,13 +300,17 @@ class ToolNode extends RunnableCallable {
300
300
  const isError = toolMsg.status === 'error';
301
301
  if (isError) {
302
302
  /**
303
- * Error ToolMessages bypass registration/annotation but must
304
- * still carry the unresolved-refs hint so the LLM can
305
- * self-correct when its reference key caused the failure.
303
+ * Error ToolMessages bypass registration but still stamp the
304
+ * unresolved-refs hint into `additional_kwargs` so the lazy
305
+ * annotation transform surfaces it to the LLM, letting the
306
+ * model self-correct when its reference key caused the
307
+ * failure. Persisted `content` stays clean.
306
308
  */
307
- if (unresolvedRefs.length > 0 &&
308
- typeof toolMsg.content === 'string') {
309
- toolMsg.content = this.applyOutputReference(runId, toolMsg.content, toolMsg.content, undefined, unresolvedRefs);
309
+ if (unresolvedRefs.length > 0) {
310
+ toolMsg.additional_kwargs = {
311
+ ...toolMsg.additional_kwargs,
312
+ _unresolvedRefs: unresolvedRefs,
313
+ };
310
314
  }
311
315
  return toolMsg;
312
316
  }
@@ -314,7 +318,14 @@ class ToolNode extends RunnableCallable {
314
318
  if (typeof toolMsg.content === 'string') {
315
319
  const rawContent = toolMsg.content;
316
320
  const llmContent = truncateToolResultContent(rawContent, this.maxToolResultChars);
317
- toolMsg.content = this.applyOutputReference(runId, llmContent, rawContent, refKey, unresolvedRefs);
321
+ toolMsg.content = llmContent;
322
+ const refMeta = this.recordOutputReference(runId, rawContent, refKey, unresolvedRefs);
323
+ if (refMeta != null) {
324
+ toolMsg.additional_kwargs = {
325
+ ...toolMsg.additional_kwargs,
326
+ ...refMeta,
327
+ };
328
+ }
318
329
  }
319
330
  else {
320
331
  /**
@@ -322,22 +333,16 @@ class ToolNode extends RunnableCallable {
322
333
  * image). Known limitation: we cannot register under a
323
334
  * reference key because there's no canonical serialized
324
335
  * form. Warn once per tool per run when the caller
325
- * intended to register.
326
- *
327
- * Still surface unresolved-ref warnings so the LLM gets
328
- * the self-correction signal that the string and error
329
- * paths already emit. Prepended as a leading text block
330
- * to keep the original content ordering intact.
336
+ * intended to register. The unresolved-refs hint is still
337
+ * stamped as metadata; the lazy transform prepends a text
338
+ * block at request time so the LLM gets the self-correction
339
+ * signal.
331
340
  */
332
- if (unresolvedRefs.length > 0 && Array.isArray(toolMsg.content)) {
333
- const warningBlock = {
334
- type: 'text',
335
- text: `[unresolved refs: ${unresolvedRefs.join(', ')}]`,
341
+ if (unresolvedRefs.length > 0) {
342
+ toolMsg.additional_kwargs = {
343
+ ...toolMsg.additional_kwargs,
344
+ _unresolvedRefs: unresolvedRefs,
336
345
  };
337
- toolMsg.content = [
338
- warningBlock,
339
- ...toolMsg.content,
340
- ];
341
346
  }
342
347
  if (refKey != null &&
343
348
  this.toolOutputRegistry.claimWarnOnce(runId, call.name)) {
@@ -351,12 +356,15 @@ class ToolNode extends RunnableCallable {
351
356
  }
352
357
  const rawContent = typeof output === 'string' ? output : JSON.stringify(output);
353
358
  const truncated = truncateToolResultContent(rawContent, this.maxToolResultChars);
354
- const content = this.applyOutputReference(runId, truncated, rawContent, refKey, unresolvedRefs);
359
+ const refMeta = this.recordOutputReference(runId, rawContent, refKey, unresolvedRefs);
355
360
  return new ToolMessage({
356
361
  status: 'success',
357
362
  name: tool.name,
358
- content,
363
+ content: truncated,
359
364
  tool_call_id: call.id,
365
+ ...(refMeta != null && {
366
+ additional_kwargs: refMeta,
367
+ }),
360
368
  });
361
369
  }
362
370
  catch (_e) {
@@ -400,51 +408,64 @@ class ToolNode extends RunnableCallable {
400
408
  });
401
409
  }
402
410
  }
403
- let errorContent = `Error: ${e.message}\n Please fix your mistakes.`;
404
- if (unresolvedRefs.length > 0) {
405
- errorContent = this.applyOutputReference(runId, errorContent, errorContent, undefined, unresolvedRefs);
406
- }
411
+ const errorContent = `Error: ${e.message}\n Please fix your mistakes.`;
412
+ const refMeta = unresolvedRefs.length > 0
413
+ ? this.recordOutputReference(runId, errorContent, undefined, unresolvedRefs)
414
+ : undefined;
407
415
  return new ToolMessage({
408
416
  status: 'error',
409
417
  content: errorContent,
410
418
  name: call.name,
411
419
  tool_call_id: call.id ?? '',
420
+ ...(refMeta != null && {
421
+ additional_kwargs: refMeta,
422
+ }),
412
423
  });
413
424
  }
414
425
  }
415
426
  /**
416
- * Finalizes the LLM-visible content for a tool call and (when a
417
- * `refKey` is provided) registers the full, raw output under that
418
- * key.
427
+ * Registers the full, raw output under `refKey` (when provided) and
428
+ * builds the per-message ref metadata stamped onto the resulting
429
+ * `ToolMessage.additional_kwargs`. The metadata is read at LLM-
430
+ * request time by `annotateMessagesForLLM` to produce a transient
431
+ * annotated copy of the message — the persisted `content` itself
432
+ * stays clean.
419
433
  *
420
- * @param llmContent The content string the LLM will see. This is
421
- * the already-truncated, post-hook view; the annotation is
422
- * applied on top of it.
423
434
  * @param registryContent The full, untruncated output to store in
424
435
  * the registry so `{{tool<i>turn<n>}}` substitutions deliver the
425
436
  * complete payload. Ignored when `refKey` is undefined.
426
437
  * @param refKey Precomputed `tool<i>turn<n>` key, or undefined when
427
438
  * the output is not to be registered (errors, disabled feature,
428
439
  * unavailable batch/turn).
429
- * @param unresolved Placeholder keys that did not resolve; appended
430
- * as `[unresolved refs: …]` so the LLM can self-correct.
431
- *
432
- * `refKey` is passed in (rather than built from `this.currentTurn`)
433
- * so parallel `invoke()` calls on the same ToolNode cannot race on
434
- * the shared turn field.
440
+ * @param unresolved Placeholder keys that did not resolve; surfaced
441
+ * to the LLM lazily so it can self-correct.
442
+ * @returns A `ToolMessageRefMetadata` object when there is anything
443
+ * to stamp, otherwise `undefined`.
435
444
  */
436
- applyOutputReference(runId, llmContent, registryContent, refKey, unresolved) {
445
+ recordOutputReference(runId, registryContent, refKey, unresolved) {
437
446
  if (this.toolOutputRegistry != null && refKey != null) {
438
447
  this.toolOutputRegistry.set(runId, refKey, registryContent);
439
448
  }
440
- /**
441
- * `annotateToolOutputWithReference` handles both the ref-key and
442
- * unresolved-refs cases together so JSON-object outputs stay
443
- * parseable: unresolved refs land in an `_unresolved_refs` field
444
- * instead of as a trailing text line that would break
445
- * `JSON.parse` for downstream consumers.
446
- */
447
- return annotateToolOutputWithReference(llmContent, refKey, unresolved);
449
+ if (refKey == null && unresolved.length === 0)
450
+ return undefined;
451
+ const meta = {};
452
+ if (refKey != null) {
453
+ meta._refKey = refKey;
454
+ /**
455
+ * Stamp the registry scope alongside the key so the lazy
456
+ * annotation transform can look up the right bucket. Anonymous
457
+ * invocations get a synthetic per-batch scope (`\0anon-<n>`)
458
+ * that `attemptInvoke` cannot derive from
459
+ * `config.configurable.run_id` — without this, anonymous-run
460
+ * refs would silently fail registry lookup and the LLM would
461
+ * never see `[ref: …]` markers for outputs that were registered.
462
+ */
463
+ if (runId != null)
464
+ meta._refScope = runId;
465
+ }
466
+ if (unresolved.length > 0)
467
+ meta._unresolvedRefs = unresolved;
468
+ return meta;
448
469
  }
449
470
  /**
450
471
  * Builds code session context for injection into event-driven tool calls.
@@ -779,19 +800,24 @@ class ToolNode extends RunnableCallable {
779
800
  if (result.status === 'error') {
780
801
  contentString = `Error: ${result.errorMessage ?? 'Unknown error'}\n Please fix your mistakes.`;
781
802
  /**
782
- * Error results bypass registration/annotation but must still
783
- * carry the unresolved-refs hint so the LLM can self-correct
784
- * when its reference key caused the failure.
803
+ * Error results bypass registration but stamp the
804
+ * unresolved-refs hint into `additional_kwargs` so the lazy
805
+ * annotation transform surfaces it to the LLM at request
806
+ * time, letting the model self-correct when its reference
807
+ * key caused the failure. Persisted `content` stays clean.
785
808
  */
786
809
  const unresolved = unresolvedByCallId.get(result.toolCallId) ?? [];
787
- if (unresolved.length > 0) {
788
- contentString = this.applyOutputReference(registryRunId, contentString, contentString, undefined, unresolved);
789
- }
810
+ const errorRefMeta = unresolved.length > 0
811
+ ? this.recordOutputReference(registryRunId, contentString, undefined, unresolved)
812
+ : undefined;
790
813
  toolMessage = new ToolMessage({
791
814
  status: 'error',
792
815
  content: contentString,
793
816
  name: toolName,
794
817
  tool_call_id: result.toolCallId,
818
+ ...(errorRefMeta != null && {
819
+ additional_kwargs: errorRefMeta,
820
+ }),
795
821
  });
796
822
  if (hasFailureHook) {
797
823
  await executeHooks({
@@ -853,13 +879,16 @@ class ToolNode extends RunnableCallable {
853
879
  turn != null
854
880
  ? buildReferenceKey(batchIndex, turn)
855
881
  : undefined;
856
- contentString = this.applyOutputReference(registryRunId, contentString, registryRaw, refKey, unresolved);
882
+ const successRefMeta = this.recordOutputReference(registryRunId, registryRaw, refKey, unresolved);
857
883
  toolMessage = new ToolMessage({
858
884
  status: 'success',
859
885
  name: toolName,
860
886
  content: contentString,
861
887
  artifact: result.artifact,
862
888
  tool_call_id: result.toolCallId,
889
+ ...(successRefMeta != null && {
890
+ additional_kwargs: successRefMeta,
891
+ }),
863
892
  });
864
893
  }
865
894
  this.dispatchStepCompleted(result.toolCallId, toolName, request?.args ?? {}, contentString, config, request?.turn);