@librechat/agents 3.1.66-dev.0 → 3.1.67
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 +24 -15
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/common/enum.cjs +0 -13
- package/dist/cjs/common/enum.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +0 -3
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/main.cjs +0 -40
- package/dist/cjs/main.cjs.map +1 -1
- package/dist/cjs/messages/format.cjs +12 -74
- package/dist/cjs/messages/format.cjs.map +1 -1
- package/dist/cjs/run.cjs +0 -111
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +140 -304
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/esm/agents/AgentContext.mjs +24 -15
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/common/enum.mjs +1 -12
- package/dist/esm/common/enum.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +0 -3
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/main.mjs +1 -10
- package/dist/esm/main.mjs.map +1 -1
- package/dist/esm/messages/format.mjs +4 -66
- package/dist/esm/messages/format.mjs.map +1 -1
- package/dist/esm/run.mjs +0 -111
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +142 -306
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/types/agents/AgentContext.d.ts +6 -0
- package/dist/types/common/enum.d.ts +1 -7
- package/dist/types/graphs/Graph.d.ts +0 -2
- package/dist/types/index.d.ts +0 -6
- package/dist/types/messages/format.d.ts +1 -2
- package/dist/types/run.d.ts +0 -1
- package/dist/types/tools/ToolNode.d.ts +2 -24
- package/dist/types/types/index.d.ts +0 -1
- package/dist/types/types/llm.d.ts +14 -2
- package/dist/types/types/run.d.ts +0 -20
- package/dist/types/types/tools.d.ts +1 -38
- package/package.json +1 -1
- package/src/agents/AgentContext.ts +28 -15
- package/src/agents/__tests__/AgentContext.test.ts +110 -0
- package/src/common/enum.ts +0 -12
- package/src/graphs/Graph.ts +0 -4
- package/src/index.ts +0 -8
- package/src/messages/format.ts +4 -74
- package/src/run.ts +0 -126
- package/src/tools/ToolNode.ts +169 -391
- package/src/tools/__tests__/ToolNode.session.test.ts +12 -12
- package/src/types/index.ts +0 -1
- package/src/types/llm.ts +16 -2
- package/src/types/run.ts +0 -20
- package/src/types/tools.ts +1 -41
- package/dist/cjs/hooks/HookRegistry.cjs +0 -162
- package/dist/cjs/hooks/HookRegistry.cjs.map +0 -1
- package/dist/cjs/hooks/executeHooks.cjs +0 -276
- package/dist/cjs/hooks/executeHooks.cjs.map +0 -1
- package/dist/cjs/hooks/matchers.cjs +0 -256
- package/dist/cjs/hooks/matchers.cjs.map +0 -1
- package/dist/cjs/hooks/types.cjs +0 -27
- package/dist/cjs/hooks/types.cjs.map +0 -1
- package/dist/cjs/tools/BashExecutor.cjs +0 -175
- package/dist/cjs/tools/BashExecutor.cjs.map +0 -1
- package/dist/cjs/tools/BashProgrammaticToolCalling.cjs +0 -296
- package/dist/cjs/tools/BashProgrammaticToolCalling.cjs.map +0 -1
- package/dist/cjs/tools/ReadFile.cjs +0 -43
- package/dist/cjs/tools/ReadFile.cjs.map +0 -1
- package/dist/cjs/tools/SkillTool.cjs +0 -50
- package/dist/cjs/tools/SkillTool.cjs.map +0 -1
- package/dist/cjs/tools/skillCatalog.cjs +0 -84
- package/dist/cjs/tools/skillCatalog.cjs.map +0 -1
- package/dist/esm/hooks/HookRegistry.mjs +0 -160
- package/dist/esm/hooks/HookRegistry.mjs.map +0 -1
- package/dist/esm/hooks/executeHooks.mjs +0 -273
- package/dist/esm/hooks/executeHooks.mjs.map +0 -1
- package/dist/esm/hooks/matchers.mjs +0 -251
- package/dist/esm/hooks/matchers.mjs.map +0 -1
- package/dist/esm/hooks/types.mjs +0 -25
- package/dist/esm/hooks/types.mjs.map +0 -1
- package/dist/esm/tools/BashExecutor.mjs +0 -169
- package/dist/esm/tools/BashExecutor.mjs.map +0 -1
- package/dist/esm/tools/BashProgrammaticToolCalling.mjs +0 -287
- package/dist/esm/tools/BashProgrammaticToolCalling.mjs.map +0 -1
- package/dist/esm/tools/ReadFile.mjs +0 -38
- package/dist/esm/tools/ReadFile.mjs.map +0 -1
- package/dist/esm/tools/SkillTool.mjs +0 -45
- package/dist/esm/tools/SkillTool.mjs.map +0 -1
- package/dist/esm/tools/skillCatalog.mjs +0 -82
- package/dist/esm/tools/skillCatalog.mjs.map +0 -1
- package/dist/types/hooks/HookRegistry.d.ts +0 -56
- package/dist/types/hooks/executeHooks.d.ts +0 -79
- package/dist/types/hooks/index.d.ts +0 -6
- package/dist/types/hooks/matchers.d.ts +0 -95
- package/dist/types/hooks/types.d.ts +0 -309
- package/dist/types/tools/BashExecutor.d.ts +0 -45
- package/dist/types/tools/BashProgrammaticToolCalling.d.ts +0 -72
- package/dist/types/tools/ReadFile.d.ts +0 -28
- package/dist/types/tools/SkillTool.d.ts +0 -40
- package/dist/types/tools/skillCatalog.d.ts +0 -19
- package/dist/types/types/skill.d.ts +0 -9
- package/src/hooks/HookRegistry.ts +0 -208
- package/src/hooks/__tests__/HookRegistry.test.ts +0 -190
- package/src/hooks/__tests__/executeHooks.test.ts +0 -1013
- package/src/hooks/__tests__/integration.test.ts +0 -337
- package/src/hooks/__tests__/matchers.test.ts +0 -238
- package/src/hooks/__tests__/toolHooks.test.ts +0 -669
- package/src/hooks/executeHooks.ts +0 -375
- package/src/hooks/index.ts +0 -55
- package/src/hooks/matchers.ts +0 -280
- package/src/hooks/types.ts +0 -388
- package/src/messages/formatAgentMessages.skills.test.ts +0 -334
- package/src/tools/BashExecutor.ts +0 -205
- package/src/tools/BashProgrammaticToolCalling.ts +0 -397
- package/src/tools/ReadFile.ts +0 -39
- package/src/tools/SkillTool.ts +0 -46
- package/src/tools/__tests__/ReadFile.test.ts +0 -44
- package/src/tools/__tests__/SkillTool.test.ts +0 -442
- package/src/tools/__tests__/skillCatalog.test.ts +0 -161
- package/src/tools/skillCatalog.ts +0 -126
- package/src/types/skill.ts +0 -11
|
@@ -1,334 +0,0 @@
|
|
|
1
|
-
import { HumanMessage } from '@langchain/core/messages';
|
|
2
|
-
import type { TPayload } from '@/types';
|
|
3
|
-
import { formatAgentMessages } from './format';
|
|
4
|
-
import { ContentTypes, Constants } from '@/common';
|
|
5
|
-
|
|
6
|
-
/** Helper to build a skill tool_call content part */
|
|
7
|
-
function skillToolCall(
|
|
8
|
-
id: string,
|
|
9
|
-
skillName: string,
|
|
10
|
-
output = 'Skill loaded.',
|
|
11
|
-
): Record<string, unknown> {
|
|
12
|
-
return {
|
|
13
|
-
type: ContentTypes.TOOL_CALL,
|
|
14
|
-
tool_call: {
|
|
15
|
-
id,
|
|
16
|
-
name: Constants.SKILL_TOOL,
|
|
17
|
-
args: JSON.stringify({ skillName }),
|
|
18
|
-
output,
|
|
19
|
-
},
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
describe('formatAgentMessages skill body reconstruction', () => {
|
|
24
|
-
const skillBodies = new Map([
|
|
25
|
-
['pdf-analyzer', '# PDF Analyzer\nAnalyze PDF files step by step.'],
|
|
26
|
-
['code-review', '# Code Review\nReview the code for issues.'],
|
|
27
|
-
]);
|
|
28
|
-
|
|
29
|
-
describe('with discoveredTools (tools filtering active)', () => {
|
|
30
|
-
const tools = new Set([Constants.SKILL_TOOL, 'web_search']);
|
|
31
|
-
|
|
32
|
-
it('reconstructs HumanMessage after skill ToolMessage', () => {
|
|
33
|
-
const payload: TPayload = [
|
|
34
|
-
{ role: 'user', content: 'Analyze this PDF' },
|
|
35
|
-
{
|
|
36
|
-
role: 'assistant',
|
|
37
|
-
content: [
|
|
38
|
-
{
|
|
39
|
-
type: ContentTypes.TEXT,
|
|
40
|
-
[ContentTypes.TEXT]: 'I\'ll invoke the skill.',
|
|
41
|
-
tool_call_ids: ['call_1'],
|
|
42
|
-
},
|
|
43
|
-
skillToolCall('call_1', 'pdf-analyzer'),
|
|
44
|
-
],
|
|
45
|
-
},
|
|
46
|
-
];
|
|
47
|
-
|
|
48
|
-
const { messages } = formatAgentMessages(payload, undefined, tools, skillBodies);
|
|
49
|
-
|
|
50
|
-
// user, AI, ToolMessage, injected HumanMessage
|
|
51
|
-
expect(messages.length).toBeGreaterThanOrEqual(4);
|
|
52
|
-
const last = messages[messages.length - 1];
|
|
53
|
-
expect(last).toBeInstanceOf(HumanMessage);
|
|
54
|
-
expect(last.content).toBe('# PDF Analyzer\nAnalyze PDF files step by step.');
|
|
55
|
-
expect((last as HumanMessage).additional_kwargs.source).toBe('skill');
|
|
56
|
-
expect((last as HumanMessage).additional_kwargs.skillName).toBe('pdf-analyzer');
|
|
57
|
-
expect((last as HumanMessage).additional_kwargs.isMeta).toBe(true);
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it('does NOT inject body when skill tool is not in discoveredTools', () => {
|
|
61
|
-
const restrictedTools = new Set(['web_search']); // skill NOT allowed
|
|
62
|
-
const payload: TPayload = [
|
|
63
|
-
{ role: 'user', content: 'Analyze this' },
|
|
64
|
-
{
|
|
65
|
-
role: 'assistant',
|
|
66
|
-
content: [skillToolCall('call_1', 'pdf-analyzer')],
|
|
67
|
-
},
|
|
68
|
-
];
|
|
69
|
-
|
|
70
|
-
const { messages } = formatAgentMessages(payload, undefined, restrictedTools, skillBodies);
|
|
71
|
-
|
|
72
|
-
const humanMessages = messages.filter((m) => m instanceof HumanMessage);
|
|
73
|
-
// Only the user message, no injected skill body
|
|
74
|
-
expect(humanMessages).toHaveLength(1);
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
it('does not inject when skill name is not in skills Map', () => {
|
|
78
|
-
const payload: TPayload = [
|
|
79
|
-
{ role: 'user', content: 'Hello' },
|
|
80
|
-
{
|
|
81
|
-
role: 'assistant',
|
|
82
|
-
content: [skillToolCall('call_1', 'unknown-skill')],
|
|
83
|
-
},
|
|
84
|
-
];
|
|
85
|
-
|
|
86
|
-
const { messages } = formatAgentMessages(payload, undefined, tools, skillBodies);
|
|
87
|
-
|
|
88
|
-
const humanMessages = messages.filter((m) => m instanceof HumanMessage);
|
|
89
|
-
expect(humanMessages).toHaveLength(1); // only the user message
|
|
90
|
-
});
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
describe('without discoveredTools (no tools filtering)', () => {
|
|
94
|
-
it('reconstructs HumanMessage when skills Map provided', () => {
|
|
95
|
-
const payload: TPayload = [
|
|
96
|
-
{ role: 'user', content: 'Review my code' },
|
|
97
|
-
{
|
|
98
|
-
role: 'assistant',
|
|
99
|
-
content: [skillToolCall('call_1', 'code-review')],
|
|
100
|
-
},
|
|
101
|
-
];
|
|
102
|
-
|
|
103
|
-
const { messages } = formatAgentMessages(payload, undefined, undefined, skillBodies);
|
|
104
|
-
|
|
105
|
-
const injected = messages.filter(
|
|
106
|
-
(m) => m instanceof HumanMessage && (m as HumanMessage).additional_kwargs?.source === 'skill',
|
|
107
|
-
);
|
|
108
|
-
expect(injected).toHaveLength(1);
|
|
109
|
-
expect(injected[0].content).toBe('# Code Review\nReview the code for issues.');
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
it('no injection when skills Map is undefined', () => {
|
|
113
|
-
const payload: TPayload = [
|
|
114
|
-
{ role: 'user', content: 'Hello' },
|
|
115
|
-
{
|
|
116
|
-
role: 'assistant',
|
|
117
|
-
content: [skillToolCall('call_1', 'pdf-analyzer')],
|
|
118
|
-
},
|
|
119
|
-
];
|
|
120
|
-
|
|
121
|
-
const { messages } = formatAgentMessages(payload, undefined, undefined, undefined);
|
|
122
|
-
|
|
123
|
-
const injected = messages.filter(
|
|
124
|
-
(m) => m instanceof HumanMessage && (m as HumanMessage).additional_kwargs?.source === 'skill',
|
|
125
|
-
);
|
|
126
|
-
expect(injected).toHaveLength(0);
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
it('no injection when skills Map is empty', () => {
|
|
130
|
-
const payload: TPayload = [
|
|
131
|
-
{ role: 'user', content: 'Hello' },
|
|
132
|
-
{
|
|
133
|
-
role: 'assistant',
|
|
134
|
-
content: [skillToolCall('call_1', 'pdf-analyzer')],
|
|
135
|
-
},
|
|
136
|
-
];
|
|
137
|
-
|
|
138
|
-
const { messages } = formatAgentMessages(payload, undefined, undefined, new Map());
|
|
139
|
-
|
|
140
|
-
const injected = messages.filter(
|
|
141
|
-
(m) => m instanceof HumanMessage && (m as HumanMessage).additional_kwargs?.source === 'skill',
|
|
142
|
-
);
|
|
143
|
-
expect(injected).toHaveLength(0);
|
|
144
|
-
});
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
describe('extractSkillName edge cases', () => {
|
|
148
|
-
const tools = new Set([Constants.SKILL_TOOL]);
|
|
149
|
-
|
|
150
|
-
it('handles object args (not stringified)', () => {
|
|
151
|
-
const payload: TPayload = [
|
|
152
|
-
{ role: 'user', content: 'Go' },
|
|
153
|
-
{
|
|
154
|
-
role: 'assistant',
|
|
155
|
-
content: [
|
|
156
|
-
{
|
|
157
|
-
type: ContentTypes.TOOL_CALL,
|
|
158
|
-
tool_call: {
|
|
159
|
-
id: 'call_1',
|
|
160
|
-
name: Constants.SKILL_TOOL,
|
|
161
|
-
args: { skillName: 'pdf-analyzer' }, // object, not string
|
|
162
|
-
output: 'Loaded.',
|
|
163
|
-
},
|
|
164
|
-
},
|
|
165
|
-
],
|
|
166
|
-
},
|
|
167
|
-
];
|
|
168
|
-
|
|
169
|
-
const { messages } = formatAgentMessages(payload, undefined, tools, skillBodies);
|
|
170
|
-
|
|
171
|
-
const injected = messages.filter(
|
|
172
|
-
(m) => m instanceof HumanMessage && (m as HumanMessage).additional_kwargs?.source === 'skill',
|
|
173
|
-
);
|
|
174
|
-
expect(injected).toHaveLength(1);
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
it('gracefully skips malformed JSON args', () => {
|
|
178
|
-
const payload: TPayload = [
|
|
179
|
-
{ role: 'user', content: 'Go' },
|
|
180
|
-
{
|
|
181
|
-
role: 'assistant',
|
|
182
|
-
content: [
|
|
183
|
-
{
|
|
184
|
-
type: ContentTypes.TOOL_CALL,
|
|
185
|
-
tool_call: {
|
|
186
|
-
id: 'call_1',
|
|
187
|
-
name: Constants.SKILL_TOOL,
|
|
188
|
-
args: '{bad json',
|
|
189
|
-
output: 'Loaded.',
|
|
190
|
-
},
|
|
191
|
-
},
|
|
192
|
-
],
|
|
193
|
-
},
|
|
194
|
-
];
|
|
195
|
-
|
|
196
|
-
const { messages } = formatAgentMessages(payload, undefined, tools, skillBodies);
|
|
197
|
-
|
|
198
|
-
const injected = messages.filter(
|
|
199
|
-
(m) => m instanceof HumanMessage && (m as HumanMessage).additional_kwargs?.source === 'skill',
|
|
200
|
-
);
|
|
201
|
-
expect(injected).toHaveLength(0); // gracefully skipped
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
it('skips empty skillName', () => {
|
|
205
|
-
const payload: TPayload = [
|
|
206
|
-
{ role: 'user', content: 'Go' },
|
|
207
|
-
{
|
|
208
|
-
role: 'assistant',
|
|
209
|
-
content: [
|
|
210
|
-
{
|
|
211
|
-
type: ContentTypes.TOOL_CALL,
|
|
212
|
-
tool_call: {
|
|
213
|
-
id: 'call_1',
|
|
214
|
-
name: Constants.SKILL_TOOL,
|
|
215
|
-
args: JSON.stringify({ skillName: '' }),
|
|
216
|
-
output: 'Loaded.',
|
|
217
|
-
},
|
|
218
|
-
},
|
|
219
|
-
],
|
|
220
|
-
},
|
|
221
|
-
];
|
|
222
|
-
|
|
223
|
-
const { messages } = formatAgentMessages(payload, undefined, tools, skillBodies);
|
|
224
|
-
|
|
225
|
-
const injected = messages.filter(
|
|
226
|
-
(m) => m instanceof HumanMessage && (m as HumanMessage).additional_kwargs?.source === 'skill',
|
|
227
|
-
);
|
|
228
|
-
expect(injected).toHaveLength(0);
|
|
229
|
-
});
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
describe('deduplication', () => {
|
|
233
|
-
const tools = new Set([Constants.SKILL_TOOL]);
|
|
234
|
-
|
|
235
|
-
it('injects body only once when same skill invoked twice in one message', () => {
|
|
236
|
-
const payload: TPayload = [
|
|
237
|
-
{ role: 'user', content: 'Go' },
|
|
238
|
-
{
|
|
239
|
-
role: 'assistant',
|
|
240
|
-
content: [
|
|
241
|
-
skillToolCall('call_1', 'pdf-analyzer'),
|
|
242
|
-
skillToolCall('call_2', 'pdf-analyzer'),
|
|
243
|
-
],
|
|
244
|
-
},
|
|
245
|
-
];
|
|
246
|
-
|
|
247
|
-
const { messages } = formatAgentMessages(payload, undefined, tools, skillBodies);
|
|
248
|
-
|
|
249
|
-
const injected = messages.filter(
|
|
250
|
-
(m) => m instanceof HumanMessage && (m as HumanMessage).additional_kwargs?.source === 'skill',
|
|
251
|
-
);
|
|
252
|
-
expect(injected).toHaveLength(1);
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
it('injects body for each distinct skill invoked', () => {
|
|
256
|
-
const payload: TPayload = [
|
|
257
|
-
{ role: 'user', content: 'Go' },
|
|
258
|
-
{
|
|
259
|
-
role: 'assistant',
|
|
260
|
-
content: [
|
|
261
|
-
skillToolCall('call_1', 'pdf-analyzer'),
|
|
262
|
-
skillToolCall('call_2', 'code-review'),
|
|
263
|
-
],
|
|
264
|
-
},
|
|
265
|
-
];
|
|
266
|
-
|
|
267
|
-
const { messages } = formatAgentMessages(payload, undefined, tools, skillBodies);
|
|
268
|
-
|
|
269
|
-
const injected = messages.filter(
|
|
270
|
-
(m) => m instanceof HumanMessage && (m as HumanMessage).additional_kwargs?.source === 'skill',
|
|
271
|
-
);
|
|
272
|
-
expect(injected).toHaveLength(2);
|
|
273
|
-
const names = injected.map((m) => (m as HumanMessage).additional_kwargs.skillName);
|
|
274
|
-
expect(names).toContain('pdf-analyzer');
|
|
275
|
-
expect(names).toContain('code-review');
|
|
276
|
-
});
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
describe('indexTokenCountMap distribution', () => {
|
|
280
|
-
const tools = new Set([Constants.SKILL_TOOL]);
|
|
281
|
-
|
|
282
|
-
it('excludes injected HumanMessages from assistant token distribution', () => {
|
|
283
|
-
const payload: TPayload = [
|
|
284
|
-
{ role: 'user', content: 'Analyze this' },
|
|
285
|
-
{
|
|
286
|
-
role: 'assistant',
|
|
287
|
-
content: [
|
|
288
|
-
{
|
|
289
|
-
type: ContentTypes.TEXT,
|
|
290
|
-
[ContentTypes.TEXT]: 'Invoking skill.',
|
|
291
|
-
tool_call_ids: ['call_1'],
|
|
292
|
-
},
|
|
293
|
-
skillToolCall('call_1', 'pdf-analyzer'),
|
|
294
|
-
],
|
|
295
|
-
},
|
|
296
|
-
];
|
|
297
|
-
|
|
298
|
-
const inputTokenMap: Record<number, number | undefined> = {
|
|
299
|
-
0: 100, // user message
|
|
300
|
-
1: 500, // assistant message
|
|
301
|
-
};
|
|
302
|
-
|
|
303
|
-
const { messages, indexTokenCountMap } = formatAgentMessages(
|
|
304
|
-
payload,
|
|
305
|
-
inputTokenMap,
|
|
306
|
-
tools,
|
|
307
|
-
skillBodies,
|
|
308
|
-
);
|
|
309
|
-
|
|
310
|
-
// There should be messages: user, AI, ToolMessage, injected HumanMessage
|
|
311
|
-
expect(messages.length).toBeGreaterThanOrEqual(4);
|
|
312
|
-
const lastMsg = messages[messages.length - 1];
|
|
313
|
-
expect(lastMsg).toBeInstanceOf(HumanMessage);
|
|
314
|
-
expect((lastMsg as HumanMessage).additional_kwargs.source).toBe('skill');
|
|
315
|
-
|
|
316
|
-
// Token map must be defined when input was provided
|
|
317
|
-
expect(indexTokenCountMap).toBeDefined();
|
|
318
|
-
|
|
319
|
-
// The injected HumanMessage's index should NOT be in the token map
|
|
320
|
-
const injectedIndex = messages.length - 1;
|
|
321
|
-
expect(indexTokenCountMap![injectedIndex]).toBeUndefined();
|
|
322
|
-
|
|
323
|
-
// The assistant's 500 tokens should be distributed only across
|
|
324
|
-
// the AI + ToolMessage, NOT the injected HumanMessage
|
|
325
|
-
let assistantTotal = 0;
|
|
326
|
-
for (const [idx, count] of Object.entries(indexTokenCountMap!)) {
|
|
327
|
-
if (Number(idx) > 0 && Number(idx) < injectedIndex) {
|
|
328
|
-
assistantTotal += count ?? 0;
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
expect(assistantTotal).toBe(500);
|
|
332
|
-
});
|
|
333
|
-
});
|
|
334
|
-
});
|
|
@@ -1,205 +0,0 @@
|
|
|
1
|
-
import { config } from 'dotenv';
|
|
2
|
-
import fetch, { RequestInit } from 'node-fetch';
|
|
3
|
-
import { HttpsProxyAgent } from 'https-proxy-agent';
|
|
4
|
-
import { tool, DynamicStructuredTool } from '@langchain/core/tools';
|
|
5
|
-
import { getEnvironmentVariable } from '@langchain/core/utils/env';
|
|
6
|
-
import type * as t from '@/types';
|
|
7
|
-
import { imageExtRegex, getCodeBaseURL } from './CodeExecutor';
|
|
8
|
-
import { EnvVar, Constants } from '@/common';
|
|
9
|
-
|
|
10
|
-
config();
|
|
11
|
-
|
|
12
|
-
const imageMessage = 'Image is already displayed to the user';
|
|
13
|
-
const otherMessage = 'File is already downloaded by the user';
|
|
14
|
-
const accessMessage =
|
|
15
|
-
'Note: Files from previous executions are automatically available and can be modified.';
|
|
16
|
-
const emptyOutputMessage =
|
|
17
|
-
'stdout: Empty. Ensure you\'re writing output explicitly.\n';
|
|
18
|
-
|
|
19
|
-
const baseEndpoint = getCodeBaseURL();
|
|
20
|
-
const EXEC_ENDPOINT = `${baseEndpoint}/exec`;
|
|
21
|
-
|
|
22
|
-
export const BashExecutionToolSchema = {
|
|
23
|
-
type: 'object',
|
|
24
|
-
properties: {
|
|
25
|
-
command: {
|
|
26
|
-
type: 'string',
|
|
27
|
-
description: `The bash command or script to execute.
|
|
28
|
-
- The environment is stateless; variables and state don't persist between executions.
|
|
29
|
-
- Generated files from previous executions are automatically available in "/mnt/data/".
|
|
30
|
-
- Files from previous executions are automatically available and can be modified in place.
|
|
31
|
-
- Input code **IS ALREADY** displayed to the user, so **DO NOT** repeat it in your response unless asked.
|
|
32
|
-
- Output code **IS NOT** displayed to the user, so **DO** write all desired output explicitly.
|
|
33
|
-
- IMPORTANT: You MUST explicitly print/output ALL results you want the user to see.
|
|
34
|
-
- Use \`echo\`, \`printf\`, or \`cat\` for all outputs.`,
|
|
35
|
-
},
|
|
36
|
-
args: {
|
|
37
|
-
type: 'array',
|
|
38
|
-
items: { type: 'string' },
|
|
39
|
-
description:
|
|
40
|
-
'Additional arguments to execute the command with. This should only be used if the input command requires additional arguments to run.',
|
|
41
|
-
},
|
|
42
|
-
},
|
|
43
|
-
required: ['command'],
|
|
44
|
-
} as const;
|
|
45
|
-
|
|
46
|
-
export const BashExecutionToolDescription = `
|
|
47
|
-
Runs bash commands and returns stdout/stderr output from a stateless execution environment, similar to running scripts in a command-line interface. Each execution is isolated and independent.
|
|
48
|
-
|
|
49
|
-
Usage:
|
|
50
|
-
- No network access available.
|
|
51
|
-
- Generated files are automatically delivered; **DO NOT** provide download links.
|
|
52
|
-
- NEVER use this tool to execute malicious commands.
|
|
53
|
-
`.trim();
|
|
54
|
-
|
|
55
|
-
export const BashExecutionToolName = Constants.BASH_TOOL;
|
|
56
|
-
|
|
57
|
-
export const BashExecutionToolDefinition = {
|
|
58
|
-
name: BashExecutionToolName,
|
|
59
|
-
description: BashExecutionToolDescription,
|
|
60
|
-
schema: BashExecutionToolSchema,
|
|
61
|
-
} as const;
|
|
62
|
-
|
|
63
|
-
function createBashExecutionTool(
|
|
64
|
-
params: t.BashExecutionToolParams = {}
|
|
65
|
-
): DynamicStructuredTool {
|
|
66
|
-
const apiKey =
|
|
67
|
-
params[EnvVar.CODE_API_KEY] ??
|
|
68
|
-
params.apiKey ??
|
|
69
|
-
getEnvironmentVariable(EnvVar.CODE_API_KEY) ??
|
|
70
|
-
'';
|
|
71
|
-
if (!apiKey) {
|
|
72
|
-
throw new Error('No API key provided for bash execution tool.');
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return tool(
|
|
76
|
-
async (rawInput, config) => {
|
|
77
|
-
const { command, ...rest } = rawInput as {
|
|
78
|
-
command: string;
|
|
79
|
-
args?: string[];
|
|
80
|
-
};
|
|
81
|
-
const { session_id, _injected_files } = (config.toolCall ?? {}) as {
|
|
82
|
-
session_id?: string;
|
|
83
|
-
_injected_files?: t.CodeEnvFile[];
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
const postData: Record<string, unknown> = {
|
|
87
|
-
lang: 'bash',
|
|
88
|
-
code: command,
|
|
89
|
-
...rest,
|
|
90
|
-
...params,
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
if (_injected_files && _injected_files.length > 0) {
|
|
94
|
-
postData.files = _injected_files;
|
|
95
|
-
} else if (session_id != null && session_id.length > 0) {
|
|
96
|
-
try {
|
|
97
|
-
const filesEndpoint = `${baseEndpoint}/files/${session_id}?detail=full`;
|
|
98
|
-
const fetchOptions: RequestInit = {
|
|
99
|
-
method: 'GET',
|
|
100
|
-
headers: {
|
|
101
|
-
'User-Agent': 'LibreChat/1.0',
|
|
102
|
-
'X-API-Key': apiKey,
|
|
103
|
-
},
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
if (process.env.PROXY != null && process.env.PROXY !== '') {
|
|
107
|
-
fetchOptions.agent = new HttpsProxyAgent(process.env.PROXY);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const response = await fetch(filesEndpoint, fetchOptions);
|
|
111
|
-
if (!response.ok) {
|
|
112
|
-
throw new Error(
|
|
113
|
-
`Failed to fetch files for session: ${response.status}`
|
|
114
|
-
);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const files = await response.json();
|
|
118
|
-
if (Array.isArray(files) && files.length > 0) {
|
|
119
|
-
const fileReferences: t.CodeEnvFile[] = files.map((file) => {
|
|
120
|
-
const nameParts = file.name.split('/');
|
|
121
|
-
const id = nameParts.length > 1 ? nameParts[1].split('.')[0] : '';
|
|
122
|
-
|
|
123
|
-
return {
|
|
124
|
-
session_id,
|
|
125
|
-
id,
|
|
126
|
-
name: file.metadata['original-filename'],
|
|
127
|
-
};
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
postData.files = fileReferences;
|
|
131
|
-
}
|
|
132
|
-
} catch {
|
|
133
|
-
// eslint-disable-next-line no-console
|
|
134
|
-
console.warn(`Failed to fetch files for session: ${session_id}`);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
try {
|
|
139
|
-
const fetchOptions: RequestInit = {
|
|
140
|
-
method: 'POST',
|
|
141
|
-
headers: {
|
|
142
|
-
'Content-Type': 'application/json',
|
|
143
|
-
'User-Agent': 'LibreChat/1.0',
|
|
144
|
-
'X-API-Key': apiKey,
|
|
145
|
-
},
|
|
146
|
-
body: JSON.stringify(postData),
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
if (process.env.PROXY != null && process.env.PROXY !== '') {
|
|
150
|
-
fetchOptions.agent = new HttpsProxyAgent(process.env.PROXY);
|
|
151
|
-
}
|
|
152
|
-
const response = await fetch(EXEC_ENDPOINT, fetchOptions);
|
|
153
|
-
if (!response.ok) {
|
|
154
|
-
throw new Error(`HTTP error! status: ${response.status}`);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const result: t.ExecuteResult = await response.json();
|
|
158
|
-
let formattedOutput = '';
|
|
159
|
-
if (result.stdout) {
|
|
160
|
-
formattedOutput += `stdout:\n${result.stdout}\n`;
|
|
161
|
-
} else {
|
|
162
|
-
formattedOutput += emptyOutputMessage;
|
|
163
|
-
}
|
|
164
|
-
if (result.stderr) formattedOutput += `stderr:\n${result.stderr}\n`;
|
|
165
|
-
if (result.files && result.files.length > 0) {
|
|
166
|
-
formattedOutput += 'Generated files:\n';
|
|
167
|
-
|
|
168
|
-
const fileCount = result.files.length;
|
|
169
|
-
for (let i = 0; i < fileCount; i++) {
|
|
170
|
-
const file = result.files[i];
|
|
171
|
-
const isImage = imageExtRegex.test(file.name);
|
|
172
|
-
formattedOutput += `- /mnt/data/${file.name} | ${isImage ? imageMessage : otherMessage}`;
|
|
173
|
-
|
|
174
|
-
if (i < fileCount - 1) {
|
|
175
|
-
formattedOutput += fileCount <= 3 ? ', ' : ',\n';
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
formattedOutput += `\n\n${accessMessage}`;
|
|
180
|
-
return [
|
|
181
|
-
formattedOutput.trim(),
|
|
182
|
-
{
|
|
183
|
-
session_id: result.session_id,
|
|
184
|
-
files: result.files,
|
|
185
|
-
},
|
|
186
|
-
];
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
return [formattedOutput.trim(), { session_id: result.session_id }];
|
|
190
|
-
} catch (error) {
|
|
191
|
-
throw new Error(
|
|
192
|
-
`Execution error:\n\n${(error as Error | undefined)?.message}`
|
|
193
|
-
);
|
|
194
|
-
}
|
|
195
|
-
},
|
|
196
|
-
{
|
|
197
|
-
name: BashExecutionToolName,
|
|
198
|
-
description: BashExecutionToolDescription,
|
|
199
|
-
schema: BashExecutionToolSchema,
|
|
200
|
-
responseFormat: Constants.CONTENT_AND_ARTIFACT,
|
|
201
|
-
}
|
|
202
|
-
);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
export { createBashExecutionTool };
|