@librechat/agents 3.0.64 → 3.0.65

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.
@@ -27,6 +27,7 @@ export class AgentContext {
27
27
  ): AgentContext {
28
28
  const {
29
29
  agentId,
30
+ name,
30
31
  provider,
31
32
  clientOptions,
32
33
  tools,
@@ -43,6 +44,7 @@ export class AgentContext {
43
44
 
44
45
  const agentContext = new AgentContext({
45
46
  agentId,
47
+ name: name ?? agentId,
46
48
  provider,
47
49
  clientOptions,
48
50
  maxContextTokens,
@@ -85,6 +87,8 @@ export class AgentContext {
85
87
 
86
88
  /** Agent identifier */
87
89
  agentId: string;
90
+ /** Human-readable name for this agent (used in handoff context). Falls back to agentId if not provided. */
91
+ name?: string;
88
92
  /** Provider for this specific agent */
89
93
  provider: Providers;
90
94
  /** Client options for this agent */
@@ -145,9 +149,17 @@ export class AgentContext {
145
149
  tokenCalculationPromise?: Promise<void>;
146
150
  /** Format content blocks as strings (for legacy compatibility) */
147
151
  useLegacyContent: boolean = false;
152
+ /**
153
+ * Handoff context when this agent receives control via handoff.
154
+ * Contains source agent name for system message identity context.
155
+ */
156
+ handoffContext?: {
157
+ sourceAgentName: string;
158
+ };
148
159
 
149
160
  constructor({
150
161
  agentId,
162
+ name,
151
163
  provider,
152
164
  clientOptions,
153
165
  maxContextTokens,
@@ -164,6 +176,7 @@ export class AgentContext {
164
176
  useLegacyContent,
165
177
  }: {
166
178
  agentId: string;
179
+ name?: string;
167
180
  provider: Providers;
168
181
  clientOptions?: t.ClientOptions;
169
182
  maxContextTokens?: number;
@@ -180,6 +193,7 @@ export class AgentContext {
180
193
  useLegacyContent?: boolean;
181
194
  }) {
182
195
  this.agentId = agentId;
196
+ this.name = name;
183
197
  this.provider = provider;
184
198
  this.clientOptions = clientOptions;
185
199
  this.maxContextTokens = maxContextTokens;
@@ -293,27 +307,61 @@ export class AgentContext {
293
307
 
294
308
  /**
295
309
  * Builds the raw instructions string (without creating SystemMessage).
310
+ * Includes agent identity preamble and handoff context when available.
296
311
  */
297
312
  private buildInstructionsString(): string {
298
- let result = this.instructions ?? '';
313
+ const parts: string[] = [];
314
+
315
+ /** Build agent identity and handoff context preamble */
316
+ const identityPreamble = this.buildIdentityPreamble();
317
+ if (identityPreamble) {
318
+ parts.push(identityPreamble);
319
+ }
299
320
 
321
+ /** Add main instructions */
322
+ if (this.instructions != null && this.instructions !== '') {
323
+ parts.push(this.instructions);
324
+ }
325
+
326
+ /** Add additional instructions */
300
327
  if (
301
328
  this.additionalInstructions != null &&
302
329
  this.additionalInstructions !== ''
303
330
  ) {
304
- result = result
305
- ? `${result}\n\n${this.additionalInstructions}`
306
- : this.additionalInstructions;
331
+ parts.push(this.additionalInstructions);
307
332
  }
308
333
 
334
+ /** Add programmatic tools documentation */
309
335
  const programmaticToolsDoc = this.buildProgrammaticOnlyToolsInstructions();
310
336
  if (programmaticToolsDoc) {
311
- result = result
312
- ? `${result}${programmaticToolsDoc}`
313
- : programmaticToolsDoc;
337
+ parts.push(programmaticToolsDoc);
314
338
  }
315
339
 
316
- return result;
340
+ return parts.join('\n\n');
341
+ }
342
+
343
+ /**
344
+ * Builds the agent identity preamble including handoff context if present.
345
+ * This helps the agent understand its role in the multi-agent workflow.
346
+ */
347
+ private buildIdentityPreamble(): string {
348
+ /** Only include preamble if we have handoff context (indicates multi-agent workflow) */
349
+ if (!this.handoffContext) return '';
350
+
351
+ /** Use name (falls back to agentId if not provided) */
352
+ const displayName = this.name ?? this.agentId;
353
+
354
+ const lines: string[] = [];
355
+ lines.push('## Agent Context');
356
+ lines.push(`You are the "${displayName}" agent.`);
357
+
358
+ if (this.handoffContext.sourceAgentName) {
359
+ lines.push(
360
+ `Control was transferred to you from the "${this.handoffContext.sourceAgentName}" agent.`
361
+ );
362
+ }
363
+
364
+ return lines.join('\n');
317
365
  }
318
366
 
319
367
  /**
@@ -393,6 +441,7 @@ export class AgentContext {
393
441
  this.tokenTypeSwitch = undefined;
394
442
  this.currentTokenType = ContentTypes.TEXT;
395
443
  this.discoveredToolNames.clear();
444
+ this.handoffContext = undefined;
396
445
  }
397
446
 
398
447
  /**
@@ -472,6 +521,28 @@ export class AgentContext {
472
521
  return registry;
473
522
  }
474
523
 
524
+ /**
525
+ * Sets the handoff context for this agent.
526
+ * Call this when the agent receives control via handoff from another agent.
527
+ * Marks system runnable as stale to include handoff context in system message.
528
+ * @param sourceAgentName - The name of the agent that handed off to this agent
529
+ */
530
+ setHandoffContext(sourceAgentName: string): void {
531
+ this.handoffContext = { sourceAgentName };
532
+ this.systemRunnableStale = true;
533
+ }
534
+
535
+ /**
536
+ * Clears any handoff context.
537
+ * Call this when resetting the agent or when handoff context is no longer relevant.
538
+ */
539
+ clearHandoffContext(): void {
540
+ if (this.handoffContext) {
541
+ this.handoffContext = undefined;
542
+ this.systemRunnableStale = true;
543
+ }
544
+ }
545
+
475
546
  /**
476
547
  * Marks tools as discovered via tool search.
477
548
  * Discovered tools will be included in the next model binding.
@@ -249,8 +249,11 @@ export class MultiAgentGraph extends StandardGraph {
249
249
 
250
250
  // Create handoff tools for this agent's outgoing edges
251
251
  const handoffTools: t.GenericTool[] = [];
252
+ const sourceAgentName = agentContext.name ?? agentId;
252
253
  for (const edge of edges) {
253
- handoffTools.push(...this.createHandoffToolsForEdge(edge));
254
+ handoffTools.push(
255
+ ...this.createHandoffToolsForEdge(edge, agentId, sourceAgentName)
256
+ );
254
257
  }
255
258
 
256
259
  // Add handoff tools to the agent's existing tools
@@ -263,8 +266,15 @@ export class MultiAgentGraph extends StandardGraph {
263
266
 
264
267
  /**
265
268
  * Create handoff tools for an edge (handles multiple destinations)
269
+ * @param edge - The graph edge defining the handoff
270
+ * @param sourceAgentId - The ID of the agent that will perform the handoff
271
+ * @param sourceAgentName - The human-readable name of the source agent
266
272
  */
267
- private createHandoffToolsForEdge(edge: t.GraphEdge): t.GenericTool[] {
273
+ private createHandoffToolsForEdge(
274
+ edge: t.GraphEdge,
275
+ sourceAgentId: string,
276
+ sourceAgentName: string
277
+ ): t.GenericTool[] {
268
278
  const tools: t.GenericTool[] = [];
269
279
  const destinations = Array.isArray(edge.to) ? edge.to : [edge.to];
270
280
 
@@ -319,6 +329,8 @@ export class MultiAgentGraph extends StandardGraph {
319
329
  additional_kwargs: {
320
330
  /** Store destination for programmatic access in handoff detection */
321
331
  handoff_destination: destination,
332
+ /** Store source agent name for receiving agent to know who handed off */
333
+ handoff_source_name: sourceAgentName,
322
334
  },
323
335
  });
324
336
 
@@ -377,6 +389,10 @@ export class MultiAgentGraph extends StandardGraph {
377
389
  content,
378
390
  name: toolName,
379
391
  tool_call_id: toolCallId,
392
+ additional_kwargs: {
393
+ /** Store source agent name for receiving agent to know who handed off */
394
+ handoff_source_name: sourceAgentName,
395
+ },
380
396
  });
381
397
 
382
398
  const state = getCurrentTaskInput() as t.BaseGraphState;
@@ -482,19 +498,23 @@ export class MultiAgentGraph extends StandardGraph {
482
498
  /**
483
499
  * Detects if the current agent is receiving a handoff and processes the messages accordingly.
484
500
  * Returns filtered messages with the transfer tool call/message removed, plus any instructions
485
- * extracted from the transfer to be injected as a HumanMessage preamble.
501
+ * and source agent information extracted from the transfer.
486
502
  *
487
503
  * Supports both single handoffs (last message is the transfer) and parallel handoffs
488
504
  * (multiple transfer ToolMessages, need to find the one targeting this agent).
489
505
  *
490
506
  * @param messages - Current state messages
491
507
  * @param agentId - The agent ID to check for handoff reception
492
- * @returns Object with filtered messages and extracted instructions, or null if not a handoff
508
+ * @returns Object with filtered messages, extracted instructions, and source agent, or null if not a handoff
493
509
  */
494
510
  private processHandoffReception(
495
511
  messages: BaseMessage[],
496
512
  agentId: string
497
- ): { filteredMessages: BaseMessage[]; instructions: string | null } | null {
513
+ ): {
514
+ filteredMessages: BaseMessage[];
515
+ instructions: string | null;
516
+ sourceAgentName: string | null;
517
+ } | null {
498
518
  if (messages.length === 0) return null;
499
519
 
500
520
  /**
@@ -550,6 +570,11 @@ export class MultiAgentGraph extends StandardGraph {
550
570
  const instructionsMatch = contentStr.match(HANDOFF_INSTRUCTIONS_PATTERN);
551
571
  const instructions = instructionsMatch?.[1]?.trim() ?? null;
552
572
 
573
+ /** Extract source agent name from additional_kwargs */
574
+ const handoffSourceName = toolMessage.additional_kwargs.handoff_source_name;
575
+ const sourceAgentName =
576
+ typeof handoffSourceName === 'string' ? handoffSourceName : null;
577
+
553
578
  /** Get the tool_call_id to find and filter the AI message's tool call */
554
579
  const toolCallId = toolMessage.tool_call_id;
555
580
 
@@ -623,7 +648,7 @@ export class MultiAgentGraph extends StandardGraph {
623
648
  filteredMessages.push(msg);
624
649
  }
625
650
 
626
- return { filteredMessages, instructions };
651
+ return { filteredMessages, instructions, sourceAgentName };
627
652
  }
628
653
 
629
654
  /**
@@ -711,7 +736,21 @@ export class MultiAgentGraph extends StandardGraph {
711
736
  );
712
737
 
713
738
  if (handoffContext !== null) {
714
- const { filteredMessages, instructions } = handoffContext;
739
+ const { filteredMessages, instructions, sourceAgentName } =
740
+ handoffContext;
741
+
742
+ /**
743
+ * Set handoff context on the receiving agent.
744
+ * This updates the system message to include agent identity info.
745
+ */
746
+ const agentContext = this.agentContexts.get(agentId);
747
+ if (
748
+ agentContext &&
749
+ sourceAgentName != null &&
750
+ sourceAgentName !== ''
751
+ ) {
752
+ agentContext.setHandoffContext(sourceAgentName);
753
+ }
715
754
 
716
755
  /** Build messages for the receiving agent */
717
756
  let messagesForAgent = filteredMessages;
@@ -726,7 +765,6 @@ export class MultiAgentGraph extends StandardGraph {
726
765
  }
727
766
 
728
767
  /** Update token map if we have a token counter */
729
- const agentContext = this.agentContexts.get(agentId);
730
768
  if (agentContext?.tokenCounter && hasInstructions) {
731
769
  const freshTokenMap: Record<string, number> = {};
732
770
  for (
@@ -58,9 +58,8 @@ When delegating, provide clear instructions to each agent about what they should
58
58
  apiKey: process.env.ANTHROPIC_API_KEY,
59
59
  },
60
60
  instructions: `You are a RESEARCHER. When you receive a task:
61
- 1. Acknowledge the handoff
62
- 2. Provide concise research findings (100-150 words)
63
- 3. Start your response with "📚 RESEARCH FINDINGS:"`,
61
+ 1. Provide concise research findings (100-150 words)
62
+ 2. Start your response with "📚 RESEARCH FINDINGS:"`,
64
63
  },
65
64
  {
66
65
  agentId: 'writer',
@@ -70,9 +69,8 @@ When delegating, provide clear instructions to each agent about what they should
70
69
  apiKey: process.env.ANTHROPIC_API_KEY,
71
70
  },
72
71
  instructions: `You are a WRITER. When you receive a task:
73
- 1. Acknowledge the handoff
74
- 2. Provide creative content (100-150 words)
75
- 3. Start your response with "✍️ WRITTEN CONTENT:"`,
72
+ 1. Provide creative content (100-150 words)
73
+ 2. Start your response with "✍️ WRITTEN CONTENT:"`,
76
74
  },
77
75
  ];
78
76
 
@@ -357,6 +357,8 @@ export type MultiAgentGraphInput = StandardGraphInput & {
357
357
 
358
358
  export interface AgentInputs {
359
359
  agentId: string;
360
+ /** Human-readable name for the agent (used in handoff context). Defaults to agentId if not provided. */
361
+ name?: string;
360
362
  toolEnd?: boolean;
361
363
  toolMap?: ToolMap;
362
364
  tools?: GraphTools;