@librechat/agents 3.0.0-rc1 → 3.0.0-rc3

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.
@@ -28,6 +28,9 @@ export type SystemCallbacks = {
28
28
  export type BaseGraphState = {
29
29
  messages: BaseMessage[];
30
30
  };
31
+ export type MultiAgentGraphState = BaseGraphState & {
32
+ agentMessages?: BaseMessage[];
33
+ };
31
34
  export type IState = BaseGraphState;
32
35
  export interface EventHandler {
33
36
  handle(event: string, data: StreamEventData | ModelEndData | RunStep | RunStepDeltaEvent | MessageDeltaEvent | ReasoningDeltaEvent | {
@@ -46,6 +49,19 @@ export type CompiledStateWorkflow = CompiledStateGraph<StateType<{
46
49
  }, {
47
50
  messages: BinaryOperatorAggregate<BaseMessage[], BaseMessage[]>;
48
51
  }, StateDefinition>;
52
+ export type CompiledMultiAgentWorkflow = CompiledStateGraph<StateType<{
53
+ messages: BinaryOperatorAggregate<BaseMessage[], BaseMessage[]>;
54
+ agentMessages: BinaryOperatorAggregate<BaseMessage[], BaseMessage[]>;
55
+ }>, UpdateType<{
56
+ messages: BinaryOperatorAggregate<BaseMessage[], BaseMessage[]>;
57
+ agentMessages: BinaryOperatorAggregate<BaseMessage[], BaseMessage[]>;
58
+ }>, string, {
59
+ messages: BinaryOperatorAggregate<BaseMessage[], BaseMessage[]>;
60
+ agentMessages: BinaryOperatorAggregate<BaseMessage[], BaseMessage[]>;
61
+ }, {
62
+ messages: BinaryOperatorAggregate<BaseMessage[], BaseMessage[]>;
63
+ agentMessages: BinaryOperatorAggregate<BaseMessage[], BaseMessage[]>;
64
+ }, StateDefinition>;
49
65
  export type CompiledAgentWorfklow = CompiledStateGraph<{
50
66
  messages: BaseMessage[];
51
67
  }, {
@@ -185,17 +201,35 @@ export type StandardGraphInput = {
185
201
  indexTokenCountMap?: Record<string, number>;
186
202
  };
187
203
  export type GraphEdge = {
188
- /** Use a list for multiple sources */
204
+ /** Agent ID, use a list for multiple sources */
189
205
  from: string | string[];
190
- /** Use a list for multiple destinations */
206
+ /** Agent ID, use a list for multiple destinations */
191
207
  to: string | string[];
192
208
  description?: string;
193
209
  /** Can return boolean or specific destination(s) */
194
210
  condition?: (state: BaseGraphState) => boolean | string | string[];
195
211
  /** 'handoff' creates tools for dynamic routing, 'direct' creates direct edges, which also allow parallel execution */
196
212
  edgeType?: 'handoff' | 'direct';
197
- /** Optional prompt to add when transitioning through this edge */
198
- promptInstructions?: string | ((messages: BaseMessage[]) => string | undefined);
213
+ /**
214
+ * For direct edges: Optional prompt to add when transitioning through this edge.
215
+ * String prompts can include variables like {results} which will be replaced with
216
+ * messages from startIndex onwards. When {results} is used, excludeResults defaults to true.
217
+ *
218
+ * For handoff edges: Description for the input parameter that the handoff tool accepts,
219
+ * allowing the supervisor to pass specific instructions/context to the transferred agent.
220
+ */
221
+ prompt?: string | ((messages: BaseMessage[], runStartIndex: number) => string | undefined);
222
+ /**
223
+ * When true, excludes messages from startIndex when adding prompt.
224
+ * Automatically set to true when {results} variable is used in prompt.
225
+ */
226
+ excludeResults?: boolean;
227
+ /**
228
+ * For handoff edges: Customizes the parameter name for the handoff input.
229
+ * Defaults to "instructions" if not specified.
230
+ * Only applies when prompt is provided for handoff edges.
231
+ */
232
+ promptKey?: string;
199
233
  };
200
234
  export type MultiAgentGraphInput = StandardGraphInput & {
201
235
  edges: GraphEdge[];
@@ -92,9 +92,13 @@ export type MultiAgentGraphConfig = {
92
92
  agents: g.AgentInputs[];
93
93
  edges: g.GraphEdge[];
94
94
  };
95
+ export type StandardGraphConfig = Omit<MultiAgentGraphConfig, 'edges' | 'type'> & {
96
+ type?: 'standard';
97
+ signal?: AbortSignal;
98
+ };
95
99
  export type RunConfig = {
96
100
  runId: string;
97
- graphConfig: LegacyGraphConfig | MultiAgentGraphConfig;
101
+ graphConfig: LegacyGraphConfig | StandardGraphConfig | MultiAgentGraphConfig;
98
102
  customHandlers?: Record<string, g.EventHandler>;
99
103
  returnContent?: boolean;
100
104
  tokenCounter?: TokenCounter;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@librechat/agents",
3
- "version": "3.0.00-rc1",
3
+ "version": "3.0.00-rc3",
4
4
  "main": "./dist/cjs/main.cjs",
5
5
  "module": "./dist/esm/main.mjs",
6
6
  "types": "./dist/types/index.d.ts",
@@ -60,6 +60,10 @@
60
60
  "multi-agent-parallel": "node -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/multi-agent-parallel.ts",
61
61
  "multi-agent-sequence": "node -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/multi-agent-sequence.ts",
62
62
  "multi-agent-conditional": "node -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/multi-agent-conditional.ts",
63
+ "multi-agent-supervisor": "node -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/multi-agent-supervisor.ts",
64
+ "multi-agent-list-handoff": "node -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/test-multi-agent-list-handoff.ts",
65
+ "test-handoff-input": "node -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/test-handoff-input.ts",
66
+ "test-custom-prompt-key": "node -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/test-custom-prompt-key.ts",
63
67
  "script2": "node -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/proto/example_test.ts",
64
68
  "script3": "node -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/proto/example_test_anthropic.ts",
65
69
  "script4": "node -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/cli4.ts --name 'Jo' --location 'New York, NY'",
@@ -1,6 +1,11 @@
1
1
  import { z } from 'zod';
2
2
  import { tool } from '@langchain/core/tools';
3
- import { ToolMessage, HumanMessage } from '@langchain/core/messages';
3
+ import {
4
+ ToolMessage,
5
+ HumanMessage,
6
+ getBufferString,
7
+ } from '@langchain/core/messages';
8
+ import { ChatPromptTemplate } from '@langchain/core/prompts';
4
9
  import {
5
10
  END,
6
11
  START,
@@ -137,37 +142,52 @@ export class MultiAgentGraph extends StandardGraph {
137
142
  const tools: t.GenericTool[] = [];
138
143
  const destinations = Array.isArray(edge.to) ? edge.to : [edge.to];
139
144
 
140
- // If there's a condition, create a single conditional handoff tool
145
+ /** If there's a condition, create a single conditional handoff tool */
141
146
  if (edge.condition != null) {
142
147
  const toolName = 'conditional_transfer';
143
148
  const toolDescription =
144
149
  edge.description ?? 'Conditionally transfer control based on state';
145
150
 
151
+ /** Check if we have a prompt for handoff input */
152
+ const hasHandoffInput =
153
+ edge.prompt != null && typeof edge.prompt === 'string';
154
+ const handoffInputDescription = hasHandoffInput ? edge.prompt : undefined;
155
+ const promptKey = edge.promptKey ?? 'instructions';
156
+
146
157
  tools.push(
147
158
  tool(
148
- async (_, config) => {
159
+ async (input: Record<string, unknown>, config) => {
149
160
  const state = getCurrentTaskInput() as t.BaseGraphState;
150
161
  const toolCallId =
151
162
  (config as ToolRunnableConfig | undefined)?.toolCall?.id ??
152
163
  'unknown';
153
164
 
154
- // Evaluate condition
165
+ /** Evaluated condition */
155
166
  const result = edge.condition!(state);
156
167
  let destination: string;
157
168
 
158
169
  if (typeof result === 'boolean') {
159
- // If true, use first destination; if false, don't transfer
170
+ /** If true, use first destination; if false, don't transfer */
160
171
  if (!result) return null;
161
172
  destination = destinations[0];
162
173
  } else if (typeof result === 'string') {
163
174
  destination = result;
164
175
  } else {
165
- // Array of destinations - for now, use the first
176
+ /** Array of destinations - for now, use the first */
166
177
  destination = Array.isArray(result) ? result[0] : destinations[0];
167
178
  }
168
179
 
180
+ let content = `Conditionally transferred to ${destination}`;
181
+ if (
182
+ hasHandoffInput &&
183
+ promptKey in input &&
184
+ input[promptKey] != null
185
+ ) {
186
+ content += `\n\n${promptKey.charAt(0).toUpperCase() + promptKey.slice(1)}: ${input[promptKey]}`;
187
+ }
188
+
169
189
  const toolMessage = new ToolMessage({
170
- content: `Conditionally transferred to ${destination}`,
190
+ content,
171
191
  name: toolName,
172
192
  tool_call_id: toolCallId,
173
193
  });
@@ -180,26 +200,51 @@ export class MultiAgentGraph extends StandardGraph {
180
200
  },
181
201
  {
182
202
  name: toolName,
183
- schema: z.object({}),
203
+ schema: hasHandoffInput
204
+ ? z.object({
205
+ [promptKey]: z
206
+ .string()
207
+ .optional()
208
+ .describe(handoffInputDescription as string),
209
+ })
210
+ : z.object({}),
184
211
  description: toolDescription,
185
212
  }
186
213
  )
187
214
  );
188
215
  } else {
189
- // Create individual tools for each destination
216
+ /** Create individual tools for each destination */
190
217
  for (const destination of destinations) {
191
218
  const toolName = `transfer_to_${destination}`;
192
219
  const toolDescription =
193
220
  edge.description ?? `Transfer control to agent '${destination}'`;
194
221
 
222
+ /** Check if we have a prompt for handoff input */
223
+ const hasHandoffInput =
224
+ edge.prompt != null && typeof edge.prompt === 'string';
225
+ const handoffInputDescription = hasHandoffInput
226
+ ? edge.prompt
227
+ : undefined;
228
+ const promptKey = edge.promptKey ?? 'instructions';
229
+
195
230
  tools.push(
196
231
  tool(
197
- async (_, config) => {
232
+ async (input: Record<string, unknown>, config) => {
198
233
  const toolCallId =
199
234
  (config as ToolRunnableConfig | undefined)?.toolCall?.id ??
200
235
  'unknown';
236
+
237
+ let content = `Successfully transferred to ${destination}`;
238
+ if (
239
+ hasHandoffInput &&
240
+ promptKey in input &&
241
+ input[promptKey] != null
242
+ ) {
243
+ content += `\n\n${promptKey.charAt(0).toUpperCase() + promptKey.slice(1)}: ${input[promptKey]}`;
244
+ }
245
+
201
246
  const toolMessage = new ToolMessage({
202
- content: `Successfully transferred to ${destination}`,
247
+ content,
203
248
  name: toolName,
204
249
  tool_call_id: toolCallId,
205
250
  });
@@ -214,7 +259,14 @@ export class MultiAgentGraph extends StandardGraph {
214
259
  },
215
260
  {
216
261
  name: toolName,
217
- schema: z.object({}),
262
+ schema: hasHandoffInput
263
+ ? z.object({
264
+ [promptKey]: z
265
+ .string()
266
+ .optional()
267
+ .describe(handoffInputDescription as string),
268
+ })
269
+ : z.object({}),
218
270
  description: toolDescription,
219
271
  }
220
272
  )
@@ -229,14 +281,14 @@ export class MultiAgentGraph extends StandardGraph {
229
281
  * Create a complete agent subgraph (similar to createReactAgent)
230
282
  */
231
283
  private createAgentSubgraph(agentId: string): t.CompiledAgentWorfklow {
232
- // This is essentially the same as createAgentNode from StandardGraph
284
+ /** This is essentially the same as `createAgentNode` from `StandardGraph` */
233
285
  return this.createAgentNode(agentId);
234
286
  }
235
287
 
236
288
  /**
237
289
  * Create the multi-agent workflow with dynamic handoffs
238
290
  */
239
- override createWorkflow(): t.CompiledStateWorkflow {
291
+ override createWorkflow(): t.CompiledMultiAgentWorkflow {
240
292
  const StateAnnotation = Annotation.Root({
241
293
  messages: Annotation<BaseMessage[]>({
242
294
  reducer: (a, b) => {
@@ -249,6 +301,12 @@ export class MultiAgentGraph extends StandardGraph {
249
301
  },
250
302
  default: () => [],
251
303
  }),
304
+ /** Channel for passing filtered messages to agents when excludeResults is true */
305
+ agentMessages: Annotation<BaseMessage[]>({
306
+ /** Replaces state entirely */
307
+ reducer: (a, b) => b,
308
+ default: () => [],
309
+ }),
252
310
  });
253
311
 
254
312
  const builder = new StateGraph(StateAnnotation);
@@ -277,18 +335,40 @@ export class MultiAgentGraph extends StandardGraph {
277
335
  }
278
336
  }
279
337
 
280
- // If agent has handoff destinations, add END to possible ends
281
- // If agent only has direct destinations, it naturally ends without explicit END
338
+ /** If agent has handoff destinations, add END to possible ends
339
+ * If agent only has direct destinations, it naturally ends without explicit END
340
+ */
282
341
  const destinations = new Set([...handoffDestinations]);
283
342
  if (handoffDestinations.size > 0 || directDestinations.size === 0) {
284
343
  destinations.add(END);
285
344
  }
286
345
 
287
- // Create the agent subgraph (includes agent + tools)
346
+ /** Agent subgraph (includes agent + tools) */
288
347
  const agentSubgraph = this.createAgentSubgraph(agentId);
289
348
 
290
- // Add the agent as a node with its possible destinations
291
- builder.addNode(agentId, agentSubgraph, {
349
+ /** Wrapper function that handles agentMessages channel */
350
+ const agentWrapper = async (
351
+ state: t.MultiAgentGraphState
352
+ ): Promise<t.MultiAgentGraphState> => {
353
+ if (state.agentMessages != null && state.agentMessages.length > 0) {
354
+ /** Temporary state with messages replaced by `agentMessages` */
355
+ const transformedState: t.MultiAgentGraphState = {
356
+ ...state,
357
+ messages: state.agentMessages,
358
+ };
359
+ const result = await agentSubgraph.invoke(transformedState);
360
+ return {
361
+ ...result,
362
+ /** Clear agentMessages for next agent */
363
+ agentMessages: [],
364
+ };
365
+ } else {
366
+ return await agentSubgraph.invoke(state);
367
+ }
368
+ };
369
+
370
+ /** Wrapped agent as a node with its possible destinations */
371
+ builder.addNode(agentId, agentWrapper, {
292
372
  ends: Array.from(destinations),
293
373
  });
294
374
  }
@@ -300,7 +380,8 @@ export class MultiAgentGraph extends StandardGraph {
300
380
  builder.addEdge(START, startNode);
301
381
  }
302
382
 
303
- /** Add direct edges for automatic transitions
383
+ /**
384
+ * Add direct edges for automatic transitions
304
385
  * Group edges by destination to handle fan-in scenarios
305
386
  */
306
387
  const edgesByDestination = new Map<string, t.GraphEdge[]>();
@@ -318,38 +399,74 @@ export class MultiAgentGraph extends StandardGraph {
318
399
  for (const [destination, edges] of edgesByDestination) {
319
400
  /** Checks if this is a fan-in scenario with prompt instructions */
320
401
  const edgesWithPrompt = edges.filter(
321
- (edge) =>
322
- edge.promptInstructions != null && edge.promptInstructions !== ''
402
+ (edge) => edge.prompt != null && edge.prompt !== ''
323
403
  );
324
404
 
325
405
  if (edgesWithPrompt.length > 0) {
326
- // Fan-in with prompt: create a single wrapper node for this destination
406
+ /**
407
+ * Single wrapper node for destination (Fan-in with prompt)
408
+ */
327
409
  const wrapperNodeId = `fan_in_${destination}_prompt`;
328
-
329
- // Use the first edge's prompt instructions (they should all be the same for fan-in)
330
- const promptInstructions = edgesWithPrompt[0].promptInstructions;
410
+ /**
411
+ * First edge's `prompt`
412
+ * (they should all be the same for fan-in)
413
+ */
414
+ const prompt = edgesWithPrompt[0].prompt;
415
+ /**
416
+ * First edge's `excludeResults` flag
417
+ * (they should all be the same for fan-in)
418
+ */
419
+ const excludeResults = edgesWithPrompt[0].excludeResults;
331
420
 
332
421
  builder.addNode(wrapperNodeId, async (state: t.BaseGraphState) => {
333
422
  let promptText: string | undefined;
334
-
335
- if (typeof promptInstructions === 'function') {
336
- promptText = promptInstructions(state.messages);
337
- } else {
338
- promptText = promptInstructions;
423
+ let effectiveExcludeResults = excludeResults;
424
+
425
+ if (typeof prompt === 'function') {
426
+ promptText = prompt(state.messages, this.startIndex);
427
+ } else if (prompt != null) {
428
+ if (prompt.includes('{results}')) {
429
+ const resultsMessages = state.messages.slice(this.startIndex);
430
+ const resultsString = getBufferString(resultsMessages);
431
+ const promptTemplate = ChatPromptTemplate.fromTemplate(prompt);
432
+ const formattedPromptValue = await promptTemplate.invoke({
433
+ results: resultsString,
434
+ });
435
+ promptText = formattedPromptValue.messages[0].content.toString();
436
+ effectiveExcludeResults =
437
+ excludeResults !== false && promptText !== '';
438
+ } else {
439
+ promptText = prompt;
440
+ }
339
441
  }
340
442
 
341
443
  if (promptText != null && promptText !== '') {
342
- // Return state with the prompt message added
444
+ if (
445
+ effectiveExcludeResults == null ||
446
+ effectiveExcludeResults === false
447
+ ) {
448
+ return {
449
+ messages: [new HumanMessage(promptText)],
450
+ };
451
+ }
452
+
453
+ /** When `excludeResults` is true, use agentMessages channel
454
+ * to pass filtered messages + prompt to the destination agent
455
+ */
456
+ const filteredMessages = state.messages.slice(0, this.startIndex);
343
457
  return {
344
- messages: [...state.messages, new HumanMessage(promptText)],
458
+ messages: [new HumanMessage(promptText)],
459
+ agentMessages: messagesStateReducer(filteredMessages, [
460
+ new HumanMessage(promptText),
461
+ ]),
345
462
  };
346
463
  }
347
464
 
348
- // No prompt needed, return empty update
465
+ /** No prompt needed, return empty update */
349
466
  return {};
350
467
  });
351
468
 
352
- // Add edges from all sources to the wrapper, then wrapper to destination
469
+ /** Add edges from all sources to the wrapper, then wrapper to destination */
353
470
  for (const edge of edges) {
354
471
  const sources = Array.isArray(edge.from) ? edge.from : [edge.from];
355
472
  for (const source of sources) {
@@ -359,12 +476,12 @@ export class MultiAgentGraph extends StandardGraph {
359
476
  }
360
477
  }
361
478
 
362
- // Single edge from wrapper to destination
479
+ /** Single edge from wrapper to destination */
363
480
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
364
481
  /** @ts-ignore */
365
482
  builder.addEdge(wrapperNodeId, destination);
366
483
  } else {
367
- // No prompt instructions, add direct edges
484
+ /** No prompt instructions, add direct edges */
368
485
  for (const edge of edges) {
369
486
  const sources = Array.isArray(edge.from) ? edge.from : [edge.from];
370
487
  for (const source of sources) {
package/src/run.ts CHANGED
@@ -73,7 +73,7 @@ export class Run<_T extends t.BaseGraphState> {
73
73
  this.Graph.handlerRegistry = handlerRegistry;
74
74
  }
75
75
  } else {
76
- // Default to legacy graph for 'standard' or undefined type
76
+ /** Default to legacy graph for 'standard' or undefined type */
77
77
  this.graphRunnable = this.createLegacyGraph(config.graphConfig);
78
78
  if (this.Graph) {
79
79
  this.Graph.compileOptions =
@@ -86,25 +86,38 @@ export class Run<_T extends t.BaseGraphState> {
86
86
  }
87
87
 
88
88
  private createLegacyGraph(
89
- config: t.LegacyGraphConfig
89
+ config: t.LegacyGraphConfig | t.StandardGraphConfig
90
90
  ): t.CompiledStateWorkflow {
91
- const {
92
- type: _type,
93
- llmConfig,
94
- signal,
95
- tools = [],
96
- ...agentInputs
97
- } = config;
98
- const { provider, ...clientOptions } = llmConfig;
99
-
100
- /** TEMP: Create agent configuration for the single agent */
101
- const agentConfig: t.AgentInputs = {
102
- ...agentInputs,
103
- tools,
104
- provider,
105
- clientOptions,
106
- agentId: 'default',
107
- };
91
+ let agentConfig: t.AgentInputs;
92
+ let signal: AbortSignal | undefined;
93
+
94
+ /** Check if this is a multi-agent style config (has agents array) */
95
+ if ('agents' in config && Array.isArray(config.agents)) {
96
+ if (config.agents.length === 0) {
97
+ throw new Error('At least one agent must be provided');
98
+ }
99
+ agentConfig = config.agents[0];
100
+ signal = config.signal;
101
+ } else {
102
+ /** Legacy path: build agent config from llmConfig */
103
+ const {
104
+ type: _type,
105
+ llmConfig,
106
+ signal: legacySignal,
107
+ tools = [],
108
+ ...agentInputs
109
+ } = config as t.LegacyGraphConfig;
110
+ const { provider, ...clientOptions } = llmConfig;
111
+
112
+ agentConfig = {
113
+ ...agentInputs,
114
+ tools,
115
+ provider,
116
+ clientOptions,
117
+ agentId: 'default',
118
+ };
119
+ signal = legacySignal;
120
+ }
108
121
 
109
122
  const standardGraph = new StandardGraph({
110
123
  signal,
@@ -113,10 +126,8 @@ export class Run<_T extends t.BaseGraphState> {
113
126
  tokenCounter: this.tokenCounter,
114
127
  indexTokenCountMap: this.indexTokenCountMap,
115
128
  });
116
- // propagate compile options from graph config
117
- standardGraph.compileOptions = (
118
- config as t.LegacyGraphConfig
119
- ).compileOptions;
129
+ /** Propagate compile options from graph config */
130
+ standardGraph.compileOptions = config.compileOptions;
120
131
  this.Graph = standardGraph;
121
132
  return standardGraph.createWorkflow();
122
133
  }
@@ -145,7 +156,7 @@ export class Run<_T extends t.BaseGraphState> {
145
156
  static async create<T extends t.BaseGraphState>(
146
157
  config: t.RunConfig
147
158
  ): Promise<Run<T>> {
148
- // Create tokenCounter if indexTokenCountMap is provided but tokenCounter is not
159
+ /** Create tokenCounter if indexTokenCountMap is provided but tokenCounter is not */
149
160
  if (config.indexTokenCountMap && !config.tokenCounter) {
150
161
  config.tokenCounter = await createTokenCounter();
151
162
  }
@@ -167,29 +167,33 @@ async function testParallelMultiAgent() {
167
167
  description: 'Aggregate analysis results',
168
168
  edgeType: 'direct', // Fan-in is also direct
169
169
  // Add prompt when all analysts have provided input
170
- promptInstructions: (messages) => {
171
- // Check if we have analysis content from all three analysts
172
- // Look for the specific headers each analyst uses
173
- const aiMessages = messages.filter((msg) => msg._getType() === 'ai');
174
- const messageContent = aiMessages.map((msg) => msg.content).join('\n');
175
-
176
- const hasFinancialAnalysis = messageContent.includes(
177
- 'FINANCIAL ANALYSIS:'
178
- );
179
- const hasTechnicalAnalysis = messageContent.includes(
180
- 'TECHNICAL ANALYSIS:'
181
- );
182
- const hasMarketAnalysis = messageContent.includes('MARKET ANALYSIS:');
183
-
184
- console.log(
185
- `Checking for analyses - Financial: ${hasFinancialAnalysis}, Technical: ${hasTechnicalAnalysis}, Market: ${hasMarketAnalysis}`
186
- );
187
-
188
- if (hasFinancialAnalysis && hasTechnicalAnalysis && hasMarketAnalysis) {
189
- return 'Based on the comprehensive analyses from all three specialist teams above, please synthesize their insights into a cohesive executive summary. Focus on the key findings, common themes, and strategic implications across the financial, technical, and market perspectives.';
190
- }
191
- return undefined; // No prompt if we haven't received all analyst inputs
192
- },
170
+ // prompt: (messages, runStartIndex) => {
171
+ // // Check if we have analysis content from all three analysts
172
+ // // Look for the specific headers each analyst uses
173
+ // const aiMessages = messages.filter(
174
+ // (msg, index) => msg.getType() === 'ai' && index >= runStartIndex
175
+ // );
176
+ // const messageContent = aiMessages.map((msg) => msg.content).join('\n');
177
+
178
+ // const hasFinancialAnalysis = messageContent.includes(
179
+ // 'FINANCIAL ANALYSIS:'
180
+ // );
181
+ // const hasTechnicalAnalysis = messageContent.includes(
182
+ // 'TECHNICAL ANALYSIS:'
183
+ // );
184
+ // const hasMarketAnalysis = messageContent.includes('MARKET ANALYSIS:');
185
+
186
+ // console.log(
187
+ // `Checking for analyses - Financial: ${hasFinancialAnalysis}, Technical: ${hasTechnicalAnalysis}, Market: ${hasMarketAnalysis}`
188
+ // );
189
+
190
+ // if (hasFinancialAnalysis && hasTechnicalAnalysis && hasMarketAnalysis) {
191
+ // return 'Based on the comprehensive analyses from all three specialist teams above, please synthesize their insights into a cohesive executive summary. Focus on the key findings, common themes, and strategic implications across the financial, technical, and market perspectives.';
192
+ // }
193
+ // return undefined; // No prompt if we haven't received all analyst inputs
194
+ // },
195
+ prompt:
196
+ 'Based on the comprehensive analyses from all three specialist teams below, please synthesize their insights into a cohesive executive summary. Focus on the key findings, common themes, and strategic implications across the financial, technical, and market perspectives.\n\n{results}',
193
197
  },
194
198
  ];
195
199