@librechat/agents 3.4.1 → 3.4.3
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.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +57 -33
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/graphs/MultiAgentGraph.cjs +81 -33
- package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
- package/dist/cjs/graphs/applyGraphRuntimeConfig.cjs +14 -0
- package/dist/cjs/graphs/applyGraphRuntimeConfig.cjs.map +1 -0
- package/dist/cjs/graphs/createGraph.cjs +15 -0
- package/dist/cjs/graphs/createGraph.cjs.map +1 -0
- package/dist/cjs/graphs/index.cjs +1 -0
- package/dist/cjs/hooks/createWorkspacePolicyHook.cjs +1 -1
- package/dist/cjs/main.cjs +20 -11
- package/dist/cjs/run.cjs +48 -38
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/session/JsonlSessionStore.cjs +2 -2
- package/dist/cjs/tools/SubagentTool.cjs +4 -4
- package/dist/cjs/tools/SubagentTool.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +13 -9
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/eagerEventExecution.cjs +1 -0
- package/dist/cjs/tools/local/LocalExecutionEngine.cjs +3 -3
- package/dist/cjs/tools/local/LocalProgrammaticToolCalling.cjs +2 -2
- package/dist/cjs/tools/search/rerankers.cjs +154 -1
- package/dist/cjs/tools/search/rerankers.cjs.map +1 -1
- package/dist/cjs/tools/search/tool.cjs +4 -1
- package/dist/cjs/tools/search/tool.cjs.map +1 -1
- package/dist/cjs/tools/subagent/SubagentExecutionRegistry.cjs +601 -0
- package/dist/cjs/tools/subagent/SubagentExecutionRegistry.cjs.map +1 -0
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs +672 -343
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
- package/dist/cjs/tools/subagent/SubagentReplay.cjs +3 -1
- package/dist/cjs/tools/subagent/SubagentReplay.cjs.map +1 -1
- package/dist/cjs/tools/subagent/childGraphConfig.cjs +278 -0
- package/dist/cjs/tools/subagent/childGraphConfig.cjs.map +1 -0
- package/dist/cjs/tools/subagent/index.cjs +1 -0
- package/dist/cjs/tools/subagent/runtimeLimits.cjs +5 -0
- package/dist/cjs/tools/subagent/runtimeLimits.cjs.map +1 -0
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +57 -33
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/graphs/MultiAgentGraph.mjs +81 -33
- package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
- package/dist/esm/graphs/applyGraphRuntimeConfig.mjs +14 -0
- package/dist/esm/graphs/applyGraphRuntimeConfig.mjs.map +1 -0
- package/dist/esm/graphs/createGraph.mjs +15 -0
- package/dist/esm/graphs/createGraph.mjs.map +1 -0
- package/dist/esm/graphs/index.mjs +1 -0
- package/dist/esm/hooks/createWorkspacePolicyHook.mjs +1 -1
- package/dist/esm/main.mjs +12 -10
- package/dist/esm/run.mjs +48 -38
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/session/JsonlSessionStore.mjs +2 -2
- package/dist/esm/tools/SubagentTool.mjs +4 -4
- package/dist/esm/tools/SubagentTool.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +13 -9
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/eagerEventExecution.mjs +1 -1
- package/dist/esm/tools/local/LocalExecutionEngine.mjs +3 -3
- package/dist/esm/tools/local/LocalProgrammaticToolCalling.mjs +2 -2
- package/dist/esm/tools/search/rerankers.mjs +154 -1
- package/dist/esm/tools/search/rerankers.mjs.map +1 -1
- package/dist/esm/tools/search/tool.mjs +4 -1
- package/dist/esm/tools/search/tool.mjs.map +1 -1
- package/dist/esm/tools/subagent/SubagentExecutionRegistry.mjs +597 -0
- package/dist/esm/tools/subagent/SubagentExecutionRegistry.mjs.map +1 -0
- package/dist/esm/tools/subagent/SubagentExecutor.mjs +672 -343
- package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
- package/dist/esm/tools/subagent/SubagentReplay.mjs +3 -1
- package/dist/esm/tools/subagent/SubagentReplay.mjs.map +1 -1
- package/dist/esm/tools/subagent/childGraphConfig.mjs +272 -0
- package/dist/esm/tools/subagent/childGraphConfig.mjs.map +1 -0
- package/dist/esm/tools/subagent/index.mjs +1 -0
- package/dist/esm/tools/subagent/runtimeLimits.mjs +5 -0
- package/dist/esm/tools/subagent/runtimeLimits.mjs.map +1 -0
- package/dist/types/agents/AgentContext.d.ts +1 -1
- package/dist/types/graphs/Graph.d.ts +7 -2
- package/dist/types/graphs/MultiAgentGraph.d.ts +7 -1
- package/dist/types/graphs/applyGraphRuntimeConfig.d.ts +3 -0
- package/dist/types/graphs/createGraph.d.ts +13 -0
- package/dist/types/graphs/graphFactory.d.ts +13 -0
- package/dist/types/graphs/index.d.ts +2 -0
- package/dist/types/run.d.ts +2 -2
- package/dist/types/tools/SubagentTool.d.ts +1 -1
- package/dist/types/tools/search/rerankers.d.ts +28 -0
- package/dist/types/tools/search/types.d.ts +38 -1
- package/dist/types/tools/subagent/SubagentExecutionRegistry.d.ts +171 -0
- package/dist/types/tools/subagent/SubagentExecutor.d.ts +32 -37
- package/dist/types/tools/subagent/SubagentReplay.d.ts +5 -1
- package/dist/types/tools/subagent/childGraphConfig.d.ts +37 -0
- package/dist/types/tools/subagent/index.d.ts +1 -1
- package/dist/types/tools/subagent/runtimeLimits.d.ts +4 -0
- package/dist/types/types/graph.d.ts +184 -19
- package/package.json +5 -3
- package/src/agents/AgentContext.ts +1 -1
- package/src/graphs/Graph.ts +111 -60
- package/src/graphs/MultiAgentGraph.ts +149 -47
- package/src/graphs/__tests__/MultiAgentGraph.test.ts +86 -0
- package/src/graphs/__tests__/composition.smoke.test.ts +152 -0
- package/src/graphs/__tests__/createGraph.test.ts +206 -0
- package/src/graphs/applyGraphRuntimeConfig.ts +25 -0
- package/src/graphs/createGraph.ts +32 -0
- package/src/graphs/graphFactory.ts +12 -0
- package/src/graphs/index.ts +2 -0
- package/src/run.ts +50 -38
- package/src/specs/graph-subagent.live.test.ts +242 -0
- package/src/specs/graph-subagent.test.ts +322 -0
- package/src/specs/langfuse-routing.integration.test.ts +120 -1
- package/src/specs/subagent.test.ts +130 -4
- package/src/tools/SubagentTool.ts +4 -4
- package/src/tools/ToolNode.ts +22 -10
- package/src/tools/__tests__/SubagentExecutor.test.ts +198 -9
- package/src/tools/__tests__/SubagentReplay.test.ts +29 -0
- package/src/tools/__tests__/directToolHITLResumeScope.test.ts +28 -2
- package/src/tools/__tests__/subagentHooks.test.ts +90 -3
- package/src/tools/search/rag-api-reranker.test.ts +891 -0
- package/src/tools/search/rerankers.ts +260 -0
- package/src/tools/search/tool.ts +6 -0
- package/src/tools/search/types.ts +45 -1
- package/src/tools/subagent/SubagentExecutionRegistry.ts +1159 -0
- package/src/tools/subagent/SubagentExecutor.ts +1245 -563
- package/src/tools/subagent/SubagentReplay.ts +10 -1
- package/src/tools/subagent/__tests__/SubagentExecutionRegistry.test.ts +637 -0
- package/src/tools/subagent/__tests__/SubagentExecutor.lazy.test.ts +3054 -0
- package/src/tools/subagent/__tests__/childGraphConfig.test.ts +521 -0
- package/src/tools/subagent/childGraphConfig.ts +694 -0
- package/src/tools/subagent/index.ts +6 -0
- package/src/tools/subagent/runtimeLimits.ts +7 -0
- package/src/types/graph.ts +217 -19
|
@@ -0,0 +1,694 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AgentInputs,
|
|
3
|
+
ExecutableSubagentConfig,
|
|
4
|
+
ExecutableSubagentConfigEntry,
|
|
5
|
+
GraphEdge,
|
|
6
|
+
GraphSubagentConfig,
|
|
7
|
+
ResolvedSingleAgentSubagentConfig,
|
|
8
|
+
ResolvedSubagentConfig,
|
|
9
|
+
ResolvedSubagentConfigEntry,
|
|
10
|
+
SubagentConfig,
|
|
11
|
+
SubagentConfigEntry,
|
|
12
|
+
} from '@/types';
|
|
13
|
+
import type { AgentContext } from '@/agents/AgentContext';
|
|
14
|
+
import {
|
|
15
|
+
MAX_GRAPH_SUBAGENT_MEMBERS,
|
|
16
|
+
MAX_GRAPH_SUBAGENT_TURNS,
|
|
17
|
+
} from './runtimeLimits';
|
|
18
|
+
|
|
19
|
+
const RESERVED_GRAPH_MEMBER_IDS = new Set([
|
|
20
|
+
'__start__',
|
|
21
|
+
'__end__',
|
|
22
|
+
'messages',
|
|
23
|
+
'agentMessages',
|
|
24
|
+
'subagentResult',
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
type ChildGraphPlanBase = {
|
|
28
|
+
subjectAgentId: string;
|
|
29
|
+
checkpointNodeId: string;
|
|
30
|
+
agents: AgentInputs[];
|
|
31
|
+
memberInputs: ReadonlyMap<string, AgentInputs>;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export type ChildGraphPlan =
|
|
35
|
+
| (ChildGraphPlanBase & {
|
|
36
|
+
kind: 'agent';
|
|
37
|
+
})
|
|
38
|
+
| (ChildGraphPlanBase & {
|
|
39
|
+
kind: 'graph';
|
|
40
|
+
edges: GraphEdge[];
|
|
41
|
+
entryAgentId: string;
|
|
42
|
+
resultAgentId: string;
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
type CreateChildGraphPlanParams = {
|
|
46
|
+
config: ResolvedSubagentConfigEntry;
|
|
47
|
+
executionSuffix: string;
|
|
48
|
+
parentAgentId?: string;
|
|
49
|
+
parentMaxDepth: number;
|
|
50
|
+
keepToolDefinitions: boolean;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
type UnvalidatedGraphSubagentConfig = Omit<
|
|
54
|
+
GraphSubagentConfig,
|
|
55
|
+
'allowNested' | 'edges'
|
|
56
|
+
> & { allowNested?: boolean; edges: GraphEdge[] };
|
|
57
|
+
|
|
58
|
+
type GraphSubagentConfigCandidate = {
|
|
59
|
+
kind?: unknown;
|
|
60
|
+
type?: unknown;
|
|
61
|
+
name?: unknown;
|
|
62
|
+
description?: unknown;
|
|
63
|
+
maxTurns?: unknown;
|
|
64
|
+
allowNested?: unknown;
|
|
65
|
+
agents?: unknown;
|
|
66
|
+
edges?: unknown;
|
|
67
|
+
entryAgentId?: unknown;
|
|
68
|
+
resultAgentId?: unknown;
|
|
69
|
+
configId?: unknown;
|
|
70
|
+
agentInputs?: unknown;
|
|
71
|
+
resolveAgentInputs?: unknown;
|
|
72
|
+
self?: unknown;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
function isObject(value: unknown): value is object {
|
|
76
|
+
return value != null && typeof value === 'object';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function isNonEmptyString(value: unknown): value is string {
|
|
80
|
+
return typeof value === 'string' && value.trim() !== '';
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function isGraphSubagentConfig(
|
|
84
|
+
config: SubagentConfigEntry
|
|
85
|
+
): config is GraphSubagentConfig {
|
|
86
|
+
return (config as { kind?: unknown }).kind === 'graph';
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function assertGraphSubagentConfigShape(
|
|
90
|
+
input: unknown
|
|
91
|
+
): asserts input is UnvalidatedGraphSubagentConfig {
|
|
92
|
+
if (!isObject(input)) {
|
|
93
|
+
throw new Error('Graph subagent config must be an object.');
|
|
94
|
+
}
|
|
95
|
+
const candidate = input as GraphSubagentConfigCandidate;
|
|
96
|
+
const configType = isNonEmptyString(candidate.type)
|
|
97
|
+
? candidate.type
|
|
98
|
+
: '<unknown>';
|
|
99
|
+
if (candidate.kind !== 'graph') {
|
|
100
|
+
throw new Error(`Graph subagent "${configType}" must set kind to "graph".`);
|
|
101
|
+
}
|
|
102
|
+
for (const [field, value] of [
|
|
103
|
+
['type', candidate.type],
|
|
104
|
+
['name', candidate.name],
|
|
105
|
+
['description', candidate.description],
|
|
106
|
+
['entryAgentId', candidate.entryAgentId],
|
|
107
|
+
['resultAgentId', candidate.resultAgentId],
|
|
108
|
+
] as const) {
|
|
109
|
+
if (!isNonEmptyString(value)) {
|
|
110
|
+
throw new Error(
|
|
111
|
+
`Graph subagent "${configType}" must define a non-empty ${field}.`
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (!Array.isArray(candidate.agents)) {
|
|
116
|
+
throw new Error(`Graph subagent "${configType}" agents must be an array.`);
|
|
117
|
+
}
|
|
118
|
+
const agents: unknown[] = candidate.agents;
|
|
119
|
+
for (const agent of agents) {
|
|
120
|
+
if (!isObject(agent)) {
|
|
121
|
+
throw new Error(
|
|
122
|
+
`Graph subagent "${configType}" agents must contain objects.`
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (!Array.isArray(candidate.edges)) {
|
|
127
|
+
throw new Error(`Graph subagent "${configType}" edges must be an array.`);
|
|
128
|
+
}
|
|
129
|
+
const edges: unknown[] = candidate.edges;
|
|
130
|
+
for (const edge of edges) {
|
|
131
|
+
if (!isObject(edge)) {
|
|
132
|
+
throw new Error(
|
|
133
|
+
`Graph subagent "${configType}" edges must contain objects.`
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (
|
|
138
|
+
candidate.allowNested != null &&
|
|
139
|
+
typeof candidate.allowNested !== 'boolean'
|
|
140
|
+
) {
|
|
141
|
+
throw new Error(
|
|
142
|
+
`Graph subagent "${configType}" allowNested must be a boolean.`
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
if (
|
|
146
|
+
candidate.maxTurns != null &&
|
|
147
|
+
(typeof candidate.maxTurns !== 'number' ||
|
|
148
|
+
!Number.isSafeInteger(candidate.maxTurns) ||
|
|
149
|
+
candidate.maxTurns <= 0)
|
|
150
|
+
) {
|
|
151
|
+
throw new Error(
|
|
152
|
+
`Graph subagent "${configType}" maxTurns must be a positive safe integer.`
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
if (
|
|
156
|
+
candidate.agentInputs !== undefined ||
|
|
157
|
+
candidate.resolveAgentInputs !== undefined ||
|
|
158
|
+
candidate.configId !== undefined ||
|
|
159
|
+
candidate.self !== undefined
|
|
160
|
+
) {
|
|
161
|
+
throw new Error(
|
|
162
|
+
`Graph subagent "${configType}" cannot define agentInputs or self, or lazy fields configId/resolveAgentInputs.`
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function getEndpoints(endpoint: string | string[]): string[] {
|
|
168
|
+
return Array.isArray(endpoint) ? endpoint : [endpoint];
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function assertNonEmptyUniqueEndpoints(
|
|
172
|
+
endpoints: string[],
|
|
173
|
+
label: string,
|
|
174
|
+
edgeIndex: number
|
|
175
|
+
): void {
|
|
176
|
+
if (endpoints.length === 0) {
|
|
177
|
+
throw new Error(
|
|
178
|
+
`Graph subagent edge ${edgeIndex} must include at least one ${label} agent.`
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
const seen = new Set<string>();
|
|
182
|
+
for (const endpoint of endpoints) {
|
|
183
|
+
if (typeof endpoint !== 'string' || endpoint.trim() === '') {
|
|
184
|
+
throw new Error(
|
|
185
|
+
`Graph subagent edge ${edgeIndex} contains a blank ${label} agent ID.`
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
if (seen.has(endpoint)) {
|
|
189
|
+
throw new Error(
|
|
190
|
+
`Graph subagent edge ${edgeIndex} repeats ${label} agent "${endpoint}".`
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
seen.add(endpoint);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function visitReachable(
|
|
198
|
+
start: string,
|
|
199
|
+
adjacency: ReadonlyMap<string, ReadonlySet<string>>
|
|
200
|
+
): Set<string> {
|
|
201
|
+
const visited = new Set<string>();
|
|
202
|
+
const pending = [start];
|
|
203
|
+
while (pending.length > 0) {
|
|
204
|
+
const current = pending.pop();
|
|
205
|
+
if (current == null || visited.has(current)) {
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
visited.add(current);
|
|
209
|
+
for (const next of adjacency.get(current) ?? []) {
|
|
210
|
+
if (!visited.has(next)) {
|
|
211
|
+
pending.push(next);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return visited;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export function validateGraphSubagentConfig(config: unknown): void {
|
|
219
|
+
assertGraphSubagentConfigShape(config);
|
|
220
|
+
if (config.agents.length === 0) {
|
|
221
|
+
throw new Error(`Graph subagent "${config.type}" must include an agent.`);
|
|
222
|
+
}
|
|
223
|
+
if (config.agents.length > MAX_GRAPH_SUBAGENT_MEMBERS) {
|
|
224
|
+
throw new Error(
|
|
225
|
+
`Graph subagent "${config.type}" cannot exceed ${MAX_GRAPH_SUBAGENT_MEMBERS} agents.`
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
if (config.maxTurns != null && config.maxTurns > MAX_GRAPH_SUBAGENT_TURNS) {
|
|
229
|
+
throw new Error(
|
|
230
|
+
`Graph subagent "${config.type}" maxTurns exceeds the safe graph recursion budget.`
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
if (config.allowNested === true) {
|
|
234
|
+
throw new Error(
|
|
235
|
+
`Graph subagent "${config.type}" cannot enable allowNested because graph-member nesting and resume semantics are outside this MVP.`
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const agentIds = new Set<string>();
|
|
240
|
+
for (const agent of config.agents) {
|
|
241
|
+
if (typeof agent.agentId !== 'string' || agent.agentId.trim() === '') {
|
|
242
|
+
throw new Error(
|
|
243
|
+
`Graph subagent "${config.type}" contains a blank agent ID.`
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
if (agentIds.has(agent.agentId)) {
|
|
247
|
+
throw new Error(
|
|
248
|
+
`Graph subagent "${config.type}" contains duplicate agent ID "${agent.agentId}".`
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
if (
|
|
252
|
+
RESERVED_GRAPH_MEMBER_IDS.has(agent.agentId) ||
|
|
253
|
+
agent.agentId.includes('|') ||
|
|
254
|
+
agent.agentId.includes(':')
|
|
255
|
+
) {
|
|
256
|
+
throw new Error(
|
|
257
|
+
`Graph subagent "${config.type}" agent ID "${agent.agentId}" is reserved by the graph runtime.`
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
agentIds.add(agent.agentId);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (!agentIds.has(config.entryAgentId)) {
|
|
264
|
+
throw new Error(
|
|
265
|
+
`Graph subagent "${config.type}" entryAgentId "${config.entryAgentId}" is not a configured agent.`
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
if (!agentIds.has(config.resultAgentId)) {
|
|
269
|
+
throw new Error(
|
|
270
|
+
`Graph subagent "${config.type}" resultAgentId "${config.resultAgentId}" is not a configured agent.`
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const adjacency = new Map<string, Set<string>>();
|
|
275
|
+
const reverseAdjacency = new Map<string, Set<string>>();
|
|
276
|
+
const incomingEdgeSources = new Map<string, string[][]>();
|
|
277
|
+
const promptedEdges: Array<{
|
|
278
|
+
edgeIndex: number;
|
|
279
|
+
destinations: string[];
|
|
280
|
+
}> = [];
|
|
281
|
+
const directedEdges = new Set<string>();
|
|
282
|
+
for (const agentId of agentIds) {
|
|
283
|
+
adjacency.set(agentId, new Set());
|
|
284
|
+
reverseAdjacency.set(agentId, new Set());
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
for (let edgeIndex = 0; edgeIndex < config.edges.length; edgeIndex++) {
|
|
288
|
+
const edge = config.edges[edgeIndex];
|
|
289
|
+
if (edge.edgeType !== 'direct') {
|
|
290
|
+
throw new Error(
|
|
291
|
+
`Graph subagent "${config.type}" edge ${edgeIndex} must set edgeType to "direct".`
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
if (edge.condition != null) {
|
|
295
|
+
throw new Error(
|
|
296
|
+
`Graph subagent "${config.type}" edge ${edgeIndex} cannot define a condition.`
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
if (edge.promptKey != null) {
|
|
300
|
+
throw new Error(
|
|
301
|
+
`Graph subagent "${config.type}" edge ${edgeIndex} cannot define promptKey.`
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
const runtimePrompt = (edge as { prompt?: unknown }).prompt;
|
|
305
|
+
if (
|
|
306
|
+
runtimePrompt != null &&
|
|
307
|
+
typeof runtimePrompt !== 'string' &&
|
|
308
|
+
typeof runtimePrompt !== 'function'
|
|
309
|
+
) {
|
|
310
|
+
throw new Error(
|
|
311
|
+
`Graph subagent "${config.type}" edge ${edgeIndex} prompt must be a string or function.`
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
const runtimeExcludeResults = (edge as { excludeResults?: unknown })
|
|
315
|
+
.excludeResults;
|
|
316
|
+
if (
|
|
317
|
+
runtimeExcludeResults != null &&
|
|
318
|
+
typeof runtimeExcludeResults !== 'boolean'
|
|
319
|
+
) {
|
|
320
|
+
throw new Error(
|
|
321
|
+
`Graph subagent "${config.type}" edge ${edgeIndex} excludeResults must be a boolean.`
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const sources = getEndpoints(edge.from);
|
|
326
|
+
const destinations = getEndpoints(edge.to);
|
|
327
|
+
assertNonEmptyUniqueEndpoints(sources, 'source', edgeIndex);
|
|
328
|
+
assertNonEmptyUniqueEndpoints(destinations, 'destination', edgeIndex);
|
|
329
|
+
if (runtimePrompt != null && runtimePrompt !== '') {
|
|
330
|
+
promptedEdges.push({ edgeIndex, destinations });
|
|
331
|
+
} else if (runtimeExcludeResults != null) {
|
|
332
|
+
throw new Error(
|
|
333
|
+
`Graph subagent "${config.type}" edge ${edgeIndex} cannot define excludeResults without a prompt.`
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
for (const endpoint of [...sources, ...destinations]) {
|
|
338
|
+
if (!agentIds.has(endpoint)) {
|
|
339
|
+
throw new Error(
|
|
340
|
+
`Graph subagent "${config.type}" edge ${edgeIndex} references unknown agent "${endpoint}".`
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
for (const destination of destinations) {
|
|
346
|
+
const incomingGroups = incomingEdgeSources.get(destination) ?? [];
|
|
347
|
+
incomingGroups.push(sources);
|
|
348
|
+
incomingEdgeSources.set(destination, incomingGroups);
|
|
349
|
+
if (
|
|
350
|
+
edge.prompt != null &&
|
|
351
|
+
edge.prompt !== '' &&
|
|
352
|
+
agentIds.has(`fan_in_${destination}_prompt`)
|
|
353
|
+
) {
|
|
354
|
+
throw new Error(
|
|
355
|
+
`Graph subagent "${config.type}" agent ID "fan_in_${destination}_prompt" conflicts with a generated prompt node.`
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
for (const source of sources) {
|
|
359
|
+
if (source === destination) {
|
|
360
|
+
throw new Error(
|
|
361
|
+
`Graph subagent "${config.type}" cannot contain self-edge "${source}".`
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
const edgeKey = `${source}\u0000${destination}`;
|
|
365
|
+
if (directedEdges.has(edgeKey)) {
|
|
366
|
+
throw new Error(
|
|
367
|
+
`Graph subagent "${config.type}" repeats edge "${source}" -> "${destination}".`
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
directedEdges.add(edgeKey);
|
|
371
|
+
adjacency.get(source)!.add(destination);
|
|
372
|
+
reverseAdjacency.get(destination)!.add(source);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
for (const [destination, incomingGroups] of incomingEdgeSources) {
|
|
378
|
+
const incomingSources = reverseAdjacency.get(destination)!;
|
|
379
|
+
if (incomingSources.size <= 1) {
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
if (
|
|
383
|
+
incomingGroups.length !== 1 ||
|
|
384
|
+
incomingGroups[0].length !== incomingSources.size
|
|
385
|
+
) {
|
|
386
|
+
throw new Error(
|
|
387
|
+
`Graph subagent "${config.type}" fan-in to "${destination}" must use one edge with an array-valued from field.`
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const isSimpleChain = [...agentIds].every(
|
|
393
|
+
(agentId) =>
|
|
394
|
+
adjacency.get(agentId)!.size <= 1 &&
|
|
395
|
+
reverseAdjacency.get(agentId)!.size <= 1
|
|
396
|
+
);
|
|
397
|
+
for (const { edgeIndex, destinations } of promptedEdges) {
|
|
398
|
+
if (destinations.length !== 1) {
|
|
399
|
+
throw new Error(
|
|
400
|
+
`Graph subagent "${config.type}" prompted edge ${edgeIndex} must have exactly one destination.`
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
if (!isSimpleChain && destinations[0] !== config.resultAgentId) {
|
|
404
|
+
throw new Error(
|
|
405
|
+
`Graph subagent "${config.type}" prompted edge ${edgeIndex} must target resultAgentId "${config.resultAgentId}" after parallel branches converge.`
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const roots = [...agentIds].filter(
|
|
411
|
+
(agentId) => reverseAdjacency.get(agentId)!.size === 0
|
|
412
|
+
);
|
|
413
|
+
if (roots.length !== 1 || roots[0] !== config.entryAgentId) {
|
|
414
|
+
throw new Error(
|
|
415
|
+
`Graph subagent "${config.type}" must have entryAgentId "${config.entryAgentId}" as its only root.`
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
const sinks = [...agentIds].filter(
|
|
419
|
+
(agentId) => adjacency.get(agentId)!.size === 0
|
|
420
|
+
);
|
|
421
|
+
if (sinks.length !== 1 || sinks[0] !== config.resultAgentId) {
|
|
422
|
+
throw new Error(
|
|
423
|
+
`Graph subagent "${config.type}" must have resultAgentId "${config.resultAgentId}" as its only sink.`
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const remainingIncoming = new Map<string, number>();
|
|
428
|
+
const ready: string[] = [];
|
|
429
|
+
for (const agentId of agentIds) {
|
|
430
|
+
const incomingCount = reverseAdjacency.get(agentId)!.size;
|
|
431
|
+
remainingIncoming.set(agentId, incomingCount);
|
|
432
|
+
if (incomingCount === 0) {
|
|
433
|
+
ready.push(agentId);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
let visitedCount = 0;
|
|
437
|
+
while (ready.length > 0) {
|
|
438
|
+
const current = ready.pop()!;
|
|
439
|
+
visitedCount++;
|
|
440
|
+
for (const destination of adjacency.get(current) ?? []) {
|
|
441
|
+
const incomingCount = remainingIncoming.get(destination)! - 1;
|
|
442
|
+
remainingIncoming.set(destination, incomingCount);
|
|
443
|
+
if (incomingCount === 0) {
|
|
444
|
+
ready.push(destination);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
if (visitedCount !== agentIds.size) {
|
|
449
|
+
throw new Error(`Graph subagent "${config.type}" must be acyclic.`);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
const reachableFromEntry = visitReachable(config.entryAgentId, adjacency);
|
|
453
|
+
const canReachResult = visitReachable(config.resultAgentId, reverseAdjacency);
|
|
454
|
+
if (
|
|
455
|
+
reachableFromEntry.size !== agentIds.size ||
|
|
456
|
+
canReachResult.size !== agentIds.size
|
|
457
|
+
) {
|
|
458
|
+
throw new Error(
|
|
459
|
+
`Every agent in graph subagent "${config.type}" must be reachable from entryAgentId and able to reach resultAgentId.`
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function normalizeSingleAgentConfig(
|
|
465
|
+
config: SubagentConfig,
|
|
466
|
+
parentContext: AgentContext
|
|
467
|
+
): ExecutableSubagentConfig | null {
|
|
468
|
+
const candidate = config as GraphSubagentConfigCandidate;
|
|
469
|
+
if (
|
|
470
|
+
candidate.kind === 'agent' &&
|
|
471
|
+
(candidate.agents !== undefined ||
|
|
472
|
+
candidate.edges !== undefined ||
|
|
473
|
+
candidate.entryAgentId !== undefined ||
|
|
474
|
+
candidate.resultAgentId !== undefined)
|
|
475
|
+
) {
|
|
476
|
+
throw new Error(
|
|
477
|
+
`Single-agent subagent "${config.type}" cannot define graph topology fields.`
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
if (config.self === true && config.resolveAgentInputs != null) {
|
|
481
|
+
throw new Error(
|
|
482
|
+
`Subagent "${config.type}" cannot combine self with resolveAgentInputs.`
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
if (config.agentInputs != null) {
|
|
486
|
+
return config as ResolvedSingleAgentSubagentConfig;
|
|
487
|
+
}
|
|
488
|
+
if (config.self === true && parentContext._sourceInputs != null) {
|
|
489
|
+
return {
|
|
490
|
+
...config,
|
|
491
|
+
agentInputs: { ...parentContext._sourceInputs },
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
if (config.resolveAgentInputs == null) {
|
|
495
|
+
return null;
|
|
496
|
+
}
|
|
497
|
+
if (!isNonEmptyString(config.configId)) {
|
|
498
|
+
throw new Error(
|
|
499
|
+
`Lazy subagent "${config.type}" requires a non-empty "configId".`
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
return config as ExecutableSubagentConfig;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/** Normalize eager, self, lazy, and graph subagent entries for advertisement. */
|
|
506
|
+
export function normalizeSubagentConfigEntries(
|
|
507
|
+
configs: SubagentConfigEntry[],
|
|
508
|
+
parentContext: AgentContext
|
|
509
|
+
): ExecutableSubagentConfigEntry[] {
|
|
510
|
+
const normalized: ExecutableSubagentConfigEntry[] = [];
|
|
511
|
+
const seenTypes = new Set<string>();
|
|
512
|
+
for (const config of configs) {
|
|
513
|
+
let executable: ExecutableSubagentConfigEntry | null;
|
|
514
|
+
if (isGraphSubagentConfig(config)) {
|
|
515
|
+
validateGraphSubagentConfig(config);
|
|
516
|
+
executable = config;
|
|
517
|
+
} else {
|
|
518
|
+
executable = normalizeSingleAgentConfig(config, parentContext);
|
|
519
|
+
}
|
|
520
|
+
if (executable == null) {
|
|
521
|
+
continue;
|
|
522
|
+
}
|
|
523
|
+
if (seenTypes.has(executable.type)) {
|
|
524
|
+
throw new Error(
|
|
525
|
+
`Duplicate subagent type "${executable.type}". Each SubagentConfig must have a unique "type" field.`
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
seenTypes.add(executable.type);
|
|
529
|
+
normalized.push(executable);
|
|
530
|
+
}
|
|
531
|
+
return normalized;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
/** Resolve self-spawn inputs and validate explicit graph configs. */
|
|
535
|
+
export function resolveSubagentConfigEntries(
|
|
536
|
+
configs: SubagentConfigEntry[],
|
|
537
|
+
parentContext: AgentContext
|
|
538
|
+
): ResolvedSubagentConfigEntry[] {
|
|
539
|
+
return normalizeSubagentConfigEntries(configs, parentContext).filter(
|
|
540
|
+
(config): config is ResolvedSubagentConfigEntry =>
|
|
541
|
+
isGraphSubagentConfig(config) || config.agentInputs != null
|
|
542
|
+
);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/** Resolve legacy single-agent configs with the original public signature. */
|
|
546
|
+
export function resolveSubagentConfigs(
|
|
547
|
+
configs: SubagentConfig[],
|
|
548
|
+
parentContext: AgentContext
|
|
549
|
+
): ResolvedSubagentConfig[] {
|
|
550
|
+
const graphConfig = configs.find(isGraphSubagentConfig);
|
|
551
|
+
if (graphConfig != null) {
|
|
552
|
+
throw new Error(
|
|
553
|
+
`Graph subagent "${graphConfig.type}" requires resolveSubagentConfigEntries().`
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
return resolveSubagentConfigEntries(
|
|
557
|
+
configs,
|
|
558
|
+
parentContext
|
|
559
|
+
) as ResolvedSubagentConfig[];
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/** Normalize legacy single-agent configs while rejecting graph entries. */
|
|
563
|
+
export function normalizeSubagentConfigs(
|
|
564
|
+
configs: SubagentConfig[],
|
|
565
|
+
parentContext: AgentContext
|
|
566
|
+
): ExecutableSubagentConfig[] {
|
|
567
|
+
const graphConfig = configs.find(isGraphSubagentConfig);
|
|
568
|
+
if (graphConfig != null) {
|
|
569
|
+
throw new Error(
|
|
570
|
+
`Graph subagent "${graphConfig.type}" requires normalizeSubagentConfigEntries().`
|
|
571
|
+
);
|
|
572
|
+
}
|
|
573
|
+
return normalizeSubagentConfigEntries(
|
|
574
|
+
configs,
|
|
575
|
+
parentContext
|
|
576
|
+
) as ExecutableSubagentConfig[];
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
function prepareChildInputs({
|
|
580
|
+
agentInputs,
|
|
581
|
+
agentId,
|
|
582
|
+
self,
|
|
583
|
+
allowNested,
|
|
584
|
+
parentMaxDepth,
|
|
585
|
+
keepToolDefinitions,
|
|
586
|
+
}: {
|
|
587
|
+
agentInputs: AgentInputs;
|
|
588
|
+
agentId: string;
|
|
589
|
+
self: boolean;
|
|
590
|
+
allowNested: boolean;
|
|
591
|
+
parentMaxDepth: number;
|
|
592
|
+
keepToolDefinitions: boolean;
|
|
593
|
+
}): AgentInputs {
|
|
594
|
+
const childInputs: AgentInputs = {
|
|
595
|
+
...agentInputs,
|
|
596
|
+
agentId,
|
|
597
|
+
toolDefinitions: keepToolDefinitions
|
|
598
|
+
? agentInputs.toolDefinitions
|
|
599
|
+
: undefined,
|
|
600
|
+
initialSummary: undefined,
|
|
601
|
+
discoveredTools: undefined,
|
|
602
|
+
graphTools: self ? undefined : agentInputs.graphTools,
|
|
603
|
+
};
|
|
604
|
+
if (allowNested) {
|
|
605
|
+
childInputs.maxSubagentDepth = Math.max(0, parentMaxDepth - 1);
|
|
606
|
+
} else {
|
|
607
|
+
childInputs.subagentConfigs = undefined;
|
|
608
|
+
childInputs.maxSubagentDepth = undefined;
|
|
609
|
+
}
|
|
610
|
+
return childInputs;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
export function buildChildInputs(
|
|
614
|
+
config: ResolvedSubagentConfig,
|
|
615
|
+
childAgentId: string,
|
|
616
|
+
parentMaxDepth: number,
|
|
617
|
+
keepToolDefinitions: boolean = false
|
|
618
|
+
): AgentInputs {
|
|
619
|
+
return prepareChildInputs({
|
|
620
|
+
agentInputs: config.agentInputs,
|
|
621
|
+
agentId: childAgentId,
|
|
622
|
+
self: config.self === true,
|
|
623
|
+
allowNested: config.allowNested === true,
|
|
624
|
+
parentMaxDepth,
|
|
625
|
+
keepToolDefinitions,
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
export function buildGraphChildInputs(
|
|
630
|
+
config: GraphSubagentConfig,
|
|
631
|
+
parentMaxDepth: number,
|
|
632
|
+
keepToolDefinitions: boolean = false
|
|
633
|
+
): AgentInputs[] {
|
|
634
|
+
return config.agents.map((agentInputs) =>
|
|
635
|
+
prepareChildInputs({
|
|
636
|
+
agentInputs,
|
|
637
|
+
agentId: agentInputs.agentId,
|
|
638
|
+
self: false,
|
|
639
|
+
allowNested: false,
|
|
640
|
+
parentMaxDepth,
|
|
641
|
+
keepToolDefinitions,
|
|
642
|
+
})
|
|
643
|
+
);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
export function createChildGraphPlan({
|
|
647
|
+
config,
|
|
648
|
+
executionSuffix,
|
|
649
|
+
parentAgentId,
|
|
650
|
+
parentMaxDepth,
|
|
651
|
+
keepToolDefinitions,
|
|
652
|
+
}: CreateChildGraphPlanParams): ChildGraphPlan {
|
|
653
|
+
if (isGraphSubagentConfig(config)) {
|
|
654
|
+
validateGraphSubagentConfig(config);
|
|
655
|
+
const agents = buildGraphChildInputs(
|
|
656
|
+
config,
|
|
657
|
+
parentMaxDepth,
|
|
658
|
+
keepToolDefinitions
|
|
659
|
+
);
|
|
660
|
+
return {
|
|
661
|
+
kind: 'graph',
|
|
662
|
+
subjectAgentId: `${parentAgentId ?? 'agent'}_subgraph_${executionSuffix}`,
|
|
663
|
+
checkpointNodeId: config.resultAgentId,
|
|
664
|
+
agents,
|
|
665
|
+
memberInputs: new Map(
|
|
666
|
+
agents.map((agentInputs) => [agentInputs.agentId, agentInputs])
|
|
667
|
+
),
|
|
668
|
+
edges: config.edges.map((edge) => ({
|
|
669
|
+
...edge,
|
|
670
|
+
from: Array.isArray(edge.from) ? [...edge.from] : edge.from,
|
|
671
|
+
to: Array.isArray(edge.to) ? [...edge.to] : edge.to,
|
|
672
|
+
})),
|
|
673
|
+
entryAgentId: config.entryAgentId,
|
|
674
|
+
resultAgentId: config.resultAgentId,
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
const childAgentId =
|
|
679
|
+
config.agentInputs.agentId ||
|
|
680
|
+
`${parentAgentId ?? 'agent'}_sub_${executionSuffix}`;
|
|
681
|
+
const childInputs = buildChildInputs(
|
|
682
|
+
config,
|
|
683
|
+
childAgentId,
|
|
684
|
+
parentMaxDepth,
|
|
685
|
+
keepToolDefinitions
|
|
686
|
+
);
|
|
687
|
+
return {
|
|
688
|
+
kind: 'agent',
|
|
689
|
+
subjectAgentId: childAgentId,
|
|
690
|
+
checkpointNodeId: childAgentId,
|
|
691
|
+
agents: [childInputs],
|
|
692
|
+
memberInputs: new Map([[childAgentId, childInputs]]),
|
|
693
|
+
};
|
|
694
|
+
}
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
export {
|
|
2
|
+
DEFAULT_SUBAGENT_DESCRIPTION,
|
|
2
3
|
SubagentExecutor,
|
|
3
4
|
filterSubagentResult,
|
|
5
|
+
filterGraphSubagentResult,
|
|
6
|
+
isGraphSubagentConfig,
|
|
7
|
+
normalizeSubagentConfigs,
|
|
8
|
+
normalizeSubagentConfigEntries,
|
|
4
9
|
resolveSubagentConfigs,
|
|
10
|
+
resolveSubagentConfigEntries,
|
|
5
11
|
buildChildInputs,
|
|
6
12
|
summarizeEvent,
|
|
7
13
|
} from './SubagentExecutor';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export const DEFAULT_SUBAGENT_MAX_TURNS = 25;
|
|
2
|
+
export const SUBAGENT_RECURSION_MULTIPLIER = 3;
|
|
3
|
+
export const MAX_GRAPH_SUBAGENT_MEMBERS = 32;
|
|
4
|
+
export const MAX_GRAPH_SUBAGENT_TURNS = Math.floor(
|
|
5
|
+
Number.MAX_SAFE_INTEGER /
|
|
6
|
+
(SUBAGENT_RECURSION_MULTIPLIER * MAX_GRAPH_SUBAGENT_MEMBERS)
|
|
7
|
+
);
|