@librechat/agents 3.1.71-dev.1 → 3.1.72
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.
- package/dist/cjs/llm/invoke.cjs.map +1 -1
- package/dist/cjs/tools/BashExecutor.cjs +3 -1
- package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +13 -1
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/toolOutputReferences.cjs +22 -9
- package/dist/cjs/tools/toolOutputReferences.cjs.map +1 -1
- package/dist/esm/llm/invoke.mjs.map +1 -1
- package/dist/esm/tools/BashExecutor.mjs +3 -1
- package/dist/esm/tools/BashExecutor.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +13 -1
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/toolOutputReferences.mjs +22 -9
- package/dist/esm/tools/toolOutputReferences.mjs.map +1 -1
- package/dist/types/llm/invoke.d.ts +25 -8
- package/package.json +1 -1
- package/src/llm/invoke.test.ts +10 -6
- package/src/llm/invoke.ts +27 -8
- package/src/tools/BashExecutor.ts +3 -1
- package/src/tools/ToolNode.ts +13 -1
- package/src/tools/__tests__/BashExecutor.test.ts +13 -0
- package/src/tools/__tests__/ToolNode.session.test.ts +155 -6
- package/src/tools/__tests__/annotateMessagesForLLM.test.ts +60 -0
- package/src/tools/toolOutputReferences.ts +21 -9
|
@@ -12,12 +12,14 @@ function makeToolMessage(fields: {
|
|
|
12
12
|
name?: string;
|
|
13
13
|
tool_call_id?: string;
|
|
14
14
|
status?: 'success' | 'error';
|
|
15
|
+
artifact?: unknown;
|
|
15
16
|
additional_kwargs?: Record<string, unknown>;
|
|
16
17
|
}): ToolMessage {
|
|
17
18
|
return new ToolMessage({
|
|
18
19
|
name: fields.name ?? 'echo',
|
|
19
20
|
tool_call_id: fields.tool_call_id ?? 'tc1',
|
|
20
21
|
status: fields.status ?? 'success',
|
|
22
|
+
artifact: fields.artifact,
|
|
21
23
|
additional_kwargs: fields.additional_kwargs,
|
|
22
24
|
content: fields.content,
|
|
23
25
|
});
|
|
@@ -190,6 +192,31 @@ describe('annotateMessagesForLLM', () => {
|
|
|
190
192
|
expect(blocks[2].type).toBe('image_url');
|
|
191
193
|
});
|
|
192
194
|
|
|
195
|
+
it('preserves artifact on the projected ToolMessage', () => {
|
|
196
|
+
/**
|
|
197
|
+
* Hosts attach `artifact` to ToolMessages via the
|
|
198
|
+
* `content_and_artifact` response format (e.g. code execution
|
|
199
|
+
* sessions, MCP tools that return structured side-data). The
|
|
200
|
+
* projection must round-trip the artifact untouched so downstream
|
|
201
|
+
* consumers (audit logs, code-session tracking) keep working.
|
|
202
|
+
*/
|
|
203
|
+
const registry = new ToolOutputReferenceRegistry();
|
|
204
|
+
registry.set('r1', 'tool0turn0', 'raw');
|
|
205
|
+
const artifact = {
|
|
206
|
+
session_id: 'abc',
|
|
207
|
+
files: [{ id: 'f1', name: 'a.txt' }],
|
|
208
|
+
};
|
|
209
|
+
const tm = makeToolMessage({
|
|
210
|
+
content: 'output',
|
|
211
|
+
artifact,
|
|
212
|
+
additional_kwargs: { _refKey: 'tool0turn0' },
|
|
213
|
+
});
|
|
214
|
+
const out = annotateMessagesForLLM([tm], registry, 'r1');
|
|
215
|
+
const projected = out[0] as ToolMessage;
|
|
216
|
+
expect(projected.artifact).toBe(artifact);
|
|
217
|
+
expect(projected.content).toBe('[ref: tool0turn0]\noutput');
|
|
218
|
+
});
|
|
219
|
+
|
|
193
220
|
it('does not mutate the original ToolMessage instance or its content', () => {
|
|
194
221
|
const registry = new ToolOutputReferenceRegistry();
|
|
195
222
|
registry.set('r1', 'tool0turn0', 'raw');
|
|
@@ -404,6 +431,39 @@ describe('annotateMessagesForLLM', () => {
|
|
|
404
431
|
expect((out[0] as ToolMessage).additional_kwargs._refKey).toBeUndefined();
|
|
405
432
|
});
|
|
406
433
|
|
|
434
|
+
it('skips a ToolMessage whose additional_kwargs is a primitive without throwing', () => {
|
|
435
|
+
/**
|
|
436
|
+
* The `in` operator throws `TypeError` on primitives, so without
|
|
437
|
+
* a runtime object guard, a hydrated ToolMessage carrying e.g.
|
|
438
|
+
* `additional_kwargs: 'not-an-object'` (from a buggy serializer)
|
|
439
|
+
* would crash `attemptInvoke` before the provider call. Verify
|
|
440
|
+
* we skip that message and process subsequent live-ref messages
|
|
441
|
+
* normally.
|
|
442
|
+
*/
|
|
443
|
+
const registry = new ToolOutputReferenceRegistry();
|
|
444
|
+
registry.set('r1', 'tool0turn0', 'raw');
|
|
445
|
+
|
|
446
|
+
const malformed = new ToolMessage({
|
|
447
|
+
name: 'echo',
|
|
448
|
+
tool_call_id: 'mal',
|
|
449
|
+
status: 'success',
|
|
450
|
+
content: 'malformed-output',
|
|
451
|
+
});
|
|
452
|
+
/* Force a primitive past LangChain's typed setter via a cast. */
|
|
453
|
+
(malformed as unknown as { additional_kwargs: unknown }).additional_kwargs =
|
|
454
|
+
'not-an-object' as unknown;
|
|
455
|
+
|
|
456
|
+
const live = makeToolMessage({
|
|
457
|
+
content: 'live-output',
|
|
458
|
+
additional_kwargs: { _refKey: 'tool0turn0' },
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
const out = annotateMessagesForLLM([malformed, live], registry, 'r1');
|
|
462
|
+
/* Malformed message passes through unchanged; live ref still annotates. */
|
|
463
|
+
expect(out[0]).toBe(malformed);
|
|
464
|
+
expect(out[1].content).toBe('[ref: tool0turn0]\nlive-output');
|
|
465
|
+
});
|
|
466
|
+
|
|
407
467
|
it('treats stale _refKey but live unresolved as unresolved-only', () => {
|
|
408
468
|
const registry = new ToolOutputReferenceRegistry();
|
|
409
469
|
const tm = makeToolMessage({
|
|
@@ -642,16 +642,19 @@ export function annotateMessagesForLLM(
|
|
|
642
642
|
/**
|
|
643
643
|
* `additional_kwargs` is untyped at the LangChain layer
|
|
644
644
|
* (`Record<string, unknown>`), so persisted or client-supplied
|
|
645
|
-
* ToolMessages can carry arbitrary shapes
|
|
646
|
-
*
|
|
647
|
-
*
|
|
648
|
-
*
|
|
649
|
-
* provider call
|
|
645
|
+
* ToolMessages can carry arbitrary shapes — including primitives
|
|
646
|
+
* (a malformed serializer might write a string, or `null`).
|
|
647
|
+
* Guard with a runtime object check before the `in` probes
|
|
648
|
+
* because the `in` operator throws `TypeError` on primitives.
|
|
649
|
+
* A single malformed message must never crash the provider call
|
|
650
|
+
* path; skip its annotation/strip and continue.
|
|
650
651
|
*/
|
|
651
|
-
const
|
|
652
|
-
|
|
653
|
-
const
|
|
654
|
-
const
|
|
652
|
+
const rawMeta = m.additional_kwargs as unknown;
|
|
653
|
+
if (rawMeta == null || typeof rawMeta !== 'object') continue;
|
|
654
|
+
const meta = rawMeta as Record<string, unknown>;
|
|
655
|
+
const hasRefKey = '_refKey' in meta;
|
|
656
|
+
const hasRefScope = '_refScope' in meta;
|
|
657
|
+
const hasUnresolvedField = '_unresolvedRefs' in meta;
|
|
655
658
|
if (!hasRefKey && !hasRefScope && !hasUnresolvedField) continue;
|
|
656
659
|
|
|
657
660
|
const refKey = readRefKey(meta);
|
|
@@ -688,6 +691,15 @@ export function annotateMessagesForLLM(
|
|
|
688
691
|
type: 'text' as const,
|
|
689
692
|
text: `[unresolved refs: ${unresolved.join(', ')}]`,
|
|
690
693
|
};
|
|
694
|
+
/**
|
|
695
|
+
* `as unknown as ToolMessage['content']` is unavoidable here:
|
|
696
|
+
* LangChain's content union (`MessageContentComplex[] |
|
|
697
|
+
* DataContentBlock[] | string`) does not accept a freshly built
|
|
698
|
+
* mixed array literal even though the structural shape is valid
|
|
699
|
+
* at runtime. The double-cast is structurally safe — we
|
|
700
|
+
* preserve every block from `tm.content` and prepend a single
|
|
701
|
+
* `{ type: 'text', text }` block that all providers accept.
|
|
702
|
+
*/
|
|
691
703
|
nextContent = [
|
|
692
704
|
warningBlock,
|
|
693
705
|
...tm.content,
|