@illuma-ai/agents 1.1.14 → 1.1.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/common/enum.cjs +11 -0
- package/dist/cjs/common/enum.cjs.map +1 -1
- package/dist/cjs/graphs/MultiAgentGraph.cjs +206 -4
- package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
- package/dist/cjs/main.cjs +2 -0
- package/dist/cjs/main.cjs.map +1 -1
- package/dist/cjs/types/graph.cjs.map +1 -1
- package/dist/esm/common/enum.mjs +9 -1
- package/dist/esm/common/enum.mjs.map +1 -1
- package/dist/esm/graphs/MultiAgentGraph.mjs +208 -6
- package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
- package/dist/esm/main.mjs +1 -1
- package/dist/esm/types/graph.mjs.map +1 -1
- package/dist/types/common/enum.d.ts +9 -1
- package/dist/types/graphs/MultiAgentGraph.d.ts +52 -0
- package/dist/types/types/graph.d.ts +6 -0
- package/package.json +1 -1
- package/src/common/__tests__/enum.test.ts +3 -3
- package/src/common/enum.ts +10 -0
- package/src/graphs/MultiAgentGraph.ts +287 -5
- package/src/graphs/__tests__/multi-agent-delegate.test.ts +208 -0
- package/src/graphs/__tests__/multi-agent-edges.test.ts +42 -3
- package/src/tools/search/search.test.ts +173 -0
- package/src/types/graph.ts +6 -0
|
@@ -78,7 +78,9 @@ export declare enum EdgeType {
|
|
|
78
78
|
/** Creates handoff tools for dynamic agent routing (default for single-to-single edges) */
|
|
79
79
|
HANDOFF = "handoff",
|
|
80
80
|
/** Creates direct edges for automatic sequential/parallel transitions */
|
|
81
|
-
DIRECT = "direct"
|
|
81
|
+
DIRECT = "direct",
|
|
82
|
+
/** Creates delegate tools that invoke child subgraph inline and return result to parent */
|
|
83
|
+
DELEGATE = "delegate"
|
|
82
84
|
}
|
|
83
85
|
export declare enum GraphNodeKeys {
|
|
84
86
|
TOOLS = "tools=",
|
|
@@ -136,6 +138,8 @@ export declare enum Constants {
|
|
|
136
138
|
WEB_SEARCH = "web_search",
|
|
137
139
|
CONTENT_AND_ARTIFACT = "content_and_artifact",
|
|
138
140
|
LC_TRANSFER_TO_ = "lc_transfer_to_",
|
|
141
|
+
/** Prefix for delegate tool names: lc_delegate_to_{agentId} */
|
|
142
|
+
LC_DELEGATE_TO_ = "lc_delegate_to_",
|
|
139
143
|
/** Tool name for the AskUser structured question tool */
|
|
140
144
|
ASK_USER = "ask_user",
|
|
141
145
|
/** Delimiter for MCP tools: toolName_mcp_serverName */
|
|
@@ -183,3 +187,7 @@ export declare enum MessageTypes {
|
|
|
183
187
|
DEVELOPER = "developer",
|
|
184
188
|
REMOVE = "remove"
|
|
185
189
|
}
|
|
190
|
+
/** Default max characters for delegate results returned to parent (~8192 tokens at ~4 chars/token) */
|
|
191
|
+
export declare const DEFAULT_DELEGATE_MAX_RESULT_CHARS = 32768;
|
|
192
|
+
/** Default timeout for delegate sub-agent execution in milliseconds (5 minutes) */
|
|
193
|
+
export declare const DELEGATE_TIMEOUT_MS = 300000;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { BaseMessage } from '@langchain/core/messages';
|
|
1
2
|
import type * as t from '@/types';
|
|
2
3
|
import { StandardGraph } from './Graph';
|
|
3
4
|
/**
|
|
@@ -19,6 +20,15 @@ export declare class MultiAgentGraph extends StandardGraph {
|
|
|
19
20
|
private startingNodes;
|
|
20
21
|
private directEdges;
|
|
21
22
|
private handoffEdges;
|
|
23
|
+
private delegateEdges;
|
|
24
|
+
/**
|
|
25
|
+
* Lazily populated registry of compiled subgraphs, keyed by agentId.
|
|
26
|
+
* Delegate tools are created in the constructor but reference subgraphs
|
|
27
|
+
* that are only created in createWorkflow(). This Map bridges that gap —
|
|
28
|
+
* tools capture the Map reference in their closure, and createWorkflow()
|
|
29
|
+
* populates it before any tool invocation occurs.
|
|
30
|
+
*/
|
|
31
|
+
private subgraphRegistry;
|
|
22
32
|
/**
|
|
23
33
|
* Map of agentId to parallel group info.
|
|
24
34
|
* Contains groupId (incrementing number reflecting execution order) for agents in parallel groups.
|
|
@@ -99,6 +109,48 @@ export declare class MultiAgentGraph extends StandardGraph {
|
|
|
99
109
|
* @param destinationId - Raw agent ID (fallback when context unavailable)
|
|
100
110
|
*/
|
|
101
111
|
private buildDefaultHandoffDescription;
|
|
112
|
+
/**
|
|
113
|
+
* Create delegate tools for agents based on delegate edges.
|
|
114
|
+
* Delegate tools invoke child agent subgraphs inline and return the result
|
|
115
|
+
* as a string to the parent agent's context. Unlike handoff tools (which
|
|
116
|
+
* return Command for fire-and-forget routing), delegate tools execute the
|
|
117
|
+
* child, extract the final text, and return it within the parent's agent loop.
|
|
118
|
+
*
|
|
119
|
+
* This enables the supervisor pattern: parent calls child → gets result → thinks → calls another.
|
|
120
|
+
*/
|
|
121
|
+
private createDelegateTools;
|
|
122
|
+
/**
|
|
123
|
+
* Create delegate tools for an edge (handles multiple destinations).
|
|
124
|
+
* Each delegate tool invokes the child agent's compiled subgraph inline,
|
|
125
|
+
* extracts the final AI message text, truncates it, and returns it as
|
|
126
|
+
* a string (which becomes a ToolMessage in the parent's context).
|
|
127
|
+
*
|
|
128
|
+
* @param edge - The graph edge defining the delegation
|
|
129
|
+
* @param sourceAgentId - The ID of the parent/supervisor agent
|
|
130
|
+
*/
|
|
131
|
+
private createDelegateToolsForEdge;
|
|
132
|
+
/**
|
|
133
|
+
* Extract the final text result from a child agent's output messages.
|
|
134
|
+
* Walks backwards to find the last AIMessage with text content.
|
|
135
|
+
* Handles both string content and array content (multi-modal messages).
|
|
136
|
+
* @param messages - The child agent's output messages
|
|
137
|
+
* @param agentId - The child agent ID (for fallback message)
|
|
138
|
+
*/
|
|
139
|
+
static extractDelegateResult(messages: BaseMessage[], agentId: string): string;
|
|
140
|
+
/**
|
|
141
|
+
* Truncate delegate result using head/tail strategy (60/40 split).
|
|
142
|
+
* Preserves the beginning (key findings) and end (conclusions).
|
|
143
|
+
* Matches the TaskTool.truncateResult pattern from Ranger.
|
|
144
|
+
* @param result - The full result text
|
|
145
|
+
* @param maxChars - Maximum allowed characters
|
|
146
|
+
*/
|
|
147
|
+
static truncateDelegateResult(result: string, maxChars: number): string;
|
|
148
|
+
/**
|
|
149
|
+
* Build a meaningful default description for a delegate tool.
|
|
150
|
+
* @param destContext - AgentContext of the destination agent
|
|
151
|
+
* @param destinationId - Raw agent ID (fallback)
|
|
152
|
+
*/
|
|
153
|
+
private buildDefaultDelegateDescription;
|
|
102
154
|
/**
|
|
103
155
|
* Create a complete agent subgraph (similar to createReactAgent)
|
|
104
156
|
*/
|
|
@@ -242,6 +242,12 @@ export type GraphEdge = {
|
|
|
242
242
|
* Only applies when prompt is provided for handoff edges.
|
|
243
243
|
*/
|
|
244
244
|
promptKey?: string;
|
|
245
|
+
/**
|
|
246
|
+
* For delegate edges: Maximum characters for the result returned to the parent.
|
|
247
|
+
* Uses head/tail truncation (60/40 split) to preserve key findings and conclusions.
|
|
248
|
+
* Defaults to Constants.DEFAULT_DELEGATE_MAX_RESULT_CHARS (32768 chars, ~8192 tokens).
|
|
249
|
+
*/
|
|
250
|
+
maxResultChars?: number;
|
|
245
251
|
};
|
|
246
252
|
export type MultiAgentGraphInput = StandardGraphInput & {
|
|
247
253
|
edges: GraphEdge[];
|
package/package.json
CHANGED
|
@@ -26,10 +26,10 @@ describe('EdgeType enum', () => {
|
|
|
26
26
|
expect(EdgeType.DIRECT).toBe('direct');
|
|
27
27
|
});
|
|
28
28
|
|
|
29
|
-
it('
|
|
29
|
+
it('has three members: handoff, direct, delegate', () => {
|
|
30
30
|
const values = Object.values(EdgeType);
|
|
31
|
-
expect(values).toHaveLength(
|
|
32
|
-
expect(values).toEqual(expect.arrayContaining(['handoff', 'direct']));
|
|
31
|
+
expect(values).toHaveLength(3);
|
|
32
|
+
expect(values).toEqual(expect.arrayContaining(['handoff', 'direct', 'delegate']));
|
|
33
33
|
});
|
|
34
34
|
});
|
|
35
35
|
|
package/src/common/enum.ts
CHANGED
|
@@ -100,6 +100,8 @@ export enum EdgeType {
|
|
|
100
100
|
HANDOFF = 'handoff',
|
|
101
101
|
/** Creates direct edges for automatic sequential/parallel transitions */
|
|
102
102
|
DIRECT = 'direct',
|
|
103
|
+
/** Creates delegate tools that invoke child subgraph inline and return result to parent */
|
|
104
|
+
DELEGATE = 'delegate',
|
|
103
105
|
}
|
|
104
106
|
|
|
105
107
|
export enum GraphNodeKeys {
|
|
@@ -182,6 +184,8 @@ export enum Constants {
|
|
|
182
184
|
WEB_SEARCH = 'web_search',
|
|
183
185
|
CONTENT_AND_ARTIFACT = 'content_and_artifact',
|
|
184
186
|
LC_TRANSFER_TO_ = 'lc_transfer_to_',
|
|
187
|
+
/** Prefix for delegate tool names: lc_delegate_to_{agentId} */
|
|
188
|
+
LC_DELEGATE_TO_ = 'lc_delegate_to_',
|
|
185
189
|
/** Tool name for the AskUser structured question tool */
|
|
186
190
|
ASK_USER = 'ask_user',
|
|
187
191
|
/** Delimiter for MCP tools: toolName_mcp_serverName */
|
|
@@ -233,3 +237,9 @@ export enum MessageTypes {
|
|
|
233
237
|
DEVELOPER = 'developer',
|
|
234
238
|
REMOVE = 'remove',
|
|
235
239
|
}
|
|
240
|
+
|
|
241
|
+
/** Default max characters for delegate results returned to parent (~8192 tokens at ~4 chars/token) */
|
|
242
|
+
export const DEFAULT_DELEGATE_MAX_RESULT_CHARS = 32768;
|
|
243
|
+
|
|
244
|
+
/** Default timeout for delegate sub-agent execution in milliseconds (5 minutes) */
|
|
245
|
+
export const DELEGATE_TIMEOUT_MS = 300_000;
|
|
@@ -22,7 +22,11 @@ import type { ToolRunnableConfig } from '@langchain/core/tools';
|
|
|
22
22
|
import type * as t from '@/types';
|
|
23
23
|
import { summarize, createEmergencySummary } from '@/messages';
|
|
24
24
|
import { StandardGraph } from './Graph';
|
|
25
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
Constants,
|
|
27
|
+
EdgeType,
|
|
28
|
+
DEFAULT_DELEGATE_MAX_RESULT_CHARS,
|
|
29
|
+
} from '@/common';
|
|
26
30
|
|
|
27
31
|
/** Pattern to extract instructions from transfer ToolMessage content */
|
|
28
32
|
const HANDOFF_INSTRUCTIONS_PATTERN = /(?:Instructions?|Context):\s*(.+)/is;
|
|
@@ -46,6 +50,15 @@ export class MultiAgentGraph extends StandardGraph {
|
|
|
46
50
|
private startingNodes: Set<string> = new Set();
|
|
47
51
|
private directEdges: t.GraphEdge[] = [];
|
|
48
52
|
private handoffEdges: t.GraphEdge[] = [];
|
|
53
|
+
private delegateEdges: t.GraphEdge[] = [];
|
|
54
|
+
/**
|
|
55
|
+
* Lazily populated registry of compiled subgraphs, keyed by agentId.
|
|
56
|
+
* Delegate tools are created in the constructor but reference subgraphs
|
|
57
|
+
* that are only created in createWorkflow(). This Map bridges that gap —
|
|
58
|
+
* tools capture the Map reference in their closure, and createWorkflow()
|
|
59
|
+
* populates it before any tool invocation occurs.
|
|
60
|
+
*/
|
|
61
|
+
private subgraphRegistry: Map<string, t.CompiledAgentWorfklow> = new Map();
|
|
49
62
|
/**
|
|
50
63
|
* Map of agentId to parallel group info.
|
|
51
64
|
* Contains groupId (incrementing number reflecting execution order) for agents in parallel groups.
|
|
@@ -69,6 +82,7 @@ export class MultiAgentGraph extends StandardGraph {
|
|
|
69
82
|
this.categorizeEdges();
|
|
70
83
|
this.analyzeGraph();
|
|
71
84
|
this.createHandoffTools();
|
|
85
|
+
this.createDelegateTools();
|
|
72
86
|
console.debug(
|
|
73
87
|
`[MultiAgentGraph] Constructor complete: ${this.agentContexts.size} agents, ${this.edges.length} edges`
|
|
74
88
|
);
|
|
@@ -81,7 +95,9 @@ export class MultiAgentGraph extends StandardGraph {
|
|
|
81
95
|
for (const edge of this.edges) {
|
|
82
96
|
// Default behavior: edges with conditions or explicit 'handoff' type are handoff edges
|
|
83
97
|
// Edges with explicit 'direct' type or multi-destination without conditions are direct edges
|
|
84
|
-
if (edge.edgeType === EdgeType.
|
|
98
|
+
if (edge.edgeType === EdgeType.DELEGATE) {
|
|
99
|
+
this.delegateEdges.push(edge);
|
|
100
|
+
} else if (edge.edgeType === EdgeType.DIRECT) {
|
|
85
101
|
this.directEdges.push(edge);
|
|
86
102
|
} else if (edge.edgeType === EdgeType.HANDOFF || edge.condition != null) {
|
|
87
103
|
this.handoffEdges.push(edge);
|
|
@@ -100,7 +116,7 @@ export class MultiAgentGraph extends StandardGraph {
|
|
|
100
116
|
}
|
|
101
117
|
}
|
|
102
118
|
console.debug(
|
|
103
|
-
`[MultiAgentGraph] Edge categorization: ${this.handoffEdges.length} handoff, ${this.directEdges.length} direct (of ${this.edges.length} total)`
|
|
119
|
+
`[MultiAgentGraph] Edge categorization: ${this.handoffEdges.length} handoff, ${this.directEdges.length} direct, ${this.delegateEdges.length} delegate (of ${this.edges.length} total)`
|
|
104
120
|
);
|
|
105
121
|
}
|
|
106
122
|
|
|
@@ -200,8 +216,8 @@ export class MultiAgentGraph extends StandardGraph {
|
|
|
200
216
|
}
|
|
201
217
|
}
|
|
202
218
|
|
|
203
|
-
// Also follow handoff edges for traversal (
|
|
204
|
-
for (const edge of this.handoffEdges) {
|
|
219
|
+
// Also follow handoff and delegate edges for traversal (they don't create parallel groups)
|
|
220
|
+
for (const edge of [...this.handoffEdges, ...this.delegateEdges]) {
|
|
205
221
|
const sources = Array.isArray(edge.from) ? edge.from : [edge.from];
|
|
206
222
|
if (!sources.includes(current)) continue;
|
|
207
223
|
|
|
@@ -547,6 +563,269 @@ export class MultiAgentGraph extends StandardGraph {
|
|
|
547
563
|
return `Transfer control to "${displayName}"`;
|
|
548
564
|
}
|
|
549
565
|
|
|
566
|
+
/**
|
|
567
|
+
* Create delegate tools for agents based on delegate edges.
|
|
568
|
+
* Delegate tools invoke child agent subgraphs inline and return the result
|
|
569
|
+
* as a string to the parent agent's context. Unlike handoff tools (which
|
|
570
|
+
* return Command for fire-and-forget routing), delegate tools execute the
|
|
571
|
+
* child, extract the final text, and return it within the parent's agent loop.
|
|
572
|
+
*
|
|
573
|
+
* This enables the supervisor pattern: parent calls child → gets result → thinks → calls another.
|
|
574
|
+
*/
|
|
575
|
+
private createDelegateTools(): void {
|
|
576
|
+
const delegatesByAgent = new Map<string, t.GraphEdge[]>();
|
|
577
|
+
|
|
578
|
+
for (const edge of this.delegateEdges) {
|
|
579
|
+
const sources = Array.isArray(edge.from) ? edge.from : [edge.from];
|
|
580
|
+
sources.forEach((source) => {
|
|
581
|
+
if (!delegatesByAgent.has(source)) {
|
|
582
|
+
delegatesByAgent.set(source, []);
|
|
583
|
+
}
|
|
584
|
+
delegatesByAgent.get(source)!.push(edge);
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
for (const [agentId, edges] of delegatesByAgent) {
|
|
589
|
+
const agentContext = this.agentContexts.get(agentId);
|
|
590
|
+
if (!agentContext) continue;
|
|
591
|
+
|
|
592
|
+
const delegateTools: t.GenericTool[] = [];
|
|
593
|
+
for (const edge of edges) {
|
|
594
|
+
delegateTools.push(
|
|
595
|
+
...this.createDelegateToolsForEdge(edge, agentId)
|
|
596
|
+
);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
if (!agentContext.graphTools) {
|
|
600
|
+
agentContext.graphTools = [];
|
|
601
|
+
}
|
|
602
|
+
agentContext.graphTools.push(...delegateTools);
|
|
603
|
+
console.debug(
|
|
604
|
+
`[MultiAgentGraph] Delegate tools for "${agentId}": [${delegateTools.map((t) => t.name).join(', ')}]`
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* Create delegate tools for an edge (handles multiple destinations).
|
|
611
|
+
* Each delegate tool invokes the child agent's compiled subgraph inline,
|
|
612
|
+
* extracts the final AI message text, truncates it, and returns it as
|
|
613
|
+
* a string (which becomes a ToolMessage in the parent's context).
|
|
614
|
+
*
|
|
615
|
+
* @param edge - The graph edge defining the delegation
|
|
616
|
+
* @param sourceAgentId - The ID of the parent/supervisor agent
|
|
617
|
+
*/
|
|
618
|
+
private createDelegateToolsForEdge(
|
|
619
|
+
edge: t.GraphEdge,
|
|
620
|
+
sourceAgentId: string
|
|
621
|
+
): t.GenericTool[] {
|
|
622
|
+
const tools: t.GenericTool[] = [];
|
|
623
|
+
const destinations = Array.isArray(edge.to) ? edge.to : [edge.to];
|
|
624
|
+
const maxResultChars =
|
|
625
|
+
edge.maxResultChars ?? DEFAULT_DELEGATE_MAX_RESULT_CHARS;
|
|
626
|
+
|
|
627
|
+
for (const destination of destinations) {
|
|
628
|
+
const toolName = `${Constants.LC_DELEGATE_TO_}${destination}`;
|
|
629
|
+
const destContext = this.agentContexts.get(destination);
|
|
630
|
+
const toolDescription =
|
|
631
|
+
edge.description ??
|
|
632
|
+
this.buildDefaultDelegateDescription(destContext, destination);
|
|
633
|
+
|
|
634
|
+
const hasPromptInput =
|
|
635
|
+
edge.prompt != null && typeof edge.prompt === 'string';
|
|
636
|
+
const promptInputDescription = hasPromptInput ? edge.prompt : undefined;
|
|
637
|
+
const promptKey = edge.promptKey ?? 'instructions';
|
|
638
|
+
|
|
639
|
+
/** Capture registry reference — Map populated in createWorkflow() */
|
|
640
|
+
const registry = this.subgraphRegistry;
|
|
641
|
+
|
|
642
|
+
tools.push(
|
|
643
|
+
tool(
|
|
644
|
+
async (rawInput, config) => {
|
|
645
|
+
const input = rawInput as Record<string, unknown>;
|
|
646
|
+
const subgraph = registry.get(destination);
|
|
647
|
+
if (!subgraph) {
|
|
648
|
+
throw new Error(
|
|
649
|
+
`Delegate target "${destination}" subgraph not found in registry. ` +
|
|
650
|
+
'This is a bug: createWorkflow() should have populated the subgraph registry.'
|
|
651
|
+
);
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
const state = getCurrentTaskInput() as t.BaseGraphState;
|
|
655
|
+
let childMessages = [...state.messages];
|
|
656
|
+
|
|
657
|
+
/** Inject instructions as HumanMessage if provided by the parent LLM */
|
|
658
|
+
if (
|
|
659
|
+
hasPromptInput &&
|
|
660
|
+
promptKey in input &&
|
|
661
|
+
input[promptKey] != null
|
|
662
|
+
) {
|
|
663
|
+
childMessages = [
|
|
664
|
+
...childMessages,
|
|
665
|
+
new HumanMessage(String(input[promptKey])),
|
|
666
|
+
];
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
const childState: t.BaseGraphState = {
|
|
670
|
+
messages: childMessages,
|
|
671
|
+
};
|
|
672
|
+
|
|
673
|
+
console.debug(
|
|
674
|
+
`[MultiAgentGraph] Delegate "${sourceAgentId}" -> "${destination}" START ` +
|
|
675
|
+
`(messages: ${childMessages.length})`
|
|
676
|
+
);
|
|
677
|
+
|
|
678
|
+
try {
|
|
679
|
+
/**
|
|
680
|
+
* Invoke the child subgraph with config propagation.
|
|
681
|
+
* Config carries callbacks (for SSE streaming), abort signal,
|
|
682
|
+
* and configurable data (thread_id, user_id) to the child.
|
|
683
|
+
*/
|
|
684
|
+
const result = await subgraph.invoke(childState, config);
|
|
685
|
+
|
|
686
|
+
const resultText = MultiAgentGraph.extractDelegateResult(
|
|
687
|
+
result.messages,
|
|
688
|
+
destination
|
|
689
|
+
);
|
|
690
|
+
const truncatedResult = MultiAgentGraph.truncateDelegateResult(
|
|
691
|
+
resultText,
|
|
692
|
+
maxResultChars
|
|
693
|
+
);
|
|
694
|
+
|
|
695
|
+
console.debug(
|
|
696
|
+
`[MultiAgentGraph] Delegate "${sourceAgentId}" -> "${destination}" DONE ` +
|
|
697
|
+
`(result: ${resultText.length} chars` +
|
|
698
|
+
`${truncatedResult.length < resultText.length ? `, truncated to ${truncatedResult.length}` : ''})`
|
|
699
|
+
);
|
|
700
|
+
|
|
701
|
+
return truncatedResult;
|
|
702
|
+
} catch (err) {
|
|
703
|
+
const errorMessage =
|
|
704
|
+
err instanceof Error ? err.message : String(err);
|
|
705
|
+
console.error(
|
|
706
|
+
`[MultiAgentGraph] Delegate "${sourceAgentId}" -> "${destination}" ERROR:`,
|
|
707
|
+
errorMessage
|
|
708
|
+
);
|
|
709
|
+
return `[Delegate to "${destination}" failed: ${errorMessage}]`;
|
|
710
|
+
}
|
|
711
|
+
},
|
|
712
|
+
{
|
|
713
|
+
name: toolName,
|
|
714
|
+
schema: hasPromptInput
|
|
715
|
+
? {
|
|
716
|
+
type: 'object',
|
|
717
|
+
properties: {
|
|
718
|
+
[promptKey]: {
|
|
719
|
+
type: 'string',
|
|
720
|
+
description: promptInputDescription as string,
|
|
721
|
+
},
|
|
722
|
+
},
|
|
723
|
+
required: [],
|
|
724
|
+
}
|
|
725
|
+
: { type: 'object', properties: {}, required: [] },
|
|
726
|
+
description: toolDescription,
|
|
727
|
+
}
|
|
728
|
+
)
|
|
729
|
+
);
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
return tools;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
/**
|
|
736
|
+
* Extract the final text result from a child agent's output messages.
|
|
737
|
+
* Walks backwards to find the last AIMessage with text content.
|
|
738
|
+
* Handles both string content and array content (multi-modal messages).
|
|
739
|
+
* @param messages - The child agent's output messages
|
|
740
|
+
* @param agentId - The child agent ID (for fallback message)
|
|
741
|
+
*/
|
|
742
|
+
static extractDelegateResult(
|
|
743
|
+
messages: BaseMessage[],
|
|
744
|
+
agentId: string
|
|
745
|
+
): string {
|
|
746
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
747
|
+
const msg = messages[i];
|
|
748
|
+
if (msg.getType() !== 'ai') continue;
|
|
749
|
+
|
|
750
|
+
const content = msg.content;
|
|
751
|
+
if (typeof content === 'string' && content.trim()) {
|
|
752
|
+
return content.trim();
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
/** Handle array content (multi-modal messages with text blocks) */
|
|
756
|
+
if (Array.isArray(content)) {
|
|
757
|
+
const textParts = content
|
|
758
|
+
.filter(
|
|
759
|
+
(
|
|
760
|
+
block
|
|
761
|
+
): block is {
|
|
762
|
+
type: string;
|
|
763
|
+
text: string;
|
|
764
|
+
} =>
|
|
765
|
+
typeof block === 'object' &&
|
|
766
|
+
block !== null &&
|
|
767
|
+
'type' in block &&
|
|
768
|
+
block.type === 'text' &&
|
|
769
|
+
'text' in block &&
|
|
770
|
+
typeof block.text === 'string'
|
|
771
|
+
)
|
|
772
|
+
.map((block) => block.text);
|
|
773
|
+
|
|
774
|
+
const text = textParts.join('\n').trim();
|
|
775
|
+
if (text) return text;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
return `[Agent "${agentId}" completed but produced no text output]`;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
/**
|
|
783
|
+
* Truncate delegate result using head/tail strategy (60/40 split).
|
|
784
|
+
* Preserves the beginning (key findings) and end (conclusions).
|
|
785
|
+
* Matches the TaskTool.truncateResult pattern from Ranger.
|
|
786
|
+
* @param result - The full result text
|
|
787
|
+
* @param maxChars - Maximum allowed characters
|
|
788
|
+
*/
|
|
789
|
+
static truncateDelegateResult(result: string, maxChars: number): string {
|
|
790
|
+
if (!result || result.length <= maxChars) {
|
|
791
|
+
return result;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
const truncationNotice =
|
|
795
|
+
'\n\n[... delegate output truncated — middle section omitted to fit parent context ...]\n\n';
|
|
796
|
+
const available = maxChars - truncationNotice.length;
|
|
797
|
+
if (available <= 0) {
|
|
798
|
+
return result.substring(0, maxChars);
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
const headSize = Math.floor(available * 0.6);
|
|
802
|
+
const tailSize = available - headSize;
|
|
803
|
+
|
|
804
|
+
return (
|
|
805
|
+
result.substring(0, headSize) +
|
|
806
|
+
truncationNotice +
|
|
807
|
+
result.substring(result.length - tailSize)
|
|
808
|
+
);
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
/**
|
|
812
|
+
* Build a meaningful default description for a delegate tool.
|
|
813
|
+
* @param destContext - AgentContext of the destination agent
|
|
814
|
+
* @param destinationId - Raw agent ID (fallback)
|
|
815
|
+
*/
|
|
816
|
+
private buildDefaultDelegateDescription(
|
|
817
|
+
destContext: import('@/agents/AgentContext').AgentContext | undefined,
|
|
818
|
+
destinationId: string
|
|
819
|
+
): string {
|
|
820
|
+
const displayName = destContext?.name ?? destinationId;
|
|
821
|
+
const agentDescription = destContext?.description;
|
|
822
|
+
|
|
823
|
+
if (agentDescription != null && agentDescription !== '') {
|
|
824
|
+
return `Delegate task to "${displayName}": ${agentDescription}. The agent will execute and return its result.`;
|
|
825
|
+
}
|
|
826
|
+
return `Delegate task to "${displayName}" and receive its result.`;
|
|
827
|
+
}
|
|
828
|
+
|
|
550
829
|
/**
|
|
551
830
|
* Create a complete agent subgraph (similar to createReactAgent)
|
|
552
831
|
*/
|
|
@@ -925,6 +1204,9 @@ export class MultiAgentGraph extends StandardGraph {
|
|
|
925
1204
|
/** Agent subgraph (includes agent + tools) */
|
|
926
1205
|
const agentSubgraph = this.createAgentSubgraph(agentId);
|
|
927
1206
|
|
|
1207
|
+
/** Register subgraph for delegate tools (lazy reference resolution) */
|
|
1208
|
+
this.subgraphRegistry.set(agentId, agentSubgraph);
|
|
1209
|
+
|
|
928
1210
|
/** Wrapper function that handles agentMessages channel, handoff reception, and conditional routing */
|
|
929
1211
|
const agentWrapper = async (
|
|
930
1212
|
state: t.MultiAgentGraphState,
|