@librechat/agents 3.1.67-dev.4 → 3.1.68
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 +3 -23
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/common/enum.cjs +0 -16
- package/dist/cjs/common/enum.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +0 -91
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/graphs/MultiAgentGraph.cjs +36 -0
- package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
- package/dist/cjs/main.cjs +1 -53
- 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/summarization/index.cjs +41 -0
- package/dist/cjs/summarization/index.cjs.map +1 -1
- package/dist/cjs/summarization/node.cjs +121 -63
- package/dist/cjs/summarization/node.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 +3 -23
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/common/enum.mjs +1 -15
- package/dist/esm/common/enum.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +0 -91
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/graphs/MultiAgentGraph.mjs +36 -0
- package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
- package/dist/esm/main.mjs +2 -13
- 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/summarization/index.mjs +41 -1
- package/dist/esm/summarization/index.mjs.map +1 -1
- package/dist/esm/summarization/node.mjs +121 -63
- package/dist/esm/summarization/node.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 +0 -6
- package/dist/types/common/enum.d.ts +1 -10
- package/dist/types/graphs/Graph.d.ts +0 -2
- package/dist/types/graphs/MultiAgentGraph.d.ts +12 -0
- package/dist/types/index.d.ts +0 -8
- package/dist/types/messages/format.d.ts +1 -2
- package/dist/types/run.d.ts +0 -1
- package/dist/types/summarization/index.d.ts +2 -0
- package/dist/types/summarization/node.d.ts +0 -2
- package/dist/types/tools/ToolNode.d.ts +2 -24
- package/dist/types/types/graph.d.ts +2 -61
- package/dist/types/types/index.d.ts +0 -1
- package/dist/types/types/run.d.ts +0 -20
- package/dist/types/types/tools.d.ts +1 -38
- package/package.json +1 -5
- package/src/agents/AgentContext.ts +2 -26
- package/src/common/enum.ts +0 -15
- package/src/graphs/Graph.ts +0 -113
- package/src/graphs/MultiAgentGraph.ts +39 -0
- package/src/graphs/__tests__/MultiAgentGraph.test.ts +91 -0
- package/src/index.ts +0 -10
- package/src/messages/format.ts +4 -74
- package/src/run.ts +0 -126
- package/src/summarization/__tests__/node.test.ts +42 -0
- package/src/summarization/__tests__/trigger.test.ts +100 -1
- package/src/summarization/index.ts +47 -0
- package/src/summarization/node.ts +149 -77
- package/src/tools/ToolNode.ts +169 -391
- package/src/tools/__tests__/ToolNode.session.test.ts +12 -12
- package/src/types/graph.ts +1 -80
- package/src/types/index.ts +0 -1
- 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/SubagentTool.cjs +0 -92
- package/dist/cjs/tools/SubagentTool.cjs.map +0 -1
- package/dist/cjs/tools/skillCatalog.cjs +0 -84
- package/dist/cjs/tools/skillCatalog.cjs.map +0 -1
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs +0 -511
- package/dist/cjs/tools/subagent/SubagentExecutor.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/SubagentTool.mjs +0 -85
- package/dist/esm/tools/SubagentTool.mjs.map +0 -1
- package/dist/esm/tools/skillCatalog.mjs +0 -82
- package/dist/esm/tools/skillCatalog.mjs.map +0 -1
- package/dist/esm/tools/subagent/SubagentExecutor.mjs +0 -505
- package/dist/esm/tools/subagent/SubagentExecutor.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 -320
- 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/SubagentTool.d.ts +0 -36
- package/dist/types/tools/skillCatalog.d.ts +0 -19
- package/dist/types/tools/subagent/SubagentExecutor.d.ts +0 -137
- package/dist/types/tools/subagent/index.d.ts +0 -2
- 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__/compactHooks.test.ts +0 -214
- 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 -57
- package/src/hooks/matchers.ts +0 -280
- package/src/hooks/types.ts +0 -404
- package/src/messages/formatAgentMessages.skills.test.ts +0 -334
- package/src/scripts/multi-agent-subagent.ts +0 -246
- package/src/scripts/subagent-event-driven-debug.ts +0 -190
- package/src/scripts/subagent-tools-debug.ts +0 -160
- package/src/specs/subagent.test.ts +0 -305
- 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/SubagentTool.ts +0 -100
- package/src/tools/__tests__/ReadFile.test.ts +0 -44
- package/src/tools/__tests__/SkillTool.test.ts +0 -442
- package/src/tools/__tests__/SubagentExecutor.test.ts +0 -1148
- package/src/tools/__tests__/SubagentTool.test.ts +0 -149
- package/src/tools/__tests__/skillCatalog.test.ts +0 -161
- package/src/tools/__tests__/subagentHooks.test.ts +0 -215
- package/src/tools/skillCatalog.ts +0 -126
- package/src/tools/subagent/SubagentExecutor.ts +0 -676
- package/src/tools/subagent/index.ts +0 -13
- 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,246 +0,0 @@
|
|
|
1
|
-
import { config } from 'dotenv';
|
|
2
|
-
config();
|
|
3
|
-
|
|
4
|
-
import { HumanMessage } from '@langchain/core/messages';
|
|
5
|
-
import type { BaseMessage } from '@langchain/core/messages';
|
|
6
|
-
import type * as t from '@/types';
|
|
7
|
-
import { ChatModelStreamHandler, createContentAggregator } from '@/stream';
|
|
8
|
-
import { ToolEndHandler, ModelEndHandler } from '@/events';
|
|
9
|
-
import { Providers, GraphEvents, Constants } from '@/common';
|
|
10
|
-
import { Run } from '@/run';
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Manual verification script for the subagent primitive.
|
|
14
|
-
*
|
|
15
|
-
* Configures a supervisor agent with two subagent types (researcher, coder),
|
|
16
|
-
* sends a query, and confirms:
|
|
17
|
-
* 1. The parent agent delegates to a subagent via the `subagent` tool
|
|
18
|
-
* 2. The child executes with isolated context (fresh message history)
|
|
19
|
-
* 3. Only the filtered text result returns to the parent
|
|
20
|
-
* 4. The parent incorporates the result and responds
|
|
21
|
-
*
|
|
22
|
-
* Usage:
|
|
23
|
-
* OPENAI_API_KEY=... npx ts-node -r tsconfig-paths/register src/scripts/multi-agent-subagent.ts
|
|
24
|
-
*
|
|
25
|
-
* Or with Anthropic:
|
|
26
|
-
* ANTHROPIC_API_KEY=... npx ts-node -r tsconfig-paths/register src/scripts/multi-agent-subagent.ts --provider anthropic
|
|
27
|
-
*/
|
|
28
|
-
|
|
29
|
-
const useAnthropic =
|
|
30
|
-
process.argv.includes('--provider') &&
|
|
31
|
-
process.argv[process.argv.indexOf('--provider') + 1] === 'anthropic';
|
|
32
|
-
|
|
33
|
-
const provider = useAnthropic ? Providers.ANTHROPIC : Providers.OPENAI;
|
|
34
|
-
const apiKey = useAnthropic
|
|
35
|
-
? process.env.ANTHROPIC_API_KEY
|
|
36
|
-
: process.env.OPENAI_API_KEY;
|
|
37
|
-
const modelName = useAnthropic ? 'claude-sonnet-4-20250514' : 'gpt-5.4';
|
|
38
|
-
|
|
39
|
-
if (!apiKey) {
|
|
40
|
-
console.error(
|
|
41
|
-
`Missing ${useAnthropic ? 'ANTHROPIC_API_KEY' : 'OPENAI_API_KEY'} environment variable`
|
|
42
|
-
);
|
|
43
|
-
process.exit(1);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
async function testSubagentPrimitive() {
|
|
47
|
-
console.log('=== Subagent Primitive Manual Verification ===\n');
|
|
48
|
-
console.log(`Provider: ${provider}`);
|
|
49
|
-
console.log(`Model: ${modelName}\n`);
|
|
50
|
-
|
|
51
|
-
const { aggregateContent } = createContentAggregator();
|
|
52
|
-
|
|
53
|
-
const parentAgent: t.AgentInputs = {
|
|
54
|
-
agentId: 'supervisor',
|
|
55
|
-
provider,
|
|
56
|
-
clientOptions: { modelName, apiKey },
|
|
57
|
-
instructions: `You are a supervisor agent. You have access to specialized subagents.
|
|
58
|
-
|
|
59
|
-
When the user asks a research question, delegate it to the "researcher" subagent.
|
|
60
|
-
When the user asks for code, delegate it to the "coder" subagent.
|
|
61
|
-
|
|
62
|
-
After receiving the subagent's result, synthesize it into a clear final answer for the user.
|
|
63
|
-
Always use a subagent for research or coding tasks — do not answer directly.`,
|
|
64
|
-
maxContextTokens: 16000,
|
|
65
|
-
subagentConfigs: [
|
|
66
|
-
{
|
|
67
|
-
type: 'researcher',
|
|
68
|
-
name: 'Research Specialist',
|
|
69
|
-
description:
|
|
70
|
-
'Researches topics and provides detailed summaries with sources.',
|
|
71
|
-
agentInputs: {
|
|
72
|
-
agentId: 'researcher',
|
|
73
|
-
provider,
|
|
74
|
-
clientOptions: { modelName, apiKey },
|
|
75
|
-
instructions: `You are a research specialist working in an isolated context.
|
|
76
|
-
You receive a single task description and must answer it thoroughly.
|
|
77
|
-
Be concise but comprehensive. Include key facts and details.`,
|
|
78
|
-
maxContextTokens: 8000,
|
|
79
|
-
},
|
|
80
|
-
},
|
|
81
|
-
{
|
|
82
|
-
type: 'coder',
|
|
83
|
-
name: 'Coding Specialist',
|
|
84
|
-
description:
|
|
85
|
-
'Writes, reviews, and explains code in any programming language.',
|
|
86
|
-
agentInputs: {
|
|
87
|
-
agentId: 'coder',
|
|
88
|
-
provider,
|
|
89
|
-
clientOptions: { modelName, apiKey },
|
|
90
|
-
instructions: `You are a coding specialist working in an isolated context.
|
|
91
|
-
You receive a single task description and must provide working code.
|
|
92
|
-
Include brief explanations. Use clean, idiomatic code.`,
|
|
93
|
-
maxContextTokens: 8000,
|
|
94
|
-
},
|
|
95
|
-
},
|
|
96
|
-
],
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
const customHandlers: Record<string, t.EventHandler> = {
|
|
100
|
-
[GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler(),
|
|
101
|
-
[GraphEvents.TOOL_END]: new ToolEndHandler(),
|
|
102
|
-
[GraphEvents.CHAT_MODEL_END]: new ModelEndHandler(),
|
|
103
|
-
[GraphEvents.ON_RUN_STEP_COMPLETED]: {
|
|
104
|
-
handle: (event: string, data: t.StreamEventData): void => {
|
|
105
|
-
aggregateContent({
|
|
106
|
-
event: event as GraphEvents,
|
|
107
|
-
data: data as t.RunStep,
|
|
108
|
-
});
|
|
109
|
-
},
|
|
110
|
-
},
|
|
111
|
-
[GraphEvents.ON_RUN_STEP]: {
|
|
112
|
-
handle: (event: string, data: t.StreamEventData): void => {
|
|
113
|
-
aggregateContent({
|
|
114
|
-
event: event as GraphEvents,
|
|
115
|
-
data: data as t.RunStep,
|
|
116
|
-
});
|
|
117
|
-
},
|
|
118
|
-
},
|
|
119
|
-
[GraphEvents.ON_RUN_STEP_DELTA]: {
|
|
120
|
-
handle: (event: string, data: t.StreamEventData): void => {
|
|
121
|
-
aggregateContent({
|
|
122
|
-
event: event as GraphEvents,
|
|
123
|
-
data: data as t.RunStepDeltaEvent,
|
|
124
|
-
});
|
|
125
|
-
},
|
|
126
|
-
},
|
|
127
|
-
[GraphEvents.ON_MESSAGE_DELTA]: {
|
|
128
|
-
handle: (event: string, data: t.StreamEventData): void => {
|
|
129
|
-
aggregateContent({
|
|
130
|
-
event: event as GraphEvents,
|
|
131
|
-
data: data as t.MessageDeltaEvent,
|
|
132
|
-
});
|
|
133
|
-
},
|
|
134
|
-
},
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
const run = await Run.create<t.IState>({
|
|
138
|
-
runId: `subagent-manual-${Date.now()}`,
|
|
139
|
-
graphConfig: {
|
|
140
|
-
type: 'standard',
|
|
141
|
-
agents: [parentAgent],
|
|
142
|
-
},
|
|
143
|
-
returnContent: true,
|
|
144
|
-
customHandlers,
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
console.log('--- Run created ---');
|
|
148
|
-
console.log(
|
|
149
|
-
`Subagent tool present: ${
|
|
150
|
-
(
|
|
151
|
-
(run.Graph as import('@/graphs/Graph').StandardGraph).agentContexts.get(
|
|
152
|
-
'supervisor'
|
|
153
|
-
)?.graphTools as t.GenericTool[] | undefined
|
|
154
|
-
)?.some((t) => 'name' in t && t.name === Constants.SUBAGENT) ?? false
|
|
155
|
-
}\n`
|
|
156
|
-
);
|
|
157
|
-
|
|
158
|
-
const conversationHistory: BaseMessage[] = [];
|
|
159
|
-
|
|
160
|
-
// Turn 1: Research question (should delegate to researcher subagent)
|
|
161
|
-
console.log('=== Turn 1: Research Question ===\n');
|
|
162
|
-
console.log(
|
|
163
|
-
'User: What are the three laws of thermodynamics? Explain briefly.\n'
|
|
164
|
-
);
|
|
165
|
-
|
|
166
|
-
const userMessage = new HumanMessage(
|
|
167
|
-
'What are the three laws of thermodynamics? Explain briefly.'
|
|
168
|
-
);
|
|
169
|
-
conversationHistory.push(userMessage);
|
|
170
|
-
|
|
171
|
-
const callerConfig = {
|
|
172
|
-
configurable: { thread_id: 'subagent-verify' },
|
|
173
|
-
streamMode: 'values' as const,
|
|
174
|
-
version: 'v2' as const,
|
|
175
|
-
};
|
|
176
|
-
|
|
177
|
-
console.log('--- Streaming response ---\n');
|
|
178
|
-
const result = await run.processStream(
|
|
179
|
-
{ messages: conversationHistory },
|
|
180
|
-
callerConfig
|
|
181
|
-
);
|
|
182
|
-
|
|
183
|
-
const runMessages = run.getRunMessages();
|
|
184
|
-
console.log('\n\n--- Run Messages ---\n');
|
|
185
|
-
|
|
186
|
-
if (runMessages) {
|
|
187
|
-
for (const msg of runMessages) {
|
|
188
|
-
const type = msg._getType();
|
|
189
|
-
if (type === 'tool') {
|
|
190
|
-
const name = 'name' in msg ? msg.name : 'unknown';
|
|
191
|
-
const rawContent =
|
|
192
|
-
typeof msg.content === 'string'
|
|
193
|
-
? msg.content
|
|
194
|
-
: JSON.stringify(msg.content);
|
|
195
|
-
const content = rawContent.slice(0, 200);
|
|
196
|
-
const truncated = rawContent.length > 200 ? '...' : '';
|
|
197
|
-
console.log(`[ToolMessage] name=${name}`);
|
|
198
|
-
console.log(` content: ${content}${truncated}\n`);
|
|
199
|
-
} else if (type === 'ai') {
|
|
200
|
-
const content =
|
|
201
|
-
typeof msg.content === 'string'
|
|
202
|
-
? msg.content.slice(0, 300)
|
|
203
|
-
: JSON.stringify(msg.content).slice(0, 300);
|
|
204
|
-
const toolCalls = 'tool_calls' in msg ? msg.tool_calls : undefined;
|
|
205
|
-
console.log(`[AIMessage]`);
|
|
206
|
-
if (toolCalls && Array.isArray(toolCalls) && toolCalls.length > 0) {
|
|
207
|
-
for (const tc of toolCalls) {
|
|
208
|
-
console.log(
|
|
209
|
-
` tool_call: ${tc.name}(${JSON.stringify(tc.args).slice(0, 100)}...)`
|
|
210
|
-
);
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
console.log(
|
|
214
|
-
` content: ${content}${content.length >= 300 ? '...' : ''}\n`
|
|
215
|
-
);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
const subagentToolMessages = runMessages.filter(
|
|
220
|
-
(msg) =>
|
|
221
|
-
msg._getType() === 'tool' &&
|
|
222
|
-
'name' in msg &&
|
|
223
|
-
msg.name === Constants.SUBAGENT
|
|
224
|
-
);
|
|
225
|
-
console.log(`\n--- Verification ---`);
|
|
226
|
-
console.log(`Subagent tool calls found: ${subagentToolMessages.length}`);
|
|
227
|
-
console.log(`Total run messages: ${runMessages.length}`);
|
|
228
|
-
console.log(`Result content parts: ${result?.length ?? 0}`);
|
|
229
|
-
|
|
230
|
-
if (subagentToolMessages.length > 0) {
|
|
231
|
-
console.log(
|
|
232
|
-
'\nSUCCESS: Subagent was invoked and returned a filtered result.'
|
|
233
|
-
);
|
|
234
|
-
console.log(
|
|
235
|
-
'The child context was isolated — only the final text came back.'
|
|
236
|
-
);
|
|
237
|
-
} else {
|
|
238
|
-
console.log('\nNOTE: No subagent tool calls detected.');
|
|
239
|
-
console.log('The LLM may have answered directly without delegating.');
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
console.log('\n=== Done ===');
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
testSubagentPrimitive().catch(console.error);
|