@librechat/agents 3.3.2 → 3.3.4
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/agents/AgentContext.cjs +2 -1
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +342 -109
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/graphs/MultiAgentGraph.cjs +3 -1
- package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
- package/dist/cjs/langfuse.cjs +6 -0
- package/dist/cjs/langfuse.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +79 -6
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/utils/message_inputs.cjs +37 -10
- package/dist/cjs/llm/bedrock/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/llm/google/utils/common.cjs +19 -7
- package/dist/cjs/llm/google/utils/common.cjs.map +1 -1
- package/dist/cjs/llm/invoke.cjs +64 -3
- package/dist/cjs/llm/invoke.cjs.map +1 -1
- package/dist/cjs/llm/openai/index.cjs +41 -5
- package/dist/cjs/llm/openai/index.cjs.map +1 -1
- package/dist/cjs/llm/openai/utils/index.cjs +25 -4
- package/dist/cjs/llm/openai/utils/index.cjs.map +1 -1
- package/dist/cjs/llm/openrouter/index.cjs +4 -1
- package/dist/cjs/llm/openrouter/index.cjs.map +1 -1
- package/dist/cjs/main.cjs +19 -2
- package/dist/cjs/messages/cache.cjs +54 -33
- package/dist/cjs/messages/cache.cjs.map +1 -1
- package/dist/cjs/messages/contextPruning.cjs +17 -43
- package/dist/cjs/messages/contextPruning.cjs.map +1 -1
- package/dist/cjs/messages/core.cjs +315 -23
- package/dist/cjs/messages/core.cjs.map +1 -1
- package/dist/cjs/messages/format.cjs +218 -60
- package/dist/cjs/messages/format.cjs.map +1 -1
- package/dist/cjs/messages/index.cjs +1 -1
- package/dist/cjs/messages/prune.cjs +503 -127
- package/dist/cjs/messages/prune.cjs.map +1 -1
- package/dist/cjs/stream.cjs +7 -3
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/summarization/node.cjs +28 -14
- package/dist/cjs/summarization/node.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +54 -41
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/toolOutputReferences.cjs +6 -0
- package/dist/cjs/tools/toolOutputReferences.cjs.map +1 -1
- package/dist/cjs/utils/index.cjs +1 -1
- package/dist/cjs/utils/llm.cjs +1 -1
- package/dist/cjs/utils/llm.cjs.map +1 -1
- package/dist/cjs/utils/tokens.cjs +307 -20
- package/dist/cjs/utils/tokens.cjs.map +1 -1
- package/dist/cjs/utils/toolContent.cjs +1514 -0
- package/dist/cjs/utils/toolContent.cjs.map +1 -0
- package/dist/esm/agents/AgentContext.mjs +2 -1
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +349 -116
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/graphs/MultiAgentGraph.mjs +3 -1
- package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
- package/dist/esm/langfuse.mjs +6 -0
- package/dist/esm/langfuse.mjs.map +1 -1
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs +79 -6
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/llm/bedrock/utils/message_inputs.mjs +37 -10
- package/dist/esm/llm/bedrock/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/llm/google/utils/common.mjs +19 -7
- package/dist/esm/llm/google/utils/common.mjs.map +1 -1
- package/dist/esm/llm/invoke.mjs +65 -5
- package/dist/esm/llm/invoke.mjs.map +1 -1
- package/dist/esm/llm/openai/index.mjs +41 -5
- package/dist/esm/llm/openai/index.mjs.map +1 -1
- package/dist/esm/llm/openai/utils/index.mjs +25 -4
- package/dist/esm/llm/openai/utils/index.mjs.map +1 -1
- package/dist/esm/llm/openrouter/index.mjs +4 -1
- package/dist/esm/llm/openrouter/index.mjs.map +1 -1
- package/dist/esm/main.mjs +7 -7
- package/dist/esm/messages/cache.mjs +55 -34
- package/dist/esm/messages/cache.mjs.map +1 -1
- package/dist/esm/messages/contextPruning.mjs +17 -43
- package/dist/esm/messages/contextPruning.mjs.map +1 -1
- package/dist/esm/messages/core.mjs +305 -24
- package/dist/esm/messages/core.mjs.map +1 -1
- package/dist/esm/messages/format.mjs +218 -61
- package/dist/esm/messages/format.mjs.map +1 -1
- package/dist/esm/messages/index.mjs +1 -1
- package/dist/esm/messages/prune.mjs +500 -128
- package/dist/esm/messages/prune.mjs.map +1 -1
- package/dist/esm/stream.mjs +7 -3
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/summarization/node.mjs +28 -14
- package/dist/esm/summarization/node.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +54 -41
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/toolOutputReferences.mjs +6 -0
- package/dist/esm/tools/toolOutputReferences.mjs.map +1 -1
- package/dist/esm/utils/index.mjs +1 -1
- package/dist/esm/utils/llm.mjs +1 -1
- package/dist/esm/utils/llm.mjs.map +1 -1
- package/dist/esm/utils/tokens.mjs +307 -21
- package/dist/esm/utils/tokens.mjs.map +1 -1
- package/dist/esm/utils/toolContent.mjs +1503 -0
- package/dist/esm/utils/toolContent.mjs.map +1 -0
- package/dist/types/graphs/Graph.d.ts +1 -1
- package/dist/types/llm/invoke.d.ts +27 -1
- package/dist/types/llm/openai/index.d.ts +4 -0
- package/dist/types/llm/openai/utils/index.d.ts +2 -0
- package/dist/types/llm/openrouter/index.d.ts +5 -0
- package/dist/types/messages/contextPruning.d.ts +2 -1
- package/dist/types/messages/core.d.ts +36 -0
- package/dist/types/messages/format.d.ts +6 -0
- package/dist/types/messages/prune.d.ts +16 -12
- package/dist/types/types/stream.d.ts +1 -1
- package/dist/types/utils/tokens.d.ts +7 -0
- package/dist/types/utils/toolContent.d.ts +107 -0
- package/package.json +13 -9
- package/src/__tests__/stream.eagerEventExecution.test.ts +74 -0
- package/src/agents/AgentContext.ts +1 -0
- package/src/graphs/Graph.ts +698 -200
- package/src/graphs/MultiAgentGraph.ts +6 -1
- package/src/graphs/__tests__/Graph.contextOverflow.test.ts +1128 -4
- package/src/langfuse.ts +15 -0
- package/src/llm/anthropic/utils/message_inputs.ts +158 -12
- package/src/llm/anthropic/utils/tool-id-normalization.test.ts +109 -0
- package/src/llm/bedrock/utils/message_inputs.test.ts +49 -1
- package/src/llm/bedrock/utils/message_inputs.ts +86 -13
- package/src/llm/google/utils/common.test.ts +75 -9
- package/src/llm/google/utils/common.ts +34 -3
- package/src/llm/invoke.test.ts +488 -0
- package/src/llm/invoke.ts +196 -2
- package/src/llm/openai/index.ts +106 -6
- package/src/llm/openai/utils/index.ts +109 -53
- package/src/llm/openai/utils/messages.test.ts +330 -1
- package/src/llm/openrouter/index.ts +12 -2
- package/src/llm/openrouter/reasoning.test.ts +314 -0
- package/src/messages/__tests__/observationMasking.test.ts +93 -2
- package/src/messages/cache.tail.test.ts +193 -0
- package/src/messages/cache.test.ts +113 -0
- package/src/messages/cache.ts +92 -60
- package/src/messages/contextPruning.test.ts +184 -0
- package/src/messages/contextPruning.ts +49 -42
- package/src/messages/core.ts +653 -40
- package/src/messages/ensureThinkingBlock.test.ts +49 -1
- package/src/messages/foldToollessToolBlocks.test.ts +163 -5
- package/src/messages/format.ts +363 -91
- package/src/messages/formatAgentMessages.test.ts +604 -14
- package/src/messages/formatAgentMessages.tools.test.ts +103 -0
- package/src/messages/prune.ts +996 -183
- package/src/specs/langfuse-callbacks.test.ts +203 -1
- package/src/specs/prune.test.ts +1083 -6
- package/src/specs/summarization-unit.test.ts +105 -0
- package/src/specs/tokens.test.ts +609 -32
- package/src/stream.ts +23 -15
- package/src/summarization/__tests__/node.test.ts +77 -0
- package/src/summarization/node.ts +46 -18
- package/src/tools/ToolNode.ts +147 -68
- package/src/tools/__tests__/ToolNode.onResultCompletion.test.ts +148 -1
- package/src/tools/__tests__/ToolNode.outputReferences.test.ts +112 -3
- package/src/tools/__tests__/annotateMessagesForLLM.test.ts +32 -0
- package/src/tools/__tests__/directToolHooks.test.ts +255 -7
- package/src/tools/toolOutputReferences.ts +6 -0
- package/src/types/stream.ts +1 -1
- package/src/utils/llm.test.ts +18 -0
- package/src/utils/llm.ts +4 -1
- package/src/utils/tokens.ts +586 -31
- package/src/utils/toolContent.test.ts +1432 -0
- package/src/utils/toolContent.ts +2707 -0
package/src/messages/core.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// src/messages.ts
|
|
2
|
+
import { isProxy } from 'node:util/types';
|
|
2
3
|
import {
|
|
3
4
|
AIMessage,
|
|
4
5
|
BaseMessage,
|
|
@@ -6,8 +7,20 @@ import {
|
|
|
6
7
|
HumanMessage,
|
|
7
8
|
AIMessageChunk,
|
|
8
9
|
} from '@langchain/core/messages';
|
|
9
|
-
import type { ToolCall } from '@langchain/core/messages/tool';
|
|
10
|
+
import type { ToolCall, ToolCallChunk } from '@langchain/core/messages/tool';
|
|
10
11
|
import type * as t from '@/types';
|
|
12
|
+
import {
|
|
13
|
+
cloneToolMessageWithContent,
|
|
14
|
+
compactToolContent,
|
|
15
|
+
getBoundedCacheControlledTextToolContent,
|
|
16
|
+
getBoundedSingleTextToolContent,
|
|
17
|
+
getComputerCallOutputScreenshot,
|
|
18
|
+
hasComputerCallOutputMarker,
|
|
19
|
+
isComputerCallOutputMessage,
|
|
20
|
+
serializeToolContentBounded,
|
|
21
|
+
} from '@/utils/toolContent';
|
|
22
|
+
import { HARD_MAX_TOOL_RESULT_CHARS } from '@/utils/truncation';
|
|
23
|
+
import { stripAnthropicCacheControl } from './cache';
|
|
11
24
|
import { ContentTypes, Providers } from '@/common';
|
|
12
25
|
import { toLangChainContent } from './langchain';
|
|
13
26
|
|
|
@@ -181,15 +194,14 @@ function getReasoningDetailsText(
|
|
|
181
194
|
function getAdditionalReasoningContent(
|
|
182
195
|
message: BaseMessage
|
|
183
196
|
): string | undefined {
|
|
184
|
-
const additionalKwargs =
|
|
185
|
-
|
|
197
|
+
const additionalKwargs = message.additional_kwargs as
|
|
198
|
+
| ReasoningAdditionalKwargs
|
|
199
|
+
| undefined;
|
|
186
200
|
if (additionalKwargs == null) {
|
|
187
201
|
return undefined;
|
|
188
202
|
}
|
|
189
203
|
|
|
190
|
-
const reasoningContent = getReasoningText(
|
|
191
|
-
additionalKwargs.reasoning_content
|
|
192
|
-
);
|
|
204
|
+
const reasoningContent = getReasoningText(additionalKwargs.reasoning_content);
|
|
193
205
|
if (reasoningContent != null) {
|
|
194
206
|
return reasoningContent;
|
|
195
207
|
}
|
|
@@ -432,8 +444,7 @@ export function convertMessagesToContent(
|
|
|
432
444
|
toolCallMap.set(tool_call.id, tool_call);
|
|
433
445
|
}
|
|
434
446
|
|
|
435
|
-
currentAIMessageIndex =
|
|
436
|
-
addContentPart(message) ?? addToolCallBoundary();
|
|
447
|
+
currentAIMessageIndex = addContentPart(message) ?? addToolCallBoundary();
|
|
437
448
|
continue;
|
|
438
449
|
} else if (
|
|
439
450
|
messageType === 'tool' &&
|
|
@@ -471,24 +482,545 @@ function stringifyToolMessageContent(
|
|
|
471
482
|
return content == null ? '' : String(content);
|
|
472
483
|
}
|
|
473
484
|
|
|
474
|
-
|
|
485
|
+
function appendContentBlocks(
|
|
486
|
+
target: t.MessageContentComplex[],
|
|
487
|
+
content: BaseMessage['content']
|
|
488
|
+
): void {
|
|
489
|
+
if (typeof content === 'string') {
|
|
490
|
+
target.push({ type: ContentTypes.TEXT, text: content });
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
for (const block of content) {
|
|
494
|
+
target.push(block as t.MessageContentComplex);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
/**
|
|
499
|
+
* Appends one artifact/tool-content segment without retaining an unbounded
|
|
500
|
+
* intermediate block array. Both operands are compacted before they are
|
|
501
|
+
* combined, and the combined result is compacted again under the aggregate
|
|
502
|
+
* cap.
|
|
503
|
+
*/
|
|
504
|
+
function appendBoundedContent(
|
|
505
|
+
current: BaseMessage['content'] | undefined,
|
|
506
|
+
next: unknown,
|
|
507
|
+
maxChars: number
|
|
508
|
+
): BaseMessage['content'] {
|
|
509
|
+
const boundedNext = compactToolContent(next, maxChars).content;
|
|
510
|
+
if (current == null) {
|
|
511
|
+
return boundedNext;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
const combined: t.MessageContentComplex[] = [];
|
|
515
|
+
appendContentBlocks(combined, current);
|
|
516
|
+
appendContentBlocks(combined, boundedNext);
|
|
517
|
+
return compactToolContent(toLangChainContent(combined), maxChars).content;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function cloneAIMessageWithToolCalls(
|
|
521
|
+
message: AIMessage,
|
|
522
|
+
toolCalls: ToolCall[],
|
|
523
|
+
removedCallIds: ReadonlySet<string>
|
|
524
|
+
): AIMessage {
|
|
525
|
+
const descriptors = Object.getOwnPropertyDescriptors(message) as Record<
|
|
526
|
+
string,
|
|
527
|
+
PropertyDescriptor | undefined
|
|
528
|
+
>;
|
|
529
|
+
let descriptor = descriptors.tool_calls;
|
|
530
|
+
descriptors.tool_calls = {
|
|
531
|
+
configurable: descriptor?.configurable ?? true,
|
|
532
|
+
enumerable: descriptor?.enumerable ?? true,
|
|
533
|
+
value: toolCalls,
|
|
534
|
+
writable: descriptor?.writable ?? true,
|
|
535
|
+
};
|
|
536
|
+
const toolCallChunks = descriptors.tool_call_chunks?.value as
|
|
537
|
+
| ToolCallChunk[]
|
|
538
|
+
| undefined;
|
|
539
|
+
if (Array.isArray(toolCallChunks)) {
|
|
540
|
+
descriptor = descriptors.tool_call_chunks;
|
|
541
|
+
descriptors.tool_call_chunks = {
|
|
542
|
+
configurable: descriptor?.configurable ?? true,
|
|
543
|
+
enumerable: descriptor?.enumerable ?? true,
|
|
544
|
+
value: toolCallChunks.filter(
|
|
545
|
+
(chunk) => typeof chunk.id !== 'string' || !removedCallIds.has(chunk.id)
|
|
546
|
+
),
|
|
547
|
+
writable: descriptor?.writable ?? true,
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
return Object.create(
|
|
551
|
+
Object.getPrototypeOf(message),
|
|
552
|
+
descriptors as PropertyDescriptorMap
|
|
553
|
+
) as AIMessage;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
function cloneAIMessageWithContent(
|
|
557
|
+
message: AIMessage,
|
|
558
|
+
content: AIMessage['content']
|
|
559
|
+
): AIMessage {
|
|
560
|
+
const descriptors = Object.getOwnPropertyDescriptors(message) as Record<
|
|
561
|
+
string,
|
|
562
|
+
PropertyDescriptor | undefined
|
|
563
|
+
>;
|
|
564
|
+
const descriptor = descriptors.content;
|
|
565
|
+
descriptors.content = {
|
|
566
|
+
configurable: descriptor?.configurable ?? true,
|
|
567
|
+
enumerable: descriptor?.enumerable ?? true,
|
|
568
|
+
value: content,
|
|
569
|
+
writable: descriptor?.writable ?? true,
|
|
570
|
+
};
|
|
571
|
+
const lcKwargs = descriptors.lc_kwargs;
|
|
572
|
+
if (
|
|
573
|
+
lcKwargs != null &&
|
|
574
|
+
'value' in lcKwargs &&
|
|
575
|
+
typeof lcKwargs.value === 'object' &&
|
|
576
|
+
lcKwargs.value != null
|
|
577
|
+
) {
|
|
578
|
+
descriptors.lc_kwargs = {
|
|
579
|
+
...lcKwargs,
|
|
580
|
+
value: {
|
|
581
|
+
...(lcKwargs.value as Record<string, unknown>),
|
|
582
|
+
content,
|
|
583
|
+
},
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
return Object.create(
|
|
587
|
+
Object.getPrototypeOf(message),
|
|
588
|
+
descriptors as PropertyDescriptorMap
|
|
589
|
+
) as AIMessage;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/**
|
|
593
|
+
* Drops incomplete streamed text-input fragments that some providers retain
|
|
594
|
+
* beside the assembled parsed tool call. They are neither user-visible text
|
|
595
|
+
* nor valid content blocks for a subsequent provider.
|
|
596
|
+
*/
|
|
597
|
+
export function projectToolStreamContentForProvider(
|
|
598
|
+
messages: BaseMessage[]
|
|
599
|
+
): BaseMessage[] {
|
|
600
|
+
let projected: BaseMessage[] | undefined;
|
|
601
|
+
for (let i = 0; i < messages.length; i++) {
|
|
602
|
+
const message = messages[i];
|
|
603
|
+
if (message.getType() !== 'ai' || !Array.isArray(message.content)) {
|
|
604
|
+
continue;
|
|
605
|
+
}
|
|
606
|
+
const content = message.content.filter((block) => {
|
|
607
|
+
if (block == null || typeof block !== 'object') {
|
|
608
|
+
return true;
|
|
609
|
+
}
|
|
610
|
+
try {
|
|
611
|
+
if (isProxy(block)) {
|
|
612
|
+
return false;
|
|
613
|
+
}
|
|
614
|
+
const type = Object.getOwnPropertyDescriptor(block, 'type');
|
|
615
|
+
if (type == null) {
|
|
616
|
+
return true;
|
|
617
|
+
}
|
|
618
|
+
if (type.enumerable !== true || !('value' in type)) {
|
|
619
|
+
return false;
|
|
620
|
+
}
|
|
621
|
+
if (type.value !== 'text') {
|
|
622
|
+
return true;
|
|
623
|
+
}
|
|
624
|
+
const text = Object.getOwnPropertyDescriptor(block, 'text');
|
|
625
|
+
return (
|
|
626
|
+
text?.enumerable === true &&
|
|
627
|
+
'value' in text &&
|
|
628
|
+
typeof text.value === 'string' &&
|
|
629
|
+
text.value !== ''
|
|
630
|
+
);
|
|
631
|
+
} catch {
|
|
632
|
+
return false;
|
|
633
|
+
}
|
|
634
|
+
});
|
|
635
|
+
if (content.length === message.content.length) {
|
|
636
|
+
continue;
|
|
637
|
+
}
|
|
638
|
+
projected ??= [...messages];
|
|
639
|
+
projected[i] = cloneAIMessageWithContent(
|
|
640
|
+
message as AIMessage,
|
|
641
|
+
toLangChainContent(content)
|
|
642
|
+
);
|
|
643
|
+
}
|
|
644
|
+
return projected ?? messages;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
type CacheControlledTextProjection = 'serialize' | 'preserve' | 'text';
|
|
648
|
+
|
|
649
|
+
function projectStructuredOpenAIToolContent(
|
|
650
|
+
content: ToolMessage['content'],
|
|
651
|
+
maxChars: number,
|
|
652
|
+
cacheControlledTextProjection: CacheControlledTextProjection
|
|
653
|
+
): ToolMessage['content'] {
|
|
654
|
+
if (cacheControlledTextProjection !== 'serialize') {
|
|
655
|
+
const cacheControlledContent = getBoundedCacheControlledTextToolContent(
|
|
656
|
+
content,
|
|
657
|
+
maxChars
|
|
658
|
+
);
|
|
659
|
+
if (cacheControlledContent != null) {
|
|
660
|
+
return cacheControlledTextProjection === 'preserve'
|
|
661
|
+
? cacheControlledContent
|
|
662
|
+
: cacheControlledContent[0].text;
|
|
663
|
+
}
|
|
664
|
+
const singleTextContent = getBoundedSingleTextToolContent(
|
|
665
|
+
content,
|
|
666
|
+
maxChars
|
|
667
|
+
);
|
|
668
|
+
if (singleTextContent != null) {
|
|
669
|
+
return singleTextContent;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
const serializableContent =
|
|
673
|
+
cacheControlledTextProjection === 'preserve'
|
|
674
|
+
? stripAnthropicCacheControl([{ content }])[0].content
|
|
675
|
+
: content;
|
|
676
|
+
return serializeToolContentBounded(serializableContent, maxChars);
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
/**
|
|
680
|
+
* OpenAI Chat tool messages only accept strings or text-only parts, while the
|
|
681
|
+
* Responses API serializes any structured ToolMessage after graph accounting.
|
|
682
|
+
* Project every non-string tool result to one bounded string before the final
|
|
683
|
+
* provider payload is measured so both APIs receive the exact representation
|
|
684
|
+
* the budget guard counted. Native Responses computer screenshots stay
|
|
685
|
+
* structured because their dedicated converter sends the media block directly.
|
|
686
|
+
*/
|
|
687
|
+
function projectOpenAIToolMessageContentInternal(
|
|
688
|
+
messages: BaseMessage[],
|
|
689
|
+
maxChars: number,
|
|
690
|
+
deduplicateResponsesComputerCalls: boolean,
|
|
691
|
+
cacheControlledTextProjection: CacheControlledTextProjection
|
|
692
|
+
): BaseMessage[] {
|
|
693
|
+
const pendingComputerCallIds: string[] = [];
|
|
694
|
+
const seenComputerCallIds = new Set<string>();
|
|
695
|
+
let projected: BaseMessage[] | undefined;
|
|
696
|
+
for (let i = 0; i < messages.length; i++) {
|
|
697
|
+
const message = messages[i];
|
|
698
|
+
const messageRole = (message as BaseMessage & { role?: unknown }).role;
|
|
699
|
+
const isAssistant =
|
|
700
|
+
message.getType() === 'ai' || messageRole === 'assistant';
|
|
701
|
+
if (isAssistant) {
|
|
702
|
+
const parsedComputerCallIds = new Set<string>();
|
|
703
|
+
const toolCalls = (message as AIMessage).tool_calls;
|
|
704
|
+
if (Array.isArray(toolCalls)) {
|
|
705
|
+
for (const toolCall of toolCalls) {
|
|
706
|
+
const record = toolCall as ToolCall & {
|
|
707
|
+
isComputerTool?: unknown;
|
|
708
|
+
};
|
|
709
|
+
if (
|
|
710
|
+
record.type !== 'tool_call' ||
|
|
711
|
+
record.isComputerTool !== true ||
|
|
712
|
+
typeof record.id !== 'string' ||
|
|
713
|
+
record.id === ''
|
|
714
|
+
) {
|
|
715
|
+
continue;
|
|
716
|
+
}
|
|
717
|
+
if (parsedComputerCallIds.has(record.id)) {
|
|
718
|
+
throw new Error(`Duplicate computer call id "${record.id}"`);
|
|
719
|
+
}
|
|
720
|
+
parsedComputerCallIds.add(record.id);
|
|
721
|
+
if (seenComputerCallIds.has(record.id)) {
|
|
722
|
+
throw new Error(`Duplicate computer call id "${record.id}"`);
|
|
723
|
+
}
|
|
724
|
+
seenComputerCallIds.add(record.id);
|
|
725
|
+
pendingComputerCallIds.push(record.id);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
const rawOutput = (
|
|
730
|
+
message.response_metadata as {
|
|
731
|
+
output?: unknown;
|
|
732
|
+
}
|
|
733
|
+
).output;
|
|
734
|
+
const fallbackOutput = (
|
|
735
|
+
message.additional_kwargs as {
|
|
736
|
+
tool_outputs?: unknown;
|
|
737
|
+
}
|
|
738
|
+
).tool_outputs;
|
|
739
|
+
let actualToolOutputs: unknown[] = [];
|
|
740
|
+
if (Array.isArray(rawOutput) && rawOutput.length > 0) {
|
|
741
|
+
actualToolOutputs = rawOutput;
|
|
742
|
+
} else if (Array.isArray(fallbackOutput)) {
|
|
743
|
+
actualToolOutputs = fallbackOutput;
|
|
744
|
+
}
|
|
745
|
+
const rawComputerCallIds = new Set<string>();
|
|
746
|
+
for (const item of actualToolOutputs) {
|
|
747
|
+
if (item == null || typeof item !== 'object') {
|
|
748
|
+
continue;
|
|
749
|
+
}
|
|
750
|
+
const record = item as {
|
|
751
|
+
type?: unknown;
|
|
752
|
+
call_id?: unknown;
|
|
753
|
+
};
|
|
754
|
+
if (
|
|
755
|
+
record.type !== 'computer_call' ||
|
|
756
|
+
typeof record.call_id !== 'string' ||
|
|
757
|
+
record.call_id === ''
|
|
758
|
+
) {
|
|
759
|
+
continue;
|
|
760
|
+
}
|
|
761
|
+
if (rawComputerCallIds.has(record.call_id)) {
|
|
762
|
+
throw new Error(`Duplicate computer call id "${record.call_id}"`);
|
|
763
|
+
}
|
|
764
|
+
rawComputerCallIds.add(record.call_id);
|
|
765
|
+
// LangChain can retain the same call in parsed and raw forms. It sends
|
|
766
|
+
// one logical call, so collapse that representation duplicate.
|
|
767
|
+
if (parsedComputerCallIds.has(record.call_id)) {
|
|
768
|
+
continue;
|
|
769
|
+
}
|
|
770
|
+
if (seenComputerCallIds.has(record.call_id)) {
|
|
771
|
+
throw new Error(`Duplicate computer call id "${record.call_id}"`);
|
|
772
|
+
}
|
|
773
|
+
seenComputerCallIds.add(record.call_id);
|
|
774
|
+
pendingComputerCallIds.push(record.call_id);
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
if (
|
|
778
|
+
deduplicateResponsesComputerCalls &&
|
|
779
|
+
Array.isArray(toolCalls) &&
|
|
780
|
+
rawComputerCallIds.size > 0
|
|
781
|
+
) {
|
|
782
|
+
/**
|
|
783
|
+
* The non-streaming Responses converter marks parsed computer calls,
|
|
784
|
+
* but the streaming converter currently emits the same call as an
|
|
785
|
+
* ordinary parsed `computer_use` tool call. In both cases the raw
|
|
786
|
+
* `computer_call` item is authoritative and is replayed by LangChain,
|
|
787
|
+
* so remove every parsed representation with the same call id.
|
|
788
|
+
*/
|
|
789
|
+
const projectedToolCalls = toolCalls.filter(
|
|
790
|
+
(toolCall) =>
|
|
791
|
+
typeof toolCall.id !== 'string' ||
|
|
792
|
+
!rawComputerCallIds.has(toolCall.id)
|
|
793
|
+
);
|
|
794
|
+
if (projectedToolCalls.length !== toolCalls.length) {
|
|
795
|
+
projected ??= [...messages];
|
|
796
|
+
projected[i] = cloneAIMessageWithToolCalls(
|
|
797
|
+
message as AIMessage,
|
|
798
|
+
projectedToolCalls,
|
|
799
|
+
rawComputerCallIds
|
|
800
|
+
);
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
if (
|
|
806
|
+
message instanceof ToolMessage &&
|
|
807
|
+
hasComputerCallOutputMarker(message)
|
|
808
|
+
) {
|
|
809
|
+
const screenshot = getComputerCallOutputScreenshot(message.content);
|
|
810
|
+
if (screenshot == null) {
|
|
811
|
+
throw new Error('Invalid computer call output screenshot');
|
|
812
|
+
}
|
|
813
|
+
if (pendingComputerCallIds[0] !== message.tool_call_id) {
|
|
814
|
+
throw new Error(
|
|
815
|
+
`Invalid computer call output pairing for "${message.tool_call_id}"`
|
|
816
|
+
);
|
|
817
|
+
}
|
|
818
|
+
pendingComputerCallIds.shift();
|
|
819
|
+
projected ??= [...messages];
|
|
820
|
+
projected[i] = cloneToolMessageWithContent(message, [screenshot]);
|
|
821
|
+
continue;
|
|
822
|
+
}
|
|
823
|
+
if (
|
|
824
|
+
!(message instanceof ToolMessage) ||
|
|
825
|
+
typeof message.content === 'string'
|
|
826
|
+
) {
|
|
827
|
+
continue;
|
|
828
|
+
}
|
|
829
|
+
projected ??= [...messages];
|
|
830
|
+
projected[i] = cloneToolMessageWithContent(
|
|
831
|
+
message,
|
|
832
|
+
projectStructuredOpenAIToolContent(
|
|
833
|
+
message.content,
|
|
834
|
+
maxChars,
|
|
835
|
+
cacheControlledTextProjection
|
|
836
|
+
)
|
|
837
|
+
);
|
|
838
|
+
}
|
|
839
|
+
if (pendingComputerCallIds.length > 0) {
|
|
840
|
+
throw new Error(
|
|
841
|
+
`Missing computer call output for "${pendingComputerCallIds[0]}"`
|
|
842
|
+
);
|
|
843
|
+
}
|
|
844
|
+
return projected ?? messages;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
/** Projects OpenAI-compatible tool content without changing parsed call parents. */
|
|
848
|
+
export function projectOpenAIToolMessageContent(
|
|
849
|
+
messages: BaseMessage[],
|
|
850
|
+
maxChars = HARD_MAX_TOOL_RESULT_CHARS
|
|
851
|
+
): BaseMessage[] {
|
|
852
|
+
return projectOpenAIToolMessageContentInternal(
|
|
853
|
+
messages,
|
|
854
|
+
maxChars,
|
|
855
|
+
false,
|
|
856
|
+
'serialize'
|
|
857
|
+
);
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
/** Projects an actual OpenAI-compatible Chat attempt and removes cache metadata. */
|
|
861
|
+
export function projectOpenAIChatToolMessageContent(
|
|
862
|
+
messages: BaseMessage[],
|
|
863
|
+
maxChars = HARD_MAX_TOOL_RESULT_CHARS
|
|
864
|
+
): BaseMessage[] {
|
|
865
|
+
return projectOpenAIToolMessageContentInternal(
|
|
866
|
+
messages,
|
|
867
|
+
maxChars,
|
|
868
|
+
false,
|
|
869
|
+
'text'
|
|
870
|
+
);
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
/** Preserves OpenRouter's cache-decorated text blocks for a Chat attempt. */
|
|
874
|
+
export function projectOpenRouterToolMessageContent(
|
|
875
|
+
messages: BaseMessage[],
|
|
876
|
+
maxChars = HARD_MAX_TOOL_RESULT_CHARS
|
|
877
|
+
): BaseMessage[] {
|
|
878
|
+
return projectOpenAIToolMessageContentInternal(
|
|
879
|
+
messages,
|
|
880
|
+
maxChars,
|
|
881
|
+
false,
|
|
882
|
+
'preserve'
|
|
883
|
+
);
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
/** Projects Responses tool content and collapses parsed/raw computer-call mirrors. */
|
|
887
|
+
export function projectOpenAIResponsesToolMessageContent(
|
|
888
|
+
messages: BaseMessage[],
|
|
889
|
+
maxChars = HARD_MAX_TOOL_RESULT_CHARS
|
|
890
|
+
): BaseMessage[] {
|
|
891
|
+
return projectOpenAIToolMessageContentInternal(
|
|
892
|
+
messages,
|
|
893
|
+
maxChars,
|
|
894
|
+
true,
|
|
895
|
+
'text'
|
|
896
|
+
);
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
/** Removes Anthropic/OpenRouter cache metadata before unsupported providers run. */
|
|
900
|
+
export function projectCacheControlledToolOutputsToText(
|
|
901
|
+
messages: BaseMessage[],
|
|
902
|
+
maxChars = HARD_MAX_TOOL_RESULT_CHARS
|
|
903
|
+
): BaseMessage[] {
|
|
904
|
+
let projected: BaseMessage[] | undefined;
|
|
905
|
+
for (let i = 0; i < messages.length; i++) {
|
|
906
|
+
const message = messages[i];
|
|
907
|
+
if (
|
|
908
|
+
!(message instanceof ToolMessage) ||
|
|
909
|
+
typeof message.content === 'string'
|
|
910
|
+
) {
|
|
911
|
+
continue;
|
|
912
|
+
}
|
|
913
|
+
const cacheControlledContent = getBoundedCacheControlledTextToolContent(
|
|
914
|
+
message.content,
|
|
915
|
+
maxChars
|
|
916
|
+
);
|
|
917
|
+
if (cacheControlledContent == null) {
|
|
918
|
+
continue;
|
|
919
|
+
}
|
|
920
|
+
projected ??= [...messages];
|
|
921
|
+
projected[i] = cloneToolMessageWithContent(
|
|
922
|
+
message,
|
|
923
|
+
cacheControlledContent[0].text
|
|
924
|
+
);
|
|
925
|
+
}
|
|
926
|
+
return projected ?? messages;
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
/** Unwraps a canonical single text block after provider cache markers are removed. */
|
|
930
|
+
export function projectSingleTextToolOutputsToText(
|
|
931
|
+
messages: BaseMessage[],
|
|
932
|
+
maxChars = HARD_MAX_TOOL_RESULT_CHARS
|
|
933
|
+
): BaseMessage[] {
|
|
934
|
+
let projected: BaseMessage[] | undefined;
|
|
935
|
+
for (let i = 0; i < messages.length; i++) {
|
|
936
|
+
const message = messages[i];
|
|
937
|
+
if (
|
|
938
|
+
!(message instanceof ToolMessage) ||
|
|
939
|
+
typeof message.content === 'string'
|
|
940
|
+
) {
|
|
941
|
+
continue;
|
|
942
|
+
}
|
|
943
|
+
const text = getBoundedSingleTextToolContent(message.content, maxChars);
|
|
944
|
+
if (text == null) {
|
|
945
|
+
continue;
|
|
946
|
+
}
|
|
947
|
+
projected ??= [...messages];
|
|
948
|
+
projected[i] = cloneToolMessageWithContent(message, text);
|
|
949
|
+
}
|
|
950
|
+
return projected ?? messages;
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
/** Serializes provider-neutral structured tool outputs without media pairing. */
|
|
954
|
+
export function projectStructuredToolOutputsToText(
|
|
955
|
+
messages: BaseMessage[],
|
|
956
|
+
maxChars = HARD_MAX_TOOL_RESULT_CHARS
|
|
957
|
+
): BaseMessage[] {
|
|
958
|
+
let projected: BaseMessage[] | undefined;
|
|
959
|
+
for (let i = 0; i < messages.length; i++) {
|
|
960
|
+
const message = messages[i];
|
|
961
|
+
if (
|
|
962
|
+
!(message instanceof ToolMessage) ||
|
|
963
|
+
typeof message.content === 'string' ||
|
|
964
|
+
hasComputerCallOutputMarker(message)
|
|
965
|
+
) {
|
|
966
|
+
continue;
|
|
967
|
+
}
|
|
968
|
+
projected ??= [...messages];
|
|
969
|
+
projected[i] = cloneToolMessageWithContent(
|
|
970
|
+
message,
|
|
971
|
+
serializeToolContentBounded(message.content, maxChars)
|
|
972
|
+
);
|
|
973
|
+
}
|
|
974
|
+
return projected ?? messages;
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
/**
|
|
978
|
+
* Non-Responses providers cannot consume native computer screenshots. Keep
|
|
979
|
+
* the tool-call structure intact, but replace screenshot bytes with a bounded
|
|
980
|
+
* text marker at the actual invocation boundary.
|
|
981
|
+
*/
|
|
982
|
+
export function projectComputerCallOutputsToText(
|
|
983
|
+
messages: BaseMessage[]
|
|
984
|
+
): BaseMessage[] {
|
|
985
|
+
let projected: BaseMessage[] | undefined;
|
|
986
|
+
for (let i = 0; i < messages.length; i++) {
|
|
987
|
+
const message = messages[i];
|
|
988
|
+
if (
|
|
989
|
+
!(message instanceof ToolMessage) ||
|
|
990
|
+
!hasComputerCallOutputMarker(message)
|
|
991
|
+
) {
|
|
992
|
+
continue;
|
|
993
|
+
}
|
|
994
|
+
projected ??= [...messages];
|
|
995
|
+
projected[i] = cloneToolMessageWithContent(
|
|
996
|
+
message,
|
|
997
|
+
'[Computer screenshot omitted for this provider]'
|
|
998
|
+
);
|
|
999
|
+
}
|
|
1000
|
+
return projected ?? messages;
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
export function projectAnthropicArtifactContent(
|
|
1004
|
+
messages: BaseMessage[],
|
|
1005
|
+
maxChars = HARD_MAX_TOOL_RESULT_CHARS
|
|
1006
|
+
): BaseMessage[] {
|
|
475
1007
|
const lastMessage = messages[messages.length - 1];
|
|
476
|
-
if (!(lastMessage instanceof ToolMessage)) return;
|
|
1008
|
+
if (!(lastMessage instanceof ToolMessage)) return messages;
|
|
477
1009
|
|
|
478
1010
|
// Find the latest AIMessage with tool_calls that this tool message belongs to
|
|
479
1011
|
const latestAIParentIndex = findLastIndex(
|
|
480
1012
|
messages,
|
|
481
1013
|
(msg) =>
|
|
482
|
-
(msg instanceof AIMessageChunk &&
|
|
1014
|
+
((msg instanceof AIMessage || msg instanceof AIMessageChunk) &&
|
|
483
1015
|
(msg.tool_calls?.length ?? 0) > 0 &&
|
|
484
1016
|
msg.tool_calls?.some((tc) => tc.id === lastMessage.tool_call_id)) ??
|
|
485
1017
|
false
|
|
486
1018
|
);
|
|
487
1019
|
|
|
488
|
-
if (latestAIParentIndex === -1) return;
|
|
1020
|
+
if (latestAIParentIndex === -1) return messages;
|
|
489
1021
|
|
|
490
1022
|
// Build tool call ID set and merge artifact content in a single forward pass.
|
|
491
|
-
const message = messages[latestAIParentIndex] as AIMessageChunk;
|
|
1023
|
+
const message = messages[latestAIParentIndex] as AIMessage | AIMessageChunk;
|
|
492
1024
|
const toolCallIdSet = new Set<string>();
|
|
493
1025
|
if (message.tool_calls) {
|
|
494
1026
|
for (const tc of message.tool_calls) {
|
|
@@ -498,69 +1030,150 @@ export function formatAnthropicArtifactContent(messages: BaseMessage[]): void {
|
|
|
498
1030
|
}
|
|
499
1031
|
}
|
|
500
1032
|
|
|
1033
|
+
let formattedMessages: BaseMessage[] | undefined;
|
|
501
1034
|
for (let j = latestAIParentIndex + 1; j < messages.length; j++) {
|
|
502
1035
|
const msg = messages[j];
|
|
503
1036
|
if (
|
|
504
1037
|
msg instanceof ToolMessage &&
|
|
1038
|
+
!isComputerCallOutputMessage(msg) &&
|
|
505
1039
|
toolCallIdSet.has(msg.tool_call_id) &&
|
|
506
1040
|
msg.artifact != null &&
|
|
507
|
-
|
|
1041
|
+
((typeof msg.artifact?.content === 'string' &&
|
|
1042
|
+
msg.artifact.content.length > 0) ||
|
|
1043
|
+
(Array.isArray(msg.artifact?.content) &&
|
|
1044
|
+
msg.artifact.content.length > 0))
|
|
508
1045
|
) {
|
|
509
|
-
const
|
|
1046
|
+
const artifactContent =
|
|
1047
|
+
typeof msg.artifact.content === 'string'
|
|
1048
|
+
? [
|
|
1049
|
+
{
|
|
1050
|
+
type: ContentTypes.TEXT,
|
|
1051
|
+
text: msg.artifact.content,
|
|
1052
|
+
},
|
|
1053
|
+
]
|
|
1054
|
+
: msg.artifact.content;
|
|
1055
|
+
const baseContent = Array.isArray(msg.content)
|
|
510
1056
|
? msg.content
|
|
511
|
-
:
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
1057
|
+
: stringifyToolMessageContent(msg.content);
|
|
1058
|
+
const content = appendBoundedContent(
|
|
1059
|
+
compactToolContent(baseContent, maxChars).content,
|
|
1060
|
+
artifactContent,
|
|
1061
|
+
maxChars
|
|
1062
|
+
);
|
|
1063
|
+
formattedMessages ??= [...messages];
|
|
1064
|
+
formattedMessages[j] = cloneToolMessageWithContent(msg, content, {
|
|
1065
|
+
...msg.artifact,
|
|
1066
|
+
content: [],
|
|
1067
|
+
});
|
|
518
1068
|
}
|
|
519
1069
|
}
|
|
1070
|
+
return formattedMessages ?? messages;
|
|
520
1071
|
}
|
|
521
1072
|
|
|
522
|
-
|
|
1073
|
+
/**
|
|
1074
|
+
* Mutating compatibility wrapper retained for existing package consumers.
|
|
1075
|
+
* New provider-call paths should use `projectAnthropicArtifactContent`.
|
|
1076
|
+
*/
|
|
1077
|
+
export function formatAnthropicArtifactContent(messages: BaseMessage[]): void {
|
|
1078
|
+
const projected = projectAnthropicArtifactContent(messages);
|
|
1079
|
+
if (projected === messages) {
|
|
1080
|
+
return;
|
|
1081
|
+
}
|
|
1082
|
+
for (let i = 0; i < messages.length; i++) {
|
|
1083
|
+
if (
|
|
1084
|
+
messages[i] instanceof ToolMessage &&
|
|
1085
|
+
projected[i] instanceof ToolMessage &&
|
|
1086
|
+
projected[i] !== messages[i]
|
|
1087
|
+
) {
|
|
1088
|
+
messages[i].content = projected[i].content;
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
export function projectArtifactPayload(
|
|
1094
|
+
messages: BaseMessage[],
|
|
1095
|
+
maxChars = HARD_MAX_TOOL_RESULT_CHARS
|
|
1096
|
+
): BaseMessage[] {
|
|
523
1097
|
const lastMessageY = messages[messages.length - 1];
|
|
524
|
-
if (!(lastMessageY instanceof ToolMessage)) return;
|
|
1098
|
+
if (!(lastMessageY instanceof ToolMessage)) return messages;
|
|
525
1099
|
|
|
526
1100
|
// Find the latest AIMessage with tool_calls that this tool message belongs to
|
|
527
1101
|
const latestAIParentIndex = findLastIndex(
|
|
528
1102
|
messages,
|
|
529
1103
|
(msg) =>
|
|
530
|
-
(msg instanceof AIMessageChunk &&
|
|
1104
|
+
((msg instanceof AIMessage || msg instanceof AIMessageChunk) &&
|
|
531
1105
|
(msg.tool_calls?.length ?? 0) > 0 &&
|
|
532
1106
|
msg.tool_calls?.some((tc) => tc.id === lastMessageY.tool_call_id)) ??
|
|
533
1107
|
false
|
|
534
1108
|
);
|
|
535
1109
|
|
|
536
|
-
if (latestAIParentIndex === -1) return;
|
|
1110
|
+
if (latestAIParentIndex === -1) return messages;
|
|
537
1111
|
|
|
538
1112
|
// Single pass: collect relevant tool messages with artifacts and aggregate
|
|
539
|
-
|
|
1113
|
+
let aggregatedContent: BaseMessage['content'] | undefined;
|
|
1114
|
+
let formattedMessages: BaseMessage[] | undefined;
|
|
540
1115
|
|
|
541
1116
|
for (let i = latestAIParentIndex + 1; i < messages.length; i++) {
|
|
542
1117
|
const msg = messages[i];
|
|
543
1118
|
if (
|
|
544
1119
|
!(msg instanceof ToolMessage) ||
|
|
545
|
-
|
|
1120
|
+
isComputerCallOutputMessage(msg) ||
|
|
1121
|
+
!(
|
|
1122
|
+
(typeof msg.artifact?.content === 'string' &&
|
|
1123
|
+
msg.artifact.content.length > 0) ||
|
|
1124
|
+
(Array.isArray(msg.artifact?.content) &&
|
|
1125
|
+
msg.artifact.content.length > 0)
|
|
1126
|
+
)
|
|
546
1127
|
) {
|
|
547
1128
|
continue;
|
|
548
1129
|
}
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
1130
|
+
aggregatedContent = appendBoundedContent(
|
|
1131
|
+
aggregatedContent,
|
|
1132
|
+
msg.content,
|
|
1133
|
+
maxChars
|
|
1134
|
+
);
|
|
1135
|
+
formattedMessages ??= [...messages];
|
|
1136
|
+
formattedMessages[i] = cloneToolMessageWithContent(
|
|
1137
|
+
msg,
|
|
1138
|
+
'Tool response is included in the next message as a Human message',
|
|
1139
|
+
{
|
|
1140
|
+
...msg.artifact,
|
|
1141
|
+
content: [],
|
|
1142
|
+
}
|
|
1143
|
+
);
|
|
1144
|
+
aggregatedContent = appendBoundedContent(
|
|
1145
|
+
aggregatedContent,
|
|
1146
|
+
msg.artifact.content,
|
|
1147
|
+
maxChars
|
|
1148
|
+
);
|
|
557
1149
|
}
|
|
558
1150
|
|
|
559
|
-
if (aggregatedContent
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
1151
|
+
if (aggregatedContent != null) {
|
|
1152
|
+
formattedMessages?.push(new HumanMessage({ content: aggregatedContent }));
|
|
1153
|
+
}
|
|
1154
|
+
return formattedMessages ?? messages;
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
/**
|
|
1158
|
+
* Mutating compatibility wrapper retained for existing package consumers.
|
|
1159
|
+
* New provider-call paths should use `projectArtifactPayload`.
|
|
1160
|
+
*/
|
|
1161
|
+
export function formatArtifactPayload(messages: BaseMessage[]): void {
|
|
1162
|
+
const originalLength = messages.length;
|
|
1163
|
+
const projected = projectArtifactPayload(messages);
|
|
1164
|
+
if (projected === messages) {
|
|
1165
|
+
return;
|
|
1166
|
+
}
|
|
1167
|
+
for (let i = 0; i < originalLength; i++) {
|
|
1168
|
+
if (
|
|
1169
|
+
messages[i] instanceof ToolMessage &&
|
|
1170
|
+
projected[i] instanceof ToolMessage &&
|
|
1171
|
+
projected[i] !== messages[i]
|
|
1172
|
+
) {
|
|
1173
|
+
messages[i].content = projected[i].content;
|
|
1174
|
+
}
|
|
563
1175
|
}
|
|
1176
|
+
messages.push(...projected.slice(originalLength));
|
|
564
1177
|
}
|
|
565
1178
|
|
|
566
1179
|
export function findLastIndex<T>(
|