@librechat/agents 3.1.64 → 3.1.66-dev.0
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/common/enum.cjs +13 -0
- package/dist/cjs/common/enum.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +3 -0
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/hooks/HookRegistry.cjs +162 -0
- package/dist/cjs/hooks/HookRegistry.cjs.map +1 -0
- package/dist/cjs/hooks/executeHooks.cjs +276 -0
- package/dist/cjs/hooks/executeHooks.cjs.map +1 -0
- package/dist/cjs/hooks/matchers.cjs +256 -0
- package/dist/cjs/hooks/matchers.cjs.map +1 -0
- package/dist/cjs/hooks/types.cjs +27 -0
- package/dist/cjs/hooks/types.cjs.map +1 -0
- package/dist/cjs/llm/anthropic/types.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +69 -54
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/main.cjs +40 -0
- package/dist/cjs/main.cjs.map +1 -1
- package/dist/cjs/messages/core.cjs +8 -1
- package/dist/cjs/messages/core.cjs.map +1 -1
- package/dist/cjs/messages/format.cjs +74 -12
- package/dist/cjs/messages/format.cjs.map +1 -1
- package/dist/cjs/run.cjs +111 -0
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/tools/BashExecutor.cjs +175 -0
- package/dist/cjs/tools/BashExecutor.cjs.map +1 -0
- package/dist/cjs/tools/BashProgrammaticToolCalling.cjs +296 -0
- package/dist/cjs/tools/BashProgrammaticToolCalling.cjs.map +1 -0
- package/dist/cjs/tools/ReadFile.cjs +43 -0
- package/dist/cjs/tools/ReadFile.cjs.map +1 -0
- package/dist/cjs/tools/SkillTool.cjs +50 -0
- package/dist/cjs/tools/SkillTool.cjs.map +1 -0
- package/dist/cjs/tools/ToolNode.cjs +304 -140
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/skillCatalog.cjs +84 -0
- package/dist/cjs/tools/skillCatalog.cjs.map +1 -0
- package/dist/esm/common/enum.mjs +12 -1
- package/dist/esm/common/enum.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +3 -0
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/hooks/HookRegistry.mjs +160 -0
- package/dist/esm/hooks/HookRegistry.mjs.map +1 -0
- package/dist/esm/hooks/executeHooks.mjs +273 -0
- package/dist/esm/hooks/executeHooks.mjs.map +1 -0
- package/dist/esm/hooks/matchers.mjs +251 -0
- package/dist/esm/hooks/matchers.mjs.map +1 -0
- package/dist/esm/hooks/types.mjs +25 -0
- package/dist/esm/hooks/types.mjs.map +1 -0
- package/dist/esm/llm/anthropic/types.mjs.map +1 -1
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs +69 -54
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/main.mjs +10 -1
- package/dist/esm/main.mjs.map +1 -1
- package/dist/esm/messages/core.mjs +8 -1
- package/dist/esm/messages/core.mjs.map +1 -1
- package/dist/esm/messages/format.mjs +66 -4
- package/dist/esm/messages/format.mjs.map +1 -1
- package/dist/esm/run.mjs +111 -0
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/tools/BashExecutor.mjs +169 -0
- package/dist/esm/tools/BashExecutor.mjs.map +1 -0
- package/dist/esm/tools/BashProgrammaticToolCalling.mjs +287 -0
- package/dist/esm/tools/BashProgrammaticToolCalling.mjs.map +1 -0
- package/dist/esm/tools/ReadFile.mjs +38 -0
- package/dist/esm/tools/ReadFile.mjs.map +1 -0
- package/dist/esm/tools/SkillTool.mjs +45 -0
- package/dist/esm/tools/SkillTool.mjs.map +1 -0
- package/dist/esm/tools/ToolNode.mjs +306 -142
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/skillCatalog.mjs +82 -0
- package/dist/esm/tools/skillCatalog.mjs.map +1 -0
- package/dist/types/common/enum.d.ts +7 -1
- package/dist/types/graphs/Graph.d.ts +2 -0
- package/dist/types/hooks/HookRegistry.d.ts +56 -0
- package/dist/types/hooks/executeHooks.d.ts +79 -0
- package/dist/types/hooks/index.d.ts +6 -0
- package/dist/types/hooks/matchers.d.ts +95 -0
- package/dist/types/hooks/types.d.ts +309 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/types/llm/anthropic/types.d.ts +1 -1
- package/dist/types/messages/format.d.ts +2 -1
- package/dist/types/run.d.ts +1 -0
- package/dist/types/tools/BashExecutor.d.ts +45 -0
- package/dist/types/tools/BashProgrammaticToolCalling.d.ts +72 -0
- package/dist/types/tools/ReadFile.d.ts +28 -0
- package/dist/types/tools/SkillTool.d.ts +40 -0
- package/dist/types/tools/ToolNode.d.ts +24 -2
- package/dist/types/tools/skillCatalog.d.ts +19 -0
- package/dist/types/types/index.d.ts +1 -0
- package/dist/types/types/run.d.ts +20 -0
- package/dist/types/types/skill.d.ts +9 -0
- package/dist/types/types/tools.d.ts +38 -1
- package/package.json +2 -2
- package/src/common/enum.ts +12 -0
- package/src/graphs/Graph.ts +4 -0
- package/src/hooks/HookRegistry.ts +208 -0
- package/src/hooks/__tests__/HookRegistry.test.ts +190 -0
- package/src/hooks/__tests__/executeHooks.test.ts +1013 -0
- package/src/hooks/__tests__/integration.test.ts +337 -0
- package/src/hooks/__tests__/matchers.test.ts +238 -0
- package/src/hooks/__tests__/toolHooks.test.ts +669 -0
- package/src/hooks/executeHooks.ts +375 -0
- package/src/hooks/index.ts +55 -0
- package/src/hooks/matchers.ts +280 -0
- package/src/hooks/types.ts +388 -0
- package/src/index.ts +8 -0
- package/src/llm/anthropic/types.ts +1 -1
- package/src/llm/anthropic/utils/message_inputs.ts +93 -68
- package/src/llm/anthropic/utils/server-tool-inputs.test.ts +349 -0
- package/src/messages/core.ts +8 -1
- package/src/messages/format.ts +74 -4
- package/src/messages/formatAgentMessages.skills.test.ts +334 -0
- package/src/run.ts +126 -0
- package/src/tools/BashExecutor.ts +205 -0
- package/src/tools/BashProgrammaticToolCalling.ts +397 -0
- package/src/tools/ReadFile.ts +39 -0
- package/src/tools/SkillTool.ts +46 -0
- package/src/tools/ToolNode.ts +391 -169
- package/src/tools/__tests__/ReadFile.test.ts +44 -0
- package/src/tools/__tests__/SkillTool.test.ts +442 -0
- package/src/tools/__tests__/ToolNode.session.test.ts +12 -12
- package/src/tools/__tests__/skillCatalog.test.ts +161 -0
- package/src/tools/skillCatalog.ts +126 -0
- package/src/types/index.ts +1 -0
- package/src/types/run.ts +20 -0
- package/src/types/skill.ts +11 -0
- package/src/types/tools.ts +41 -1
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import { AIMessage, HumanMessage, ToolMessage } from '@langchain/core/messages';
|
|
3
|
+
import type { BaseMessage } from '@langchain/core/messages';
|
|
4
|
+
import { _convertMessagesToAnthropicPayload } from './message_inputs';
|
|
5
|
+
|
|
6
|
+
describe('_convertMessagesToAnthropicPayload — server tool use (web search) multi-turn', () => {
|
|
7
|
+
it('corrects tool_use blocks with srvtoolu_ IDs to server_tool_use', () => {
|
|
8
|
+
const messageHistory: BaseMessage[] = [
|
|
9
|
+
new HumanMessage('search for X and Y'),
|
|
10
|
+
new AIMessage({
|
|
11
|
+
content: [
|
|
12
|
+
{ type: 'text', text: 'I will search for that.' },
|
|
13
|
+
{
|
|
14
|
+
type: 'tool_use',
|
|
15
|
+
id: 'srvtoolu_1',
|
|
16
|
+
name: 'web_search',
|
|
17
|
+
input: { query: 'X' },
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
type: 'web_search_tool_result',
|
|
21
|
+
tool_use_id: 'srvtoolu_1',
|
|
22
|
+
content: [
|
|
23
|
+
{
|
|
24
|
+
type: 'web_search_result',
|
|
25
|
+
url: 'https://example.com',
|
|
26
|
+
title: 'Result',
|
|
27
|
+
encrypted_content: 'abc',
|
|
28
|
+
page_age: '1d',
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
type: 'tool_use',
|
|
34
|
+
id: 'srvtoolu_2',
|
|
35
|
+
name: 'web_search',
|
|
36
|
+
input: { query: 'Y' },
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
type: 'web_search_tool_result',
|
|
40
|
+
tool_use_id: 'srvtoolu_2',
|
|
41
|
+
content: [
|
|
42
|
+
{
|
|
43
|
+
type: 'web_search_result',
|
|
44
|
+
url: 'https://example2.com',
|
|
45
|
+
title: 'Result 2',
|
|
46
|
+
encrypted_content: 'def',
|
|
47
|
+
page_age: '2d',
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
},
|
|
51
|
+
{ type: 'text', text: 'Here are the results.' },
|
|
52
|
+
],
|
|
53
|
+
tool_calls: [
|
|
54
|
+
{
|
|
55
|
+
id: 'srvtoolu_1',
|
|
56
|
+
name: 'web_search',
|
|
57
|
+
args: { query: 'X' },
|
|
58
|
+
type: 'tool_call',
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
id: 'srvtoolu_2',
|
|
62
|
+
name: 'web_search',
|
|
63
|
+
args: { query: 'Y' },
|
|
64
|
+
type: 'tool_call',
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
}),
|
|
68
|
+
new HumanMessage('follow up question'),
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
const { messages } = _convertMessagesToAnthropicPayload(messageHistory);
|
|
72
|
+
expect(messages).toHaveLength(3);
|
|
73
|
+
|
|
74
|
+
const assistantContent = messages[1].content as any[];
|
|
75
|
+
const serverToolBlocks = assistantContent.filter(
|
|
76
|
+
(b: any) => b.type === 'server_tool_use'
|
|
77
|
+
);
|
|
78
|
+
const searchResultBlocks = assistantContent.filter(
|
|
79
|
+
(b: any) => b.type === 'web_search_tool_result'
|
|
80
|
+
);
|
|
81
|
+
const regularToolUseBlocks = assistantContent.filter(
|
|
82
|
+
(b: any) => b.type === 'tool_use' && b.id?.startsWith('srvtoolu_')
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
expect(serverToolBlocks).toHaveLength(2);
|
|
86
|
+
expect(searchResultBlocks).toHaveLength(2);
|
|
87
|
+
expect(regularToolUseBlocks).toHaveLength(0);
|
|
88
|
+
|
|
89
|
+
// Verify blocks are clean (no extra streaming properties)
|
|
90
|
+
for (const b of serverToolBlocks) {
|
|
91
|
+
expect(Object.keys(b).sort()).toEqual(
|
|
92
|
+
['id', 'input', 'name', 'type'].sort()
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
for (const b of searchResultBlocks) {
|
|
96
|
+
expect(Object.keys(b).sort()).toEqual(
|
|
97
|
+
['content', 'tool_use_id', 'type'].sort()
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('corrects text-typed server tool blocks back to correct types', () => {
|
|
103
|
+
const messageHistory: BaseMessage[] = [
|
|
104
|
+
new HumanMessage('search for X'),
|
|
105
|
+
new AIMessage({
|
|
106
|
+
content: [
|
|
107
|
+
{
|
|
108
|
+
type: 'text',
|
|
109
|
+
id: 'srvtoolu_1',
|
|
110
|
+
name: 'web_search',
|
|
111
|
+
input: '{"query":"X"}',
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
type: 'text',
|
|
115
|
+
tool_use_id: 'srvtoolu_1',
|
|
116
|
+
content: [
|
|
117
|
+
{
|
|
118
|
+
type: 'web_search_result',
|
|
119
|
+
url: 'https://example.com',
|
|
120
|
+
title: 'Result',
|
|
121
|
+
encrypted_content: 'abc',
|
|
122
|
+
page_age: '1d',
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
},
|
|
126
|
+
{ type: 'text', text: 'Found results.' },
|
|
127
|
+
],
|
|
128
|
+
tool_calls: [
|
|
129
|
+
{
|
|
130
|
+
id: 'srvtoolu_1',
|
|
131
|
+
name: 'web_search',
|
|
132
|
+
args: { query: 'X' },
|
|
133
|
+
type: 'tool_call',
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
}),
|
|
137
|
+
new HumanMessage('follow up'),
|
|
138
|
+
];
|
|
139
|
+
|
|
140
|
+
const { messages } = _convertMessagesToAnthropicPayload(messageHistory);
|
|
141
|
+
const assistantContent = messages[1].content as any[];
|
|
142
|
+
|
|
143
|
+
expect(assistantContent[0]).toEqual({
|
|
144
|
+
type: 'server_tool_use',
|
|
145
|
+
id: 'srvtoolu_1',
|
|
146
|
+
name: 'web_search',
|
|
147
|
+
input: { query: 'X' },
|
|
148
|
+
});
|
|
149
|
+
expect(assistantContent[1].type).toBe('web_search_tool_result');
|
|
150
|
+
expect(assistantContent[1].tool_use_id).toBe('srvtoolu_1');
|
|
151
|
+
expect(assistantContent[2]).toEqual({
|
|
152
|
+
type: 'text',
|
|
153
|
+
text: 'Found results.',
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('filters server tool calls when content is a string', () => {
|
|
158
|
+
const messageHistory: BaseMessage[] = [
|
|
159
|
+
new HumanMessage('search for X'),
|
|
160
|
+
new AIMessage({
|
|
161
|
+
content: 'I searched and found results.',
|
|
162
|
+
tool_calls: [
|
|
163
|
+
{
|
|
164
|
+
id: 'srvtoolu_1',
|
|
165
|
+
name: 'web_search',
|
|
166
|
+
args: { query: 'X' },
|
|
167
|
+
type: 'tool_call',
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
id: 'toolu_regular',
|
|
171
|
+
name: 'calculator',
|
|
172
|
+
args: { expr: '2+2' },
|
|
173
|
+
type: 'tool_call',
|
|
174
|
+
},
|
|
175
|
+
],
|
|
176
|
+
}),
|
|
177
|
+
new ToolMessage({
|
|
178
|
+
content: '4',
|
|
179
|
+
tool_call_id: 'toolu_regular',
|
|
180
|
+
}),
|
|
181
|
+
new HumanMessage('follow up'),
|
|
182
|
+
];
|
|
183
|
+
|
|
184
|
+
const { messages } = _convertMessagesToAnthropicPayload(messageHistory);
|
|
185
|
+
const assistantContent = messages[1].content as any[];
|
|
186
|
+
|
|
187
|
+
const toolUseBlocks = assistantContent.filter(
|
|
188
|
+
(b: any) => b.type === 'tool_use'
|
|
189
|
+
);
|
|
190
|
+
expect(toolUseBlocks).toHaveLength(1);
|
|
191
|
+
expect(toolUseBlocks[0].id).toBe('toolu_regular');
|
|
192
|
+
|
|
193
|
+
const serverToolBlocks = assistantContent.filter((b: any) =>
|
|
194
|
+
b.id?.startsWith('srvtoolu_')
|
|
195
|
+
);
|
|
196
|
+
expect(serverToolBlocks).toHaveLength(0);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('handles empty string content with only server tool calls', () => {
|
|
200
|
+
const messageHistory: BaseMessage[] = [
|
|
201
|
+
new HumanMessage('search for X'),
|
|
202
|
+
new AIMessage({
|
|
203
|
+
content: '',
|
|
204
|
+
tool_calls: [
|
|
205
|
+
{
|
|
206
|
+
id: 'srvtoolu_1',
|
|
207
|
+
name: 'web_search',
|
|
208
|
+
args: { query: 'X' },
|
|
209
|
+
type: 'tool_call',
|
|
210
|
+
},
|
|
211
|
+
],
|
|
212
|
+
}),
|
|
213
|
+
new HumanMessage('follow up'),
|
|
214
|
+
];
|
|
215
|
+
|
|
216
|
+
const { messages } = _convertMessagesToAnthropicPayload(messageHistory);
|
|
217
|
+
const assistantContent = messages[1].content as any[];
|
|
218
|
+
expect(assistantContent).toHaveLength(1);
|
|
219
|
+
expect(assistantContent[0].type).toBe('text');
|
|
220
|
+
expect(assistantContent[0].text).toBe(' ');
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('preserves regular tool_use blocks alongside corrected server tool blocks', () => {
|
|
224
|
+
const messageHistory: BaseMessage[] = [
|
|
225
|
+
new HumanMessage('search for X and calculate 2+2'),
|
|
226
|
+
new AIMessage({
|
|
227
|
+
content: [
|
|
228
|
+
{ type: 'text', text: 'Let me help.' },
|
|
229
|
+
{
|
|
230
|
+
type: 'tool_use',
|
|
231
|
+
id: 'srvtoolu_1',
|
|
232
|
+
name: 'web_search',
|
|
233
|
+
input: { query: 'X' },
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
type: 'web_search_tool_result',
|
|
237
|
+
tool_use_id: 'srvtoolu_1',
|
|
238
|
+
content: [
|
|
239
|
+
{
|
|
240
|
+
type: 'web_search_result',
|
|
241
|
+
url: 'https://example.com',
|
|
242
|
+
title: 'Result',
|
|
243
|
+
encrypted_content: 'abc',
|
|
244
|
+
page_age: '1d',
|
|
245
|
+
},
|
|
246
|
+
],
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
type: 'tool_use',
|
|
250
|
+
id: 'toolu_calc',
|
|
251
|
+
name: 'calculator',
|
|
252
|
+
input: { expr: '2+2' },
|
|
253
|
+
},
|
|
254
|
+
],
|
|
255
|
+
tool_calls: [
|
|
256
|
+
{
|
|
257
|
+
id: 'srvtoolu_1',
|
|
258
|
+
name: 'web_search',
|
|
259
|
+
args: { query: 'X' },
|
|
260
|
+
type: 'tool_call',
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
id: 'toolu_calc',
|
|
264
|
+
name: 'calculator',
|
|
265
|
+
args: { expr: '2+2' },
|
|
266
|
+
type: 'tool_call',
|
|
267
|
+
},
|
|
268
|
+
],
|
|
269
|
+
}),
|
|
270
|
+
new ToolMessage({
|
|
271
|
+
content: '4',
|
|
272
|
+
tool_call_id: 'toolu_calc',
|
|
273
|
+
}),
|
|
274
|
+
new HumanMessage('follow up'),
|
|
275
|
+
];
|
|
276
|
+
|
|
277
|
+
const { messages } = _convertMessagesToAnthropicPayload(messageHistory);
|
|
278
|
+
const assistantContent = messages[1].content as any[];
|
|
279
|
+
|
|
280
|
+
const serverToolUse = assistantContent.filter(
|
|
281
|
+
(b: any) => b.type === 'server_tool_use'
|
|
282
|
+
);
|
|
283
|
+
const webSearchResult = assistantContent.filter(
|
|
284
|
+
(b: any) => b.type === 'web_search_tool_result'
|
|
285
|
+
);
|
|
286
|
+
const regularToolUse = assistantContent.filter(
|
|
287
|
+
(b: any) => b.type === 'tool_use' && !b.id?.startsWith('srvtoolu_')
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
expect(serverToolUse).toHaveLength(1);
|
|
291
|
+
expect(serverToolUse[0].id).toBe('srvtoolu_1');
|
|
292
|
+
expect(webSearchResult).toHaveLength(1);
|
|
293
|
+
expect(regularToolUse).toHaveLength(1);
|
|
294
|
+
expect(regularToolUse[0].id).toBe('toolu_calc');
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it('filters out empty text blocks from array content', () => {
|
|
298
|
+
const messageHistory: BaseMessage[] = [
|
|
299
|
+
new HumanMessage('search for X'),
|
|
300
|
+
new AIMessage({
|
|
301
|
+
content: [
|
|
302
|
+
{ type: 'text', text: '' },
|
|
303
|
+
{
|
|
304
|
+
type: 'server_tool_use',
|
|
305
|
+
id: 'srvtoolu_1',
|
|
306
|
+
name: 'web_search',
|
|
307
|
+
input: { query: 'X' },
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
type: 'web_search_tool_result',
|
|
311
|
+
tool_use_id: 'srvtoolu_1',
|
|
312
|
+
content: [
|
|
313
|
+
{
|
|
314
|
+
type: 'web_search_result',
|
|
315
|
+
url: 'https://example.com',
|
|
316
|
+
title: 'Result',
|
|
317
|
+
encrypted_content: 'abc',
|
|
318
|
+
page_age: '1d',
|
|
319
|
+
},
|
|
320
|
+
],
|
|
321
|
+
},
|
|
322
|
+
{ type: 'text', text: '' },
|
|
323
|
+
{ type: 'text', text: 'Here are the results.' },
|
|
324
|
+
],
|
|
325
|
+
tool_calls: [
|
|
326
|
+
{
|
|
327
|
+
id: 'srvtoolu_1',
|
|
328
|
+
name: 'web_search',
|
|
329
|
+
args: { query: 'X' },
|
|
330
|
+
type: 'tool_call',
|
|
331
|
+
},
|
|
332
|
+
],
|
|
333
|
+
}),
|
|
334
|
+
new HumanMessage('follow up'),
|
|
335
|
+
];
|
|
336
|
+
|
|
337
|
+
const { messages } = _convertMessagesToAnthropicPayload(messageHistory);
|
|
338
|
+
const assistantContent = messages[1].content as any[];
|
|
339
|
+
|
|
340
|
+
const emptyTextBlocks = assistantContent.filter(
|
|
341
|
+
(b: any) => b.type === 'text' && b.text === ''
|
|
342
|
+
);
|
|
343
|
+
expect(emptyTextBlocks).toHaveLength(0);
|
|
344
|
+
|
|
345
|
+
const textBlocks = assistantContent.filter((b: any) => b.type === 'text');
|
|
346
|
+
expect(textBlocks).toHaveLength(1);
|
|
347
|
+
expect(textBlocks[0].text).toBe('Here are the results.');
|
|
348
|
+
});
|
|
349
|
+
});
|
package/src/messages/core.ts
CHANGED
|
@@ -41,7 +41,14 @@ User: ${userMessage[1]}
|
|
|
41
41
|
const _allowedTypes = ['image_url', 'text', 'tool_use', 'tool_result'];
|
|
42
42
|
const allowedTypesByProvider: Record<string, string[]> = {
|
|
43
43
|
default: _allowedTypes,
|
|
44
|
-
[Providers.ANTHROPIC]: [
|
|
44
|
+
[Providers.ANTHROPIC]: [
|
|
45
|
+
..._allowedTypes,
|
|
46
|
+
'thinking',
|
|
47
|
+
'redacted_thinking',
|
|
48
|
+
'server_tool_use',
|
|
49
|
+
'web_search_tool_result',
|
|
50
|
+
'web_search_result',
|
|
51
|
+
],
|
|
45
52
|
[Providers.BEDROCK]: [..._allowedTypes, 'reasoning_content'],
|
|
46
53
|
[Providers.OPENAI]: _allowedTypes,
|
|
47
54
|
};
|
package/src/messages/format.ts
CHANGED
|
@@ -797,18 +797,39 @@ function contentPartCharLength(part: MessageContentComplex): number {
|
|
|
797
797
|
return len;
|
|
798
798
|
}
|
|
799
799
|
|
|
800
|
+
/** Extracts the skillName from a skill tool_call's args (string or object). */
|
|
801
|
+
function extractSkillName(args: unknown): string | undefined {
|
|
802
|
+
let parsed: Record<string, unknown> | undefined;
|
|
803
|
+
if (typeof args === 'string') {
|
|
804
|
+
try {
|
|
805
|
+
parsed = JSON.parse(args) as Record<string, unknown>;
|
|
806
|
+
} catch {
|
|
807
|
+
/* malformed args — skip */
|
|
808
|
+
}
|
|
809
|
+
} else {
|
|
810
|
+
parsed = args as Record<string, unknown> | undefined;
|
|
811
|
+
}
|
|
812
|
+
const name = parsed?.skillName;
|
|
813
|
+
return typeof name === 'string' && name !== '' ? name : undefined;
|
|
814
|
+
}
|
|
815
|
+
|
|
800
816
|
/**
|
|
801
817
|
* Formats an array of messages for LangChain, handling tool calls and creating ToolMessage instances.
|
|
802
818
|
*
|
|
803
819
|
* @param payload - The array of messages to format.
|
|
804
820
|
* @param indexTokenCountMap - Optional map of message indices to token counts.
|
|
805
821
|
* @param tools - Optional set of tool names that are allowed in the request.
|
|
822
|
+
* @param skills - Optional map of skill name to body for reconstructing skill HumanMessages.
|
|
806
823
|
* @returns - Object containing formatted messages and updated indexTokenCountMap if provided.
|
|
807
824
|
*/
|
|
808
825
|
export const formatAgentMessages = (
|
|
809
826
|
payload: TPayload,
|
|
810
827
|
indexTokenCountMap?: Record<number, number | undefined>,
|
|
811
|
-
tools?: Set<string
|
|
828
|
+
tools?: Set<string>,
|
|
829
|
+
/** Pre-resolved skill bodies keyed by skill name. When present, HumanMessages
|
|
830
|
+
* are reconstructed after skill ToolMessages to restore skill instructions
|
|
831
|
+
* that were only in LangGraph state during the original run. */
|
|
832
|
+
skills?: Map<string, string>
|
|
812
833
|
): {
|
|
813
834
|
messages: Array<HumanMessage | AIMessage | SystemMessage | ToolMessage>;
|
|
814
835
|
indexTokenCountMap?: Record<number, number>;
|
|
@@ -902,6 +923,7 @@ export const formatAgentMessages = (
|
|
|
902
923
|
* - Dynamically expand the set when tool_search results are encountered
|
|
903
924
|
*/
|
|
904
925
|
let processedMessage = message;
|
|
926
|
+
let pendingSkillNames: Set<string> | undefined;
|
|
905
927
|
if (discoveredTools) {
|
|
906
928
|
const content = message.content;
|
|
907
929
|
if (content != null && Array.isArray(content)) {
|
|
@@ -950,8 +972,17 @@ export const formatAgentMessages = (
|
|
|
950
972
|
}
|
|
951
973
|
|
|
952
974
|
if (discoveredTools.has(toolName)) {
|
|
953
|
-
/** Valid tool - keep it */
|
|
954
975
|
filteredContent.push(part);
|
|
976
|
+
if (
|
|
977
|
+
toolName === Constants.SKILL_TOOL &&
|
|
978
|
+
skills?.size != null &&
|
|
979
|
+
skills.size > 0
|
|
980
|
+
) {
|
|
981
|
+
const skillName = extractSkillName(part.tool_call.args) ?? '';
|
|
982
|
+
if (skillName) {
|
|
983
|
+
(pendingSkillNames ??= new Set()).add(skillName);
|
|
984
|
+
}
|
|
985
|
+
}
|
|
955
986
|
} else {
|
|
956
987
|
/** Invalid tool - convert to string for context preservation */
|
|
957
988
|
if (
|
|
@@ -1027,6 +1058,25 @@ export const formatAgentMessages = (
|
|
|
1027
1058
|
}
|
|
1028
1059
|
}
|
|
1029
1060
|
|
|
1061
|
+
/** When tools filtering is off, still detect skill tool_calls for body reconstruction */
|
|
1062
|
+
if (!discoveredTools && skills?.size != null && skills.size > 0) {
|
|
1063
|
+
const content = processedMessage.content;
|
|
1064
|
+
if (Array.isArray(content)) {
|
|
1065
|
+
for (const part of content) {
|
|
1066
|
+
if (
|
|
1067
|
+
part.type !== ContentTypes.TOOL_CALL ||
|
|
1068
|
+
part.tool_call?.name !== Constants.SKILL_TOOL
|
|
1069
|
+
) {
|
|
1070
|
+
continue;
|
|
1071
|
+
}
|
|
1072
|
+
const skillName = extractSkillName(part.tool_call.args) ?? '';
|
|
1073
|
+
if (skillName) {
|
|
1074
|
+
(pendingSkillNames ??= new Set()).add(skillName);
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1030
1080
|
const formattedMessages = formatAssistantMessage(processedMessage);
|
|
1031
1081
|
if (sourceMessageId != null && sourceMessageId !== '') {
|
|
1032
1082
|
for (const formattedMessage of formattedMessages) {
|
|
@@ -1035,9 +1085,29 @@ export const formatAgentMessages = (
|
|
|
1035
1085
|
}
|
|
1036
1086
|
messages.push(...formattedMessages);
|
|
1037
1087
|
|
|
1038
|
-
//
|
|
1039
|
-
//
|
|
1088
|
+
// Capture index range BEFORE skill body injection so injected
|
|
1089
|
+
// HumanMessages are excluded from the assistant's token distribution.
|
|
1040
1090
|
const endMessageIndex = messages.length;
|
|
1091
|
+
|
|
1092
|
+
if (pendingSkillNames?.size != null && pendingSkillNames.size > 0) {
|
|
1093
|
+
for (const skillName of pendingSkillNames) {
|
|
1094
|
+
const body = skills?.get(skillName) ?? '';
|
|
1095
|
+
if (body) {
|
|
1096
|
+
messages.push(
|
|
1097
|
+
new HumanMessage({
|
|
1098
|
+
content: body,
|
|
1099
|
+
additional_kwargs: {
|
|
1100
|
+
role: 'user',
|
|
1101
|
+
isMeta: true,
|
|
1102
|
+
source: 'skill',
|
|
1103
|
+
skillName,
|
|
1104
|
+
},
|
|
1105
|
+
})
|
|
1106
|
+
);
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1041
1111
|
const resultIndices = [];
|
|
1042
1112
|
for (let j = startMessageIndex; j < endMessageIndex; j++) {
|
|
1043
1113
|
resultIndices.push(j);
|