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