@librechat/agents 3.1.67-dev.0 → 3.1.67-dev.4

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.
@@ -4,7 +4,7 @@ import type { BaseMessage, AIMessageChunk, SystemMessage } from '@langchain/core
4
4
  import type { RunnableConfig, Runnable } from '@langchain/core/runnables';
5
5
  import type { ChatGenerationChunk } from '@langchain/core/outputs';
6
6
  import type { GoogleAIToolType } from '@langchain/google-common';
7
- import type { ToolMap, ToolEndEvent, GenericTool, LCTool } from '@/types/tools';
7
+ import type { ToolMap, ToolEndEvent, GenericTool, LCTool, ToolExecuteBatchRequest } from '@/types/tools';
8
8
  import type { Providers, Callback, GraphNodeKeys } from '@/common';
9
9
  import type { StandardGraph, MultiAgentGraph } from '@/graphs';
10
10
  import type { ClientOptions } from '@/types/llm';
@@ -45,7 +45,7 @@ export interface AgentLogEvent {
45
45
  agentId?: string;
46
46
  }
47
47
  export interface EventHandler {
48
- handle(event: string, data: StreamEventData | ModelEndData | RunStep | RunStepDeltaEvent | MessageDeltaEvent | ReasoningDeltaEvent | SummarizeStartEvent | SummarizeDeltaEvent | SummarizeCompleteEvent | AgentLogEvent | {
48
+ handle(event: string, data: StreamEventData | ModelEndData | RunStep | RunStepDeltaEvent | MessageDeltaEvent | ReasoningDeltaEvent | SummarizeStartEvent | SummarizeDeltaEvent | SummarizeCompleteEvent | SubagentUpdateEvent | AgentLogEvent | ToolExecuteBatchRequest | {
49
49
  result: ToolEndEvent;
50
50
  }, metadata?: Record<string, unknown>, graph?: StandardGraph | MultiAgentGraph): void | Promise<void>;
51
51
  }
@@ -269,6 +269,40 @@ export type SubagentConfig = {
269
269
  export type ResolvedSubagentConfig = SubagentConfig & {
270
270
  agentInputs: AgentInputs;
271
271
  };
272
+ /** Lifecycle phase carried on {@link SubagentUpdateEvent}. */
273
+ export type SubagentUpdatePhase = 'start' | 'run_step' | 'run_step_delta' | 'run_step_completed' | 'message_delta' | 'reasoning_delta' | 'stop' | 'error';
274
+ /**
275
+ * Wrapper event emitted when a subagent's child graph dispatches activity.
276
+ * Lets hosts show subagent progress in a UI surface separate from the parent
277
+ * conversation without having to untangle events by agent ID.
278
+ */
279
+ export interface SubagentUpdateEvent {
280
+ /** Parent run ID. */
281
+ runId: string;
282
+ /** Child run ID (unique per subagent execution). */
283
+ subagentRunId: string;
284
+ /**
285
+ * Parent-side `tool_call_id` for the `subagent` tool invocation that
286
+ * triggered this run. Stable for the duration of the child; lets hosts
287
+ * correlate updates deterministically instead of inferring by ordering.
288
+ * Omitted when the executor was invoked outside of a tool-call context.
289
+ */
290
+ parentToolCallId?: string;
291
+ /** Subagent `type` identifier from the SubagentConfig. */
292
+ subagentType: string;
293
+ /** Child agent ID assigned to this subagent execution. */
294
+ subagentAgentId: string;
295
+ /** Parent agent ID that spawned this subagent. */
296
+ parentAgentId?: string;
297
+ /** Lifecycle phase carried by this update. */
298
+ phase: SubagentUpdatePhase;
299
+ /** Underlying event payload (shape depends on phase). */
300
+ data?: unknown;
301
+ /** Short human-readable description. Hosts can render this directly. */
302
+ label?: string;
303
+ /** ISO timestamp for ordering / display. */
304
+ timestamp: string;
305
+ }
272
306
  export interface AgentInputs {
273
307
  agentId: string;
274
308
  /** Human-readable name for the agent (used in handoff context). Defaults to agentId if not provided. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@librechat/agents",
3
- "version": "3.1.67-dev.0",
3
+ "version": "3.1.67-dev.4",
4
4
  "main": "./dist/cjs/main.cjs",
5
5
  "module": "./dist/esm/main.mjs",
6
6
  "types": "./dist/types/index.d.ts",
@@ -61,6 +61,9 @@
61
61
  "tool": "node --trace-warnings -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/tools.ts --provider 'bedrock' --name 'Jo' --location 'New York, NY'",
62
62
  "search": "node -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/search.ts --provider 'bedrock' --name 'Jo' --location 'New York, NY'",
63
63
  "tool_search": "node -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/tool_search.ts",
64
+ "subagent": "node -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/multi-agent-subagent.ts",
65
+ "subagent:events": "node -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/subagent-event-driven-debug.ts",
66
+ "subagent:tools": "node -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/subagent-tools-debug.ts",
64
67
  "programmatic_exec": "node -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/programmatic_exec.ts",
65
68
  "code_exec_ptc": "node -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/code_exec_ptc.ts --provider 'openAI' --name 'Jo' --location 'New York, NY'",
66
69
  "programmatic_exec_agent": "node -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/programmatic_exec_agent.ts --provider 'openAI' --name 'Jo' --location 'New York, NY'",
@@ -27,6 +27,8 @@ export enum GraphEvents {
27
27
  ON_SUMMARIZE_DELTA = 'on_summarize_delta',
28
28
  /** [Custom] Emitted when the summarize node completes with the final summary */
29
29
  ON_SUMMARIZE_COMPLETE = 'on_summarize_complete',
30
+ /** [Custom] Progress update from a running subagent (wraps child-graph events so hosts can display activity separately from parent). */
31
+ ON_SUBAGENT_UPDATE = 'on_subagent_update',
30
32
  /** [Custom] Diagnostic logging event for context management observability */
31
33
  ON_AGENT_LOG = 'on_agent_log',
32
34
 
@@ -1176,10 +1176,16 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1176
1176
  agentContext
1177
1177
  );
1178
1178
  if (resolvedConfigs.length > 0) {
1179
+ const getParentHandlerRegistry = (): HandlerRegistry | undefined =>
1180
+ this.handlerRegistry;
1179
1181
  const executor = new SubagentExecutor({
1180
1182
  configs: new Map(resolvedConfigs.map((c) => [c.type, c])),
1181
1183
  parentSignal: this.signal,
1182
1184
  hookRegistry: this.hookRegistry,
1185
+ /** Lazy — Run wires the registry onto the graph AFTER
1186
+ * `createWorkflow()` runs, so a direct capture here would be
1187
+ * `undefined` at construction time. */
1188
+ parentHandlerRegistry: getParentHandlerRegistry,
1183
1189
  parentRunId: this.runId ?? '',
1184
1190
  parentAgentId: agentContext.agentId,
1185
1191
  tokenCounter: agentContext.tokenCounter,
@@ -1200,10 +1206,25 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1200
1206
  const subagentType =
1201
1207
  typeof input.subagent_type === 'string' ? input.subagent_type : '';
1202
1208
  const threadId = config.configurable?.thread_id as string | undefined;
1209
+ /**
1210
+ * When the tool is dispatched from an LLM's `tool_call`, LangChain
1211
+ * threads the originating `ToolCall` onto the RunnableConfig as
1212
+ * `config.toolCall` (see `ToolRunnableConfig` in
1213
+ * `@langchain/core/tools` — internal but stable since ≥0.3.x).
1214
+ * Surfacing its id lets hosts correlate `SubagentUpdateEvent`s
1215
+ * back to the parent's `tool_call_id` deterministically — no
1216
+ * temporal heuristics needed. If a future LangChain version
1217
+ * changes the threading, the type-guarded read falls back to
1218
+ * `undefined` and the correlation degrades gracefully.
1219
+ */
1220
+ const toolCall = (config as { toolCall?: { id?: string } }).toolCall;
1221
+ const parentToolCallId =
1222
+ typeof toolCall?.id === 'string' ? toolCall.id : undefined;
1203
1223
  const result = await executor.execute({
1204
1224
  description,
1205
1225
  subagentType,
1206
1226
  threadId,
1227
+ parentToolCallId,
1207
1228
  });
1208
1229
  return result.content;
1209
1230
  }, buildSubagentToolParams(resolvedConfigs));
@@ -0,0 +1,190 @@
1
+ import { config } from 'dotenv';
2
+ config();
3
+
4
+ import { HumanMessage } from '@langchain/core/messages';
5
+ import type { BaseMessage } from '@langchain/core/messages';
6
+ import type * as t from '@/types';
7
+ import { ChatModelStreamHandler } from '@/stream';
8
+ import { ToolEndHandler, ModelEndHandler } from '@/events';
9
+ import { Providers, GraphEvents, Constants } from '@/common';
10
+ import { Run } from '@/run';
11
+
12
+ /**
13
+ * Repro for LibreChat's actual setup: event-driven tools via `toolDefinitions`
14
+ * + an ON_TOOL_EXECUTE handler that runs the tool. Self-spawn subagent must
15
+ * be able to drive the SAME tool pipeline.
16
+ */
17
+ const apiKey = process.env.OPENAI_API_KEY!;
18
+ if (!apiKey) {
19
+ console.error('Missing OPENAI_API_KEY');
20
+ process.exit(1);
21
+ }
22
+
23
+ // Simulate LibreChat: tool definitions only, execution routed via event.
24
+ const calculatorDef: t.LCTool = {
25
+ name: 'calculator',
26
+ description: 'Evaluate a math expression. Use for any arithmetic.',
27
+ parameters: {
28
+ type: 'object',
29
+ properties: {
30
+ expression: {
31
+ type: 'string',
32
+ description: "A JS math expression, e.g. '42 * 58'",
33
+ },
34
+ },
35
+ required: ['expression'],
36
+ },
37
+ };
38
+
39
+ async function main() {
40
+ console.log('=== Subagent Event-Driven Tool Diagnostic ===\n');
41
+
42
+ const parentAgent: t.AgentInputs = {
43
+ agentId: 'supervisor',
44
+ provider: Providers.OPENAI,
45
+ clientOptions: { modelName: 'gpt-4o-mini', apiKey },
46
+ instructions: `You have calculator AND can spawn a "self" subagent in an isolated context.
47
+ For any arithmetic question, spawn the "self" subagent with the math task.
48
+ The subagent MUST use the calculator tool — never estimate.`,
49
+ maxContextTokens: 8000,
50
+ toolDefinitions: [calculatorDef],
51
+ subagentConfigs: [
52
+ {
53
+ type: 'self',
54
+ self: true,
55
+ name: 'supervisor',
56
+ description:
57
+ 'Spawn a copy of this agent in an isolated context for a focused math subtask.',
58
+ },
59
+ ],
60
+ };
61
+
62
+ let toolCallCount = 0;
63
+ const customHandlers: Record<string, t.EventHandler> = {
64
+ [GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler(),
65
+ [GraphEvents.TOOL_END]: new ToolEndHandler(),
66
+ [GraphEvents.CHAT_MODEL_END]: new ModelEndHandler(),
67
+ [GraphEvents.ON_TOOL_EXECUTE]: {
68
+ handle: (_event, rawData): void => {
69
+ const data = rawData as t.ToolExecuteBatchRequest;
70
+ console.log(
71
+ `[PARENT ON_TOOL_EXECUTE] agentId=${data.agentId} calls=${data.toolCalls
72
+ .map((c) => c.name)
73
+ .join(',')}`
74
+ );
75
+ const results: t.ToolExecuteResult[] = data.toolCalls.map((call) => {
76
+ toolCallCount += 1;
77
+ const args = call.args as { expression?: string };
78
+ const expression = args.expression ?? '';
79
+ let content: string;
80
+ try {
81
+ // eslint-disable-next-line no-eval
82
+ const result = eval(expression);
83
+ content = `${expression} = ${result}`;
84
+ } catch (err) {
85
+ content = `Error: ${String(err)}`;
86
+ }
87
+ return {
88
+ toolCallId: call.id!,
89
+ status: 'success',
90
+ content,
91
+ };
92
+ });
93
+ data.resolve(results);
94
+ },
95
+ },
96
+ [GraphEvents.ON_RUN_STEP]: {
97
+ handle: (event, data): void => {
98
+ const d = data as { type?: string; runId?: string; agentId?: string };
99
+ console.log(
100
+ `[PARENT ${event}] type=${d.type} agentId=${d.agentId ?? '-'} runId=${d.runId ?? '-'}`
101
+ );
102
+ },
103
+ },
104
+ [GraphEvents.ON_RUN_STEP_COMPLETED]: {
105
+ handle: (event, data): void => {
106
+ const r = (
107
+ data as { result: { type: string; tool_call?: { name?: string } } }
108
+ ).result;
109
+ console.log(
110
+ `[PARENT ${event}] type=${r.type} tool=${r.tool_call?.name ?? '-'}`
111
+ );
112
+ },
113
+ },
114
+ [GraphEvents.ON_SUBAGENT_UPDATE]: {
115
+ handle: (_event, rawData): void => {
116
+ const d = rawData as t.SubagentUpdateEvent;
117
+ console.log(
118
+ `[SUBAGENT ${d.phase}] [${d.subagentType}] tool_call_id=${d.parentToolCallId ?? '-'} ${d.label ?? ''}`
119
+ );
120
+ },
121
+ },
122
+ };
123
+
124
+ const run = await Run.create<t.IState>({
125
+ runId: `sub-evt-${Date.now()}`,
126
+ graphConfig: { type: 'standard', agents: [parentAgent] },
127
+ customHandlers,
128
+ });
129
+
130
+ const question = new HumanMessage(
131
+ 'Compute (42 * 58) + (13 ** 3). Use the self subagent, and have it use the calculator.'
132
+ );
133
+
134
+ console.log('User:', question.content, '\n');
135
+
136
+ await run.processStream(
137
+ { messages: [question] },
138
+ {
139
+ configurable: { thread_id: `sub-evt` },
140
+ version: 'v2' as const,
141
+ }
142
+ );
143
+
144
+ const msgs = (run.getRunMessages() ?? []) as BaseMessage[];
145
+ console.log('\n--- Run messages ---\n');
146
+ for (const msg of msgs) {
147
+ const type = msg._getType();
148
+ const name = 'name' in msg ? (msg as { name?: string }).name : undefined;
149
+ const content =
150
+ typeof msg.content === 'string'
151
+ ? msg.content.slice(0, 400)
152
+ : JSON.stringify(msg.content).slice(0, 400);
153
+ const toolCalls =
154
+ 'tool_calls' in msg
155
+ ? (msg as { tool_calls?: Array<{ name: string; args: unknown }> })
156
+ .tool_calls
157
+ : undefined;
158
+ console.log(`[${type}]${name ? ` name=${name}` : ''}`);
159
+ if (toolCalls?.length) {
160
+ for (const tc of toolCalls) {
161
+ console.log(
162
+ ` tool_call: ${tc.name}(${JSON.stringify(tc.args).slice(0, 150)})`
163
+ );
164
+ }
165
+ }
166
+ console.log(` content: ${content}\n`);
167
+ }
168
+
169
+ const subagentMsgs = msgs.filter(
170
+ (m) =>
171
+ m._getType() === 'tool' &&
172
+ (m as { name?: string }).name === Constants.SUBAGENT
173
+ );
174
+
175
+ console.log('--- Verification ---');
176
+ console.log(`subagent tool calls seen (parent): ${subagentMsgs.length}`);
177
+ console.log(
178
+ `ON_TOOL_EXECUTE dispatched (parent saw): ${toolCallCount} (expected >= 1 if subagent used calculator)`
179
+ );
180
+ if (subagentMsgs[0]) {
181
+ console.log(
182
+ `\nsubagent result:\n${(subagentMsgs[0].content as string).slice(0, 600)}`
183
+ );
184
+ }
185
+ }
186
+
187
+ main().catch((err) => {
188
+ console.error('Script error:', err);
189
+ process.exit(1);
190
+ });
@@ -0,0 +1,160 @@
1
+ import { config } from 'dotenv';
2
+ config();
3
+
4
+ import { HumanMessage } from '@langchain/core/messages';
5
+ import type { BaseMessage } from '@langchain/core/messages';
6
+ import { tool } from '@langchain/core/tools';
7
+ import { z } from 'zod';
8
+ import type * as t from '@/types';
9
+ import { ChatModelStreamHandler } from '@/stream';
10
+ import { ToolEndHandler, ModelEndHandler } from '@/events';
11
+ import { Providers, GraphEvents, Constants } from '@/common';
12
+ import { Run } from '@/run';
13
+
14
+ /**
15
+ * Diagnostic: verify a self-spawned subagent can call parent's real tools.
16
+ * Expected before-fix: parent delegates to self; child cannot invoke calculator.
17
+ */
18
+ const apiKey = process.env.OPENAI_API_KEY!;
19
+ if (!apiKey) {
20
+ console.error('Missing OPENAI_API_KEY');
21
+ process.exit(1);
22
+ }
23
+
24
+ const calculator = tool(
25
+ async ({ expression }) => {
26
+ const result = eval(expression); // don't do this in prod
27
+ return `${expression} = ${result}`;
28
+ },
29
+ {
30
+ name: 'calculator',
31
+ description: 'Evaluate a math expression. Use for any arithmetic.',
32
+ schema: z.object({
33
+ expression: z.string().describe("A JS math expression, e.g. '42 * 58'"),
34
+ }),
35
+ }
36
+ );
37
+
38
+ async function main() {
39
+ console.log('=== Subagent Tool-Access Diagnostic ===\n');
40
+
41
+ const parentAgent: t.AgentInputs = {
42
+ agentId: 'supervisor',
43
+ provider: Providers.OPENAI,
44
+ clientOptions: { modelName: 'gpt-4o-mini', apiKey },
45
+ instructions: `You have calculator AND can spawn a "self" subagent in an isolated context.
46
+ For any arithmetic question that would bloat your context, spawn the "self" subagent with the math task.
47
+ The subagent must use the calculator tool — never estimate.`,
48
+ maxContextTokens: 8000,
49
+ tools: [calculator],
50
+ subagentConfigs: [
51
+ {
52
+ type: 'self',
53
+ self: true,
54
+ name: 'supervisor',
55
+ description:
56
+ 'Spawn a copy of this agent in an isolated context for a focused math subtask.',
57
+ },
58
+ ],
59
+ };
60
+
61
+ const customHandlers: Record<string, t.EventHandler> = {
62
+ [GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler(),
63
+ [GraphEvents.TOOL_END]: new ToolEndHandler(),
64
+ [GraphEvents.CHAT_MODEL_END]: new ModelEndHandler(),
65
+ [GraphEvents.ON_RUN_STEP]: {
66
+ handle: (event, data): void => {
67
+ console.log(
68
+ `[PARENT EVENT] ${event}`,
69
+ JSON.stringify(data).slice(0, 200)
70
+ );
71
+ },
72
+ },
73
+ [GraphEvents.ON_RUN_STEP_COMPLETED]: {
74
+ handle: (event, data): void => {
75
+ console.log(
76
+ `[PARENT EVENT] ${event}`,
77
+ JSON.stringify(data).slice(0, 200)
78
+ );
79
+ },
80
+ },
81
+ };
82
+
83
+ const run = await Run.create<t.IState>({
84
+ runId: `subagent-debug-${Date.now()}`,
85
+ graphConfig: { type: 'standard', agents: [parentAgent] },
86
+ customHandlers,
87
+ });
88
+
89
+ const question = new HumanMessage(
90
+ 'Compute (42 * 58) + (13 ^ 3). Spawn the self subagent to do this, and have IT use calculator.'
91
+ );
92
+
93
+ console.log('User:', question.content, '\n');
94
+
95
+ await run.processStream(
96
+ { messages: [question] },
97
+ {
98
+ configurable: { thread_id: `subagent-debug` },
99
+ version: 'v2' as const,
100
+ }
101
+ );
102
+
103
+ const msgs = (run.getRunMessages() ?? []) as BaseMessage[];
104
+ console.log('\n--- Run messages ---\n');
105
+ for (const msg of msgs) {
106
+ const type = msg._getType();
107
+ const name = 'name' in msg ? (msg as { name?: string }).name : undefined;
108
+ const content =
109
+ typeof msg.content === 'string'
110
+ ? msg.content.slice(0, 400)
111
+ : JSON.stringify(msg.content).slice(0, 400);
112
+ const toolCalls =
113
+ 'tool_calls' in msg
114
+ ? (msg as { tool_calls?: Array<{ name: string; args: unknown }> })
115
+ .tool_calls
116
+ : undefined;
117
+ console.log(`[${type}]${name ? ` name=${name}` : ''}`);
118
+ if (toolCalls?.length) {
119
+ for (const tc of toolCalls) {
120
+ console.log(
121
+ ` tool_call: ${tc.name}(${JSON.stringify(tc.args).slice(0, 150)})`
122
+ );
123
+ }
124
+ }
125
+ console.log(` content: ${content}\n`);
126
+ }
127
+
128
+ const subagentCalls = msgs.filter(
129
+ (m) =>
130
+ m._getType() === 'tool' &&
131
+ 'name' in m &&
132
+ (m as { name?: string }).name === Constants.SUBAGENT
133
+ );
134
+ const calculatorCalls = msgs.filter(
135
+ (m) =>
136
+ m._getType() === 'tool' &&
137
+ 'name' in m &&
138
+ (m as { name?: string }).name === 'calculator'
139
+ );
140
+
141
+ console.log('--- Verification ---');
142
+ console.log(`subagent tool calls seen (parent): ${subagentCalls.length}`);
143
+ console.log(
144
+ `calculator tool calls seen (parent): ${calculatorCalls.length} (expected: 0 if subagent did the math)`
145
+ );
146
+ if (subagentCalls.length > 0) {
147
+ const subResult = subagentCalls[0].content as string;
148
+ console.log(`\nsubagent result snippet:\n${subResult.slice(0, 600)}\n`);
149
+ if (/\berror\b/i.test(subResult) && /tool/i.test(subResult)) {
150
+ console.log('⚠️ BUG CONFIRMED: subagent result mentions tool error');
151
+ } else if (!/\d/.test(subResult)) {
152
+ console.log('⚠️ POSSIBLY BUGGY: subagent result has no numbers');
153
+ }
154
+ }
155
+ }
156
+
157
+ main().catch((err) => {
158
+ console.error('Script error:', err);
159
+ process.exit(1);
160
+ });