@librechat/agents 3.3.3 → 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/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/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 +1 -1
- 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/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/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
|
@@ -5,13 +5,30 @@ import {
|
|
|
5
5
|
SystemMessage,
|
|
6
6
|
ToolMessage,
|
|
7
7
|
} from '@langchain/core/messages';
|
|
8
|
+
import {
|
|
9
|
+
convertMessagesToCompletionsMessageParams,
|
|
10
|
+
convertResponsesDeltaToChatGenerationChunk,
|
|
11
|
+
convertMessagesToResponsesInput,
|
|
12
|
+
convertResponsesMessageToAIMessage,
|
|
13
|
+
} from '@langchain/openai';
|
|
8
14
|
import type { MessageContentComplex, TPayload } from '@/types';
|
|
9
|
-
import { _convertMessagesToAnthropicPayload } from '@/llm/anthropic/utils/message_inputs';
|
|
10
15
|
import {
|
|
11
16
|
convertMessagesToContent,
|
|
12
17
|
formatAnthropicArtifactContent,
|
|
18
|
+
formatArtifactPayload,
|
|
19
|
+
projectAnthropicArtifactContent,
|
|
20
|
+
projectArtifactPayload,
|
|
21
|
+
projectComputerCallOutputsToText,
|
|
22
|
+
projectOpenAIToolMessageContent,
|
|
23
|
+
projectOpenAIResponsesToolMessageContent,
|
|
13
24
|
} from './core';
|
|
25
|
+
import {
|
|
26
|
+
_convertMessagesToOpenAIParams,
|
|
27
|
+
_convertMessagesToOpenAIResponsesParams,
|
|
28
|
+
} from '@/llm/openai/utils';
|
|
29
|
+
import { _convertMessagesToAnthropicPayload } from '@/llm/anthropic/utils/message_inputs';
|
|
14
30
|
import { Constants, ContentTypes, Providers } from '@/common';
|
|
31
|
+
import { serializeToolContent } from '@/utils/toolContent';
|
|
15
32
|
import { formatAgentMessages } from './format';
|
|
16
33
|
|
|
17
34
|
type AnthropicPayloadBlock = {
|
|
@@ -1551,7 +1568,7 @@ describe('formatAgentMessages', () => {
|
|
|
1551
1568
|
configurable: true,
|
|
1552
1569
|
});
|
|
1553
1570
|
|
|
1554
|
-
|
|
1571
|
+
const originalMessages = [
|
|
1555
1572
|
new AIMessageChunk({
|
|
1556
1573
|
content: '',
|
|
1557
1574
|
tool_calls: [
|
|
@@ -1564,12 +1581,575 @@ describe('formatAgentMessages', () => {
|
|
|
1564
1581
|
],
|
|
1565
1582
|
}),
|
|
1566
1583
|
toolMessage,
|
|
1567
|
-
]
|
|
1584
|
+
];
|
|
1585
|
+
const formattedMessages = projectAnthropicArtifactContent(originalMessages);
|
|
1586
|
+
const formattedToolMessage = formattedMessages[1] as ToolMessage;
|
|
1568
1587
|
|
|
1569
|
-
expect(
|
|
1588
|
+
expect(formattedToolMessage.content).toEqual([
|
|
1570
1589
|
{ type: ContentTypes.TEXT, text: '' },
|
|
1571
1590
|
{ type: ContentTypes.TEXT, text: 'artifact text' },
|
|
1572
1591
|
]);
|
|
1592
|
+
expect(toolMessage.content).toBeUndefined();
|
|
1593
|
+
expect(formattedMessages).not.toBe(originalMessages);
|
|
1594
|
+
});
|
|
1595
|
+
|
|
1596
|
+
it('caps Anthropic artifact expansion after pruning', () => {
|
|
1597
|
+
const toolMessage = new ToolMessage({
|
|
1598
|
+
content: 'short result',
|
|
1599
|
+
tool_call_id: 'call_artifact_capped',
|
|
1600
|
+
artifact: {
|
|
1601
|
+
content: 'artifact'.repeat(1_000),
|
|
1602
|
+
},
|
|
1603
|
+
});
|
|
1604
|
+
const messages = [
|
|
1605
|
+
new AIMessageChunk({
|
|
1606
|
+
content: '',
|
|
1607
|
+
tool_calls: [
|
|
1608
|
+
{
|
|
1609
|
+
id: 'call_artifact_capped',
|
|
1610
|
+
name: 'render',
|
|
1611
|
+
args: {},
|
|
1612
|
+
type: 'tool_call' as const,
|
|
1613
|
+
},
|
|
1614
|
+
],
|
|
1615
|
+
}),
|
|
1616
|
+
toolMessage,
|
|
1617
|
+
];
|
|
1618
|
+
|
|
1619
|
+
const formatted = projectAnthropicArtifactContent(messages, 200);
|
|
1620
|
+
const formattedTool = formatted[1] as ToolMessage;
|
|
1621
|
+
|
|
1622
|
+
expect(
|
|
1623
|
+
serializeToolContent(formattedTool.content).length
|
|
1624
|
+
).toBeLessThanOrEqual(200);
|
|
1625
|
+
expect(serializeToolContent(formattedTool.content)).toContain('truncated');
|
|
1626
|
+
expect(toolMessage.content).toBe('short result');
|
|
1627
|
+
expect(toolMessage.artifact.content).toContain('artifact');
|
|
1628
|
+
expect(projectAnthropicArtifactContent(formatted, 200)).toBe(formatted);
|
|
1629
|
+
});
|
|
1630
|
+
|
|
1631
|
+
it('caps the aggregate OpenAI/Google artifact payload', () => {
|
|
1632
|
+
const messages = [
|
|
1633
|
+
new AIMessageChunk({
|
|
1634
|
+
content: '',
|
|
1635
|
+
tool_calls: [
|
|
1636
|
+
{
|
|
1637
|
+
id: 'call_artifact_payload',
|
|
1638
|
+
name: 'render',
|
|
1639
|
+
args: {},
|
|
1640
|
+
type: 'tool_call' as const,
|
|
1641
|
+
},
|
|
1642
|
+
],
|
|
1643
|
+
}),
|
|
1644
|
+
new ToolMessage({
|
|
1645
|
+
content: 'short result',
|
|
1646
|
+
tool_call_id: 'call_artifact_payload',
|
|
1647
|
+
artifact: {
|
|
1648
|
+
content: [
|
|
1649
|
+
{ type: ContentTypes.TEXT, text: 'artifact'.repeat(1_000) },
|
|
1650
|
+
],
|
|
1651
|
+
},
|
|
1652
|
+
}),
|
|
1653
|
+
];
|
|
1654
|
+
|
|
1655
|
+
const formatted = projectArtifactPayload(messages, 200);
|
|
1656
|
+
|
|
1657
|
+
const payload = formatted[formatted.length - 1] as HumanMessage;
|
|
1658
|
+
expect(payload).toBeInstanceOf(HumanMessage);
|
|
1659
|
+
expect(serializeToolContent(payload.content).length).toBeLessThanOrEqual(
|
|
1660
|
+
200
|
|
1661
|
+
);
|
|
1662
|
+
expect(messages).toHaveLength(2);
|
|
1663
|
+
expect((messages[1] as ToolMessage).content).toBe('short result');
|
|
1664
|
+
expect(projectArtifactPayload(formatted, 200)).toBe(formatted);
|
|
1665
|
+
});
|
|
1666
|
+
|
|
1667
|
+
it('bounds artifact block arrays without spreading them into call arguments', () => {
|
|
1668
|
+
const repeatedBlock = {
|
|
1669
|
+
type: ContentTypes.TEXT,
|
|
1670
|
+
text: 'x',
|
|
1671
|
+
} as MessageContentComplex;
|
|
1672
|
+
const artifactContent = new Array<MessageContentComplex>(150_000).fill(
|
|
1673
|
+
repeatedBlock
|
|
1674
|
+
);
|
|
1675
|
+
const toolMessage = new ToolMessage({
|
|
1676
|
+
content: 'short result',
|
|
1677
|
+
tool_call_id: 'call_artifact_many_blocks',
|
|
1678
|
+
artifact: { content: artifactContent },
|
|
1679
|
+
});
|
|
1680
|
+
const messages = [
|
|
1681
|
+
new AIMessageChunk({
|
|
1682
|
+
content: '',
|
|
1683
|
+
tool_calls: [
|
|
1684
|
+
{
|
|
1685
|
+
id: 'call_artifact_many_blocks',
|
|
1686
|
+
name: 'render',
|
|
1687
|
+
args: {},
|
|
1688
|
+
type: 'tool_call' as const,
|
|
1689
|
+
},
|
|
1690
|
+
],
|
|
1691
|
+
}),
|
|
1692
|
+
toolMessage,
|
|
1693
|
+
];
|
|
1694
|
+
|
|
1695
|
+
const formatted = projectArtifactPayload(messages, 200);
|
|
1696
|
+
const payload = formatted[formatted.length - 1] as HumanMessage;
|
|
1697
|
+
|
|
1698
|
+
expect(payload).toBeInstanceOf(HumanMessage);
|
|
1699
|
+
expect(serializeToolContent(payload.content).length).toBeLessThanOrEqual(
|
|
1700
|
+
200
|
|
1701
|
+
);
|
|
1702
|
+
expect(toolMessage.content).toBe('short result');
|
|
1703
|
+
expect(toolMessage.artifact.content).toBe(artifactContent);
|
|
1704
|
+
});
|
|
1705
|
+
|
|
1706
|
+
it('projects structured tool content to the bounded string both OpenAI APIs send', () => {
|
|
1707
|
+
let toJSONCalls = 0;
|
|
1708
|
+
const toolMessage = new ToolMessage({
|
|
1709
|
+
id: 'tool-message-id',
|
|
1710
|
+
name: 'render',
|
|
1711
|
+
status: 'success',
|
|
1712
|
+
tool_call_id: 'call_openai_structured',
|
|
1713
|
+
content: [
|
|
1714
|
+
{ type: ContentTypes.TEXT, text: 'rendered chart' },
|
|
1715
|
+
{
|
|
1716
|
+
type: 'image_url',
|
|
1717
|
+
image_url: {
|
|
1718
|
+
url: `data:image/png;base64,${'A'.repeat(2_000)}`,
|
|
1719
|
+
},
|
|
1720
|
+
toJSON() {
|
|
1721
|
+
toJSONCalls++;
|
|
1722
|
+
return { expanded: 'B'.repeat(20_000) };
|
|
1723
|
+
},
|
|
1724
|
+
},
|
|
1725
|
+
],
|
|
1726
|
+
artifact: { retained: true },
|
|
1727
|
+
});
|
|
1728
|
+
|
|
1729
|
+
const original = [toolMessage];
|
|
1730
|
+
const projected = projectOpenAIToolMessageContent(original, 200);
|
|
1731
|
+
const projectedTool = projected[0] as ToolMessage;
|
|
1732
|
+
const chatPayload = _convertMessagesToOpenAIParams(projected);
|
|
1733
|
+
const responsesPayload = _convertMessagesToOpenAIResponsesParams(projected);
|
|
1734
|
+
|
|
1735
|
+
expect(projected).not.toBe(original);
|
|
1736
|
+
expect(typeof projectedTool.content).toBe('string');
|
|
1737
|
+
expect((projectedTool.content as string).length).toBeLessThanOrEqual(200);
|
|
1738
|
+
expect(projectedTool.tool_call_id).toBe(toolMessage.tool_call_id);
|
|
1739
|
+
expect(projectedTool.status).toBe(toolMessage.status);
|
|
1740
|
+
expect(projectedTool.artifact).toBe(toolMessage.artifact);
|
|
1741
|
+
expect(Array.isArray(toolMessage.content)).toBe(true);
|
|
1742
|
+
expect(toJSONCalls).toBe(0);
|
|
1743
|
+
expect(chatPayload[0]).toMatchObject({
|
|
1744
|
+
role: 'tool',
|
|
1745
|
+
content: projectedTool.content,
|
|
1746
|
+
tool_call_id: toolMessage.tool_call_id,
|
|
1747
|
+
});
|
|
1748
|
+
expect(responsesPayload[0]).toMatchObject({
|
|
1749
|
+
type: 'function_call_output',
|
|
1750
|
+
output: projectedTool.content,
|
|
1751
|
+
call_id: toolMessage.tool_call_id,
|
|
1752
|
+
});
|
|
1753
|
+
});
|
|
1754
|
+
|
|
1755
|
+
it('preserves Responses computer outputs handled as provider-native media', () => {
|
|
1756
|
+
const computerCall = new AIMessage({
|
|
1757
|
+
content: '',
|
|
1758
|
+
response_metadata: {
|
|
1759
|
+
output: [
|
|
1760
|
+
{
|
|
1761
|
+
type: 'computer_call',
|
|
1762
|
+
id: 'computer-item',
|
|
1763
|
+
call_id: 'call_computer',
|
|
1764
|
+
action: { type: 'screenshot' },
|
|
1765
|
+
},
|
|
1766
|
+
],
|
|
1767
|
+
},
|
|
1768
|
+
});
|
|
1769
|
+
const computerOutput = new ToolMessage({
|
|
1770
|
+
content: [
|
|
1771
|
+
{
|
|
1772
|
+
type: 'computer_screenshot',
|
|
1773
|
+
image_url: 'data:image/png;base64,AA==',
|
|
1774
|
+
},
|
|
1775
|
+
],
|
|
1776
|
+
tool_call_id: 'call_computer',
|
|
1777
|
+
additional_kwargs: { type: 'computer_call_output' },
|
|
1778
|
+
});
|
|
1779
|
+
const messages = [computerCall, computerOutput];
|
|
1780
|
+
const projected = projectOpenAIToolMessageContent(messages, 10);
|
|
1781
|
+
const responsesInput = convertMessagesToResponsesInput({
|
|
1782
|
+
messages: projected,
|
|
1783
|
+
zdrEnabled: false,
|
|
1784
|
+
model: 'computer-use-preview',
|
|
1785
|
+
});
|
|
1786
|
+
|
|
1787
|
+
expect(projected).not.toBe(messages);
|
|
1788
|
+
expect(
|
|
1789
|
+
responsesInput.find((item) => item.type === 'computer_call_output')
|
|
1790
|
+
).toMatchObject({
|
|
1791
|
+
type: 'computer_call_output',
|
|
1792
|
+
call_id: 'call_computer',
|
|
1793
|
+
output: {
|
|
1794
|
+
type: 'computer_screenshot',
|
|
1795
|
+
image_url: 'data:image/png;base64,AA==',
|
|
1796
|
+
},
|
|
1797
|
+
});
|
|
1798
|
+
});
|
|
1799
|
+
|
|
1800
|
+
it('deduplicates parsed and raw Responses computer calls in ZDR mode', () => {
|
|
1801
|
+
const response = {
|
|
1802
|
+
id: 'resp_computer',
|
|
1803
|
+
object: 'response',
|
|
1804
|
+
created_at: 0,
|
|
1805
|
+
model: 'computer-use-preview',
|
|
1806
|
+
output: [
|
|
1807
|
+
{
|
|
1808
|
+
type: 'computer_call',
|
|
1809
|
+
id: 'computer-item',
|
|
1810
|
+
call_id: 'call_computer',
|
|
1811
|
+
action: { type: 'screenshot' },
|
|
1812
|
+
status: 'completed',
|
|
1813
|
+
},
|
|
1814
|
+
],
|
|
1815
|
+
status: 'completed',
|
|
1816
|
+
usage: {
|
|
1817
|
+
input_tokens: 1,
|
|
1818
|
+
output_tokens: 1,
|
|
1819
|
+
total_tokens: 2,
|
|
1820
|
+
input_tokens_details: { cached_tokens: 0 },
|
|
1821
|
+
output_tokens_details: { reasoning_tokens: 0 },
|
|
1822
|
+
},
|
|
1823
|
+
error: null,
|
|
1824
|
+
incomplete_details: null,
|
|
1825
|
+
metadata: {},
|
|
1826
|
+
user: null,
|
|
1827
|
+
service_tier: 'default',
|
|
1828
|
+
} as unknown as Parameters<typeof convertResponsesMessageToAIMessage>[0];
|
|
1829
|
+
const computerCall = convertResponsesMessageToAIMessage(response);
|
|
1830
|
+
const computerOutput = new ToolMessage({
|
|
1831
|
+
content: 'data:image/png;base64,AA==',
|
|
1832
|
+
tool_call_id: 'call_computer',
|
|
1833
|
+
additional_kwargs: { type: 'computer_call_output' },
|
|
1834
|
+
});
|
|
1835
|
+
|
|
1836
|
+
const projected = projectOpenAIResponsesToolMessageContent([
|
|
1837
|
+
computerCall,
|
|
1838
|
+
computerOutput,
|
|
1839
|
+
]);
|
|
1840
|
+
const zdrInput = convertMessagesToResponsesInput({
|
|
1841
|
+
messages: projected,
|
|
1842
|
+
zdrEnabled: true,
|
|
1843
|
+
model: 'computer-use-preview',
|
|
1844
|
+
});
|
|
1845
|
+
const retainedInput = convertMessagesToResponsesInput({
|
|
1846
|
+
messages: projected,
|
|
1847
|
+
zdrEnabled: false,
|
|
1848
|
+
model: 'computer-use-preview',
|
|
1849
|
+
});
|
|
1850
|
+
|
|
1851
|
+
expect(computerCall.tool_calls).toHaveLength(1);
|
|
1852
|
+
expect((projected[0] as AIMessage).tool_calls).toHaveLength(0);
|
|
1853
|
+
for (const input of [zdrInput, retainedInput]) {
|
|
1854
|
+
expect(
|
|
1855
|
+
input.filter(
|
|
1856
|
+
(item) =>
|
|
1857
|
+
item.type === 'computer_call' && item.call_id === 'call_computer'
|
|
1858
|
+
)
|
|
1859
|
+
).toHaveLength(1);
|
|
1860
|
+
expect(
|
|
1861
|
+
input.filter(
|
|
1862
|
+
(item) =>
|
|
1863
|
+
item.type === 'computer_call_output' &&
|
|
1864
|
+
item.call_id === 'call_computer'
|
|
1865
|
+
)
|
|
1866
|
+
).toHaveLength(1);
|
|
1867
|
+
}
|
|
1868
|
+
});
|
|
1869
|
+
|
|
1870
|
+
it('deduplicates streaming Responses computer calls before replay', () => {
|
|
1871
|
+
const event = {
|
|
1872
|
+
type: 'response.output_item.done',
|
|
1873
|
+
sequence_number: 1,
|
|
1874
|
+
output_index: 0,
|
|
1875
|
+
item: {
|
|
1876
|
+
type: 'computer_call',
|
|
1877
|
+
id: 'computer-stream-item',
|
|
1878
|
+
call_id: 'call_computer_stream',
|
|
1879
|
+
action: { type: 'screenshot' },
|
|
1880
|
+
status: 'completed',
|
|
1881
|
+
},
|
|
1882
|
+
} as unknown as Parameters<
|
|
1883
|
+
typeof convertResponsesDeltaToChatGenerationChunk
|
|
1884
|
+
>[0];
|
|
1885
|
+
const generation = convertResponsesDeltaToChatGenerationChunk(event);
|
|
1886
|
+
const computerCall = generation?.message;
|
|
1887
|
+
expect(computerCall).toBeDefined();
|
|
1888
|
+
|
|
1889
|
+
const computerOutput = new ToolMessage({
|
|
1890
|
+
content: 'data:image/png;base64,AA==',
|
|
1891
|
+
tool_call_id: 'call_computer_stream',
|
|
1892
|
+
additional_kwargs: { type: 'computer_call_output' },
|
|
1893
|
+
});
|
|
1894
|
+
const projected = projectOpenAIResponsesToolMessageContent([
|
|
1895
|
+
computerCall!,
|
|
1896
|
+
computerOutput,
|
|
1897
|
+
]);
|
|
1898
|
+
|
|
1899
|
+
expect((computerCall as AIMessage).tool_calls).toHaveLength(1);
|
|
1900
|
+
expect((computerCall as AIMessageChunk).tool_call_chunks).toHaveLength(1);
|
|
1901
|
+
expect((projected[0] as AIMessage).tool_calls).toHaveLength(0);
|
|
1902
|
+
expect((projected[0] as AIMessageChunk).tool_call_chunks).toHaveLength(0);
|
|
1903
|
+
expect(Object.getPrototypeOf(projected[0])).toBe(
|
|
1904
|
+
Object.getPrototypeOf(computerCall)
|
|
1905
|
+
);
|
|
1906
|
+
expect(
|
|
1907
|
+
new AIMessageChunk(projected[0] as AIMessageChunk).tool_calls
|
|
1908
|
+
).toHaveLength(0);
|
|
1909
|
+
for (const zdrEnabled of [true, false]) {
|
|
1910
|
+
const input = convertMessagesToResponsesInput({
|
|
1911
|
+
messages: projected,
|
|
1912
|
+
zdrEnabled,
|
|
1913
|
+
model: 'computer-use-preview',
|
|
1914
|
+
});
|
|
1915
|
+
expect(
|
|
1916
|
+
input.filter(
|
|
1917
|
+
(item) =>
|
|
1918
|
+
item.type === 'computer_call' &&
|
|
1919
|
+
item.call_id === 'call_computer_stream'
|
|
1920
|
+
)
|
|
1921
|
+
).toHaveLength(1);
|
|
1922
|
+
expect(
|
|
1923
|
+
input.filter(
|
|
1924
|
+
(item) =>
|
|
1925
|
+
(item.type === 'function_call' || item.type === 'computer_call') &&
|
|
1926
|
+
item.call_id === 'call_computer_stream'
|
|
1927
|
+
)
|
|
1928
|
+
).toHaveLength(1);
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1931
|
+
const chatProjected = projectComputerCallOutputsToText(
|
|
1932
|
+
projectOpenAIToolMessageContent([computerCall!, computerOutput])
|
|
1933
|
+
);
|
|
1934
|
+
const chatInput = convertMessagesToCompletionsMessageParams({
|
|
1935
|
+
messages: chatProjected,
|
|
1936
|
+
model: 'gpt-4o',
|
|
1937
|
+
});
|
|
1938
|
+
|
|
1939
|
+
expect((chatProjected[0] as AIMessage).tool_calls).toHaveLength(1);
|
|
1940
|
+
expect(chatInput).toEqual([
|
|
1941
|
+
expect.objectContaining({
|
|
1942
|
+
role: 'assistant',
|
|
1943
|
+
tool_calls: [
|
|
1944
|
+
expect.objectContaining({
|
|
1945
|
+
id: 'call_computer_stream',
|
|
1946
|
+
type: 'function',
|
|
1947
|
+
}),
|
|
1948
|
+
],
|
|
1949
|
+
}),
|
|
1950
|
+
expect.objectContaining({
|
|
1951
|
+
role: 'tool',
|
|
1952
|
+
tool_call_id: 'call_computer_stream',
|
|
1953
|
+
content: '[Computer screenshot omitted for this provider]',
|
|
1954
|
+
}),
|
|
1955
|
+
]);
|
|
1956
|
+
});
|
|
1957
|
+
|
|
1958
|
+
it('canonicalizes real OpenAI file-backed computer screenshots on the production converter path', () => {
|
|
1959
|
+
const computerCall = new AIMessage({
|
|
1960
|
+
content: '',
|
|
1961
|
+
additional_kwargs: {
|
|
1962
|
+
tool_outputs: [
|
|
1963
|
+
{
|
|
1964
|
+
type: 'computer_call',
|
|
1965
|
+
call_id: 'call_computer_file',
|
|
1966
|
+
action: { type: 'screenshot' },
|
|
1967
|
+
},
|
|
1968
|
+
],
|
|
1969
|
+
},
|
|
1970
|
+
});
|
|
1971
|
+
const computerOutput = new ToolMessage({
|
|
1972
|
+
content: [
|
|
1973
|
+
{
|
|
1974
|
+
type: 'input_image',
|
|
1975
|
+
file_id: 'file-abc123',
|
|
1976
|
+
detail: 'low',
|
|
1977
|
+
},
|
|
1978
|
+
],
|
|
1979
|
+
tool_call_id: 'call_computer_file',
|
|
1980
|
+
additional_kwargs: { type: 'computer_call_output' },
|
|
1981
|
+
});
|
|
1982
|
+
|
|
1983
|
+
const projected = projectOpenAIToolMessageContent(
|
|
1984
|
+
[computerCall, computerOutput],
|
|
1985
|
+
100
|
|
1986
|
+
);
|
|
1987
|
+
const responsesInput = convertMessagesToResponsesInput({
|
|
1988
|
+
messages: projected,
|
|
1989
|
+
zdrEnabled: false,
|
|
1990
|
+
model: 'computer-use-preview',
|
|
1991
|
+
});
|
|
1992
|
+
|
|
1993
|
+
expect(responsesInput).toContainEqual({
|
|
1994
|
+
type: 'computer_call_output',
|
|
1995
|
+
call_id: 'call_computer_file',
|
|
1996
|
+
output: {
|
|
1997
|
+
type: 'input_image',
|
|
1998
|
+
file_id: 'file-abc123',
|
|
1999
|
+
detail: 'low',
|
|
2000
|
+
},
|
|
2001
|
+
});
|
|
2002
|
+
});
|
|
2003
|
+
|
|
2004
|
+
it('rejects extra invalid screenshot fields instead of shipping the original block', () => {
|
|
2005
|
+
const computerCall = new AIMessage({
|
|
2006
|
+
content: '',
|
|
2007
|
+
response_metadata: {
|
|
2008
|
+
output: [
|
|
2009
|
+
{
|
|
2010
|
+
type: 'computer_call',
|
|
2011
|
+
call_id: 'call_computer_extra',
|
|
2012
|
+
action: { type: 'screenshot' },
|
|
2013
|
+
},
|
|
2014
|
+
],
|
|
2015
|
+
},
|
|
2016
|
+
});
|
|
2017
|
+
const malformed = new ToolMessage({
|
|
2018
|
+
content: [
|
|
2019
|
+
{
|
|
2020
|
+
type: 'input_image',
|
|
2021
|
+
image_url: 'data:image/png;base64,AA==',
|
|
2022
|
+
file_id: 'not-a-file-id',
|
|
2023
|
+
},
|
|
2024
|
+
],
|
|
2025
|
+
tool_call_id: 'call_computer_extra',
|
|
2026
|
+
additional_kwargs: { type: 'computer_call_output' },
|
|
2027
|
+
});
|
|
2028
|
+
|
|
2029
|
+
expect(() =>
|
|
2030
|
+
projectOpenAIToolMessageContent([computerCall, malformed], 100)
|
|
2031
|
+
).toThrow('Invalid computer call output screenshot');
|
|
2032
|
+
});
|
|
2033
|
+
|
|
2034
|
+
it('rejects computer outputs that precede their call', () => {
|
|
2035
|
+
const computerCall = new AIMessage({
|
|
2036
|
+
content: '',
|
|
2037
|
+
response_metadata: {
|
|
2038
|
+
output: [
|
|
2039
|
+
{
|
|
2040
|
+
type: 'computer_call',
|
|
2041
|
+
call_id: 'call_computer_order',
|
|
2042
|
+
action: { type: 'screenshot' },
|
|
2043
|
+
},
|
|
2044
|
+
],
|
|
2045
|
+
},
|
|
2046
|
+
});
|
|
2047
|
+
const computerOutput = new ToolMessage({
|
|
2048
|
+
content: 'data:image/png;base64,AA==',
|
|
2049
|
+
tool_call_id: 'call_computer_order',
|
|
2050
|
+
additional_kwargs: { type: 'computer_call_output' },
|
|
2051
|
+
});
|
|
2052
|
+
|
|
2053
|
+
expect(() =>
|
|
2054
|
+
projectOpenAIToolMessageContent([computerOutput, computerCall], 100)
|
|
2055
|
+
).toThrow('Invalid computer call output pairing');
|
|
2056
|
+
});
|
|
2057
|
+
|
|
2058
|
+
it('rejects a malformed marked computer output before provider conversion', () => {
|
|
2059
|
+
const malformed = new ToolMessage({
|
|
2060
|
+
content: [{ type: 'text', text: 'A'.repeat(2_000) }],
|
|
2061
|
+
tool_call_id: 'call_computer_malformed',
|
|
2062
|
+
additional_kwargs: { type: 'computer_call_output' },
|
|
2063
|
+
});
|
|
2064
|
+
|
|
2065
|
+
expect(() => projectOpenAIToolMessageContent([malformed], 100)).toThrow(
|
|
2066
|
+
'Invalid computer call output screenshot'
|
|
2067
|
+
);
|
|
2068
|
+
});
|
|
2069
|
+
|
|
2070
|
+
it('rejects a marked screenshot paired with a normal function call', () => {
|
|
2071
|
+
const functionCall = new AIMessage({
|
|
2072
|
+
content: '',
|
|
2073
|
+
tool_calls: [
|
|
2074
|
+
{
|
|
2075
|
+
id: 'call_function',
|
|
2076
|
+
name: 'render',
|
|
2077
|
+
args: {},
|
|
2078
|
+
type: 'tool_call',
|
|
2079
|
+
},
|
|
2080
|
+
],
|
|
2081
|
+
});
|
|
2082
|
+
const malformedPair = new ToolMessage({
|
|
2083
|
+
content: 'data:image/png;base64,AA==',
|
|
2084
|
+
tool_call_id: 'call_function',
|
|
2085
|
+
additional_kwargs: { type: 'computer_call_output' },
|
|
2086
|
+
});
|
|
2087
|
+
|
|
2088
|
+
expect(() =>
|
|
2089
|
+
projectOpenAIToolMessageContent([functionCall, malformedPair], 100)
|
|
2090
|
+
).toThrow('Invalid computer call output pairing');
|
|
2091
|
+
});
|
|
2092
|
+
|
|
2093
|
+
it('keeps the mutating artifact formatter API backward compatible', () => {
|
|
2094
|
+
const anthropicToolMessage = new ToolMessage({
|
|
2095
|
+
content: 'result',
|
|
2096
|
+
tool_call_id: 'anthropic_legacy',
|
|
2097
|
+
artifact: {
|
|
2098
|
+
content: [{ type: ContentTypes.TEXT, text: 'anthropic artifact' }],
|
|
2099
|
+
},
|
|
2100
|
+
});
|
|
2101
|
+
const anthropicMessages = [
|
|
2102
|
+
new AIMessageChunk({
|
|
2103
|
+
content: '',
|
|
2104
|
+
tool_calls: [
|
|
2105
|
+
{
|
|
2106
|
+
id: 'anthropic_legacy',
|
|
2107
|
+
name: 'render',
|
|
2108
|
+
args: {},
|
|
2109
|
+
type: 'tool_call' as const,
|
|
2110
|
+
},
|
|
2111
|
+
],
|
|
2112
|
+
}),
|
|
2113
|
+
anthropicToolMessage,
|
|
2114
|
+
];
|
|
2115
|
+
|
|
2116
|
+
expect(formatAnthropicArtifactContent(anthropicMessages)).toBeUndefined();
|
|
2117
|
+
expect(anthropicToolMessage.content).toEqual([
|
|
2118
|
+
{ type: ContentTypes.TEXT, text: 'result' },
|
|
2119
|
+
{ type: ContentTypes.TEXT, text: 'anthropic artifact' },
|
|
2120
|
+
]);
|
|
2121
|
+
expect(anthropicToolMessage.artifact.content).toHaveLength(1);
|
|
2122
|
+
|
|
2123
|
+
const payloadToolMessage = new ToolMessage({
|
|
2124
|
+
content: 'result',
|
|
2125
|
+
tool_call_id: 'payload_legacy',
|
|
2126
|
+
artifact: {
|
|
2127
|
+
content: [{ type: ContentTypes.TEXT, text: 'payload artifact' }],
|
|
2128
|
+
},
|
|
2129
|
+
});
|
|
2130
|
+
const payloadMessages = [
|
|
2131
|
+
new AIMessageChunk({
|
|
2132
|
+
content: '',
|
|
2133
|
+
tool_calls: [
|
|
2134
|
+
{
|
|
2135
|
+
id: 'payload_legacy',
|
|
2136
|
+
name: 'render',
|
|
2137
|
+
args: {},
|
|
2138
|
+
type: 'tool_call' as const,
|
|
2139
|
+
},
|
|
2140
|
+
],
|
|
2141
|
+
}),
|
|
2142
|
+
payloadToolMessage,
|
|
2143
|
+
];
|
|
2144
|
+
|
|
2145
|
+
expect(formatArtifactPayload(payloadMessages)).toBeUndefined();
|
|
2146
|
+
expect(payloadToolMessage.content).toContain(
|
|
2147
|
+
'Tool response is included in the next message'
|
|
2148
|
+
);
|
|
2149
|
+
expect(payloadMessages[payloadMessages.length - 1]).toBeInstanceOf(
|
|
2150
|
+
HumanMessage
|
|
2151
|
+
);
|
|
2152
|
+
expect(payloadToolMessage.artifact.content).toHaveLength(1);
|
|
1573
2153
|
});
|
|
1574
2154
|
|
|
1575
2155
|
it('should dynamically discover tools from tool_search output and keep their tool calls', () => {
|
|
@@ -2216,9 +2796,15 @@ describe('formatAgentMessages', () => {
|
|
|
2216
2796
|
},
|
|
2217
2797
|
];
|
|
2218
2798
|
|
|
2219
|
-
const result = formatAgentMessages(
|
|
2220
|
-
|
|
2221
|
-
|
|
2799
|
+
const result = formatAgentMessages(
|
|
2800
|
+
payload,
|
|
2801
|
+
undefined,
|
|
2802
|
+
undefined,
|
|
2803
|
+
undefined,
|
|
2804
|
+
{
|
|
2805
|
+
preserveReasoningContent: true,
|
|
2806
|
+
}
|
|
2807
|
+
);
|
|
2222
2808
|
|
|
2223
2809
|
const toolCallMessage = result.messages[0] as AIMessage;
|
|
2224
2810
|
expect(toolCallMessage.tool_calls).toHaveLength(1);
|
|
@@ -2254,15 +2840,19 @@ describe('formatAgentMessages', () => {
|
|
|
2254
2840
|
},
|
|
2255
2841
|
];
|
|
2256
2842
|
|
|
2257
|
-
const result = formatAgentMessages(
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2843
|
+
const result = formatAgentMessages(
|
|
2844
|
+
payload,
|
|
2845
|
+
undefined,
|
|
2846
|
+
undefined,
|
|
2847
|
+
undefined,
|
|
2848
|
+
{
|
|
2849
|
+
provider: Providers.DEEPSEEK,
|
|
2850
|
+
preserveReasoningContent: false,
|
|
2851
|
+
}
|
|
2852
|
+
);
|
|
2261
2853
|
|
|
2262
2854
|
const toolCallMessage = result.messages[0] as AIMessage;
|
|
2263
|
-
expect(
|
|
2264
|
-
toolCallMessage.additional_kwargs.reasoning_content
|
|
2265
|
-
).toBeUndefined();
|
|
2855
|
+
expect(toolCallMessage.additional_kwargs.reasoning_content).toBeUndefined();
|
|
2266
2856
|
});
|
|
2267
2857
|
|
|
2268
2858
|
it('should preserve DeepSeek reasoning from supported hidden content blocks', () => {
|