@librechat/agents 3.4.1 → 3.4.2

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.
Files changed (128) hide show
  1. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  2. package/dist/cjs/graphs/Graph.cjs +57 -33
  3. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  4. package/dist/cjs/graphs/MultiAgentGraph.cjs +81 -33
  5. package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
  6. package/dist/cjs/graphs/applyGraphRuntimeConfig.cjs +14 -0
  7. package/dist/cjs/graphs/applyGraphRuntimeConfig.cjs.map +1 -0
  8. package/dist/cjs/graphs/createGraph.cjs +15 -0
  9. package/dist/cjs/graphs/createGraph.cjs.map +1 -0
  10. package/dist/cjs/graphs/index.cjs +1 -0
  11. package/dist/cjs/hooks/createWorkspacePolicyHook.cjs +1 -1
  12. package/dist/cjs/main.cjs +20 -11
  13. package/dist/cjs/run.cjs +48 -38
  14. package/dist/cjs/run.cjs.map +1 -1
  15. package/dist/cjs/session/JsonlSessionStore.cjs +2 -2
  16. package/dist/cjs/tools/SubagentTool.cjs +4 -4
  17. package/dist/cjs/tools/SubagentTool.cjs.map +1 -1
  18. package/dist/cjs/tools/ToolNode.cjs +13 -9
  19. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  20. package/dist/cjs/tools/eagerEventExecution.cjs +1 -0
  21. package/dist/cjs/tools/local/LocalExecutionEngine.cjs +3 -3
  22. package/dist/cjs/tools/local/LocalProgrammaticToolCalling.cjs +2 -2
  23. package/dist/cjs/tools/search/rerankers.cjs +154 -1
  24. package/dist/cjs/tools/search/rerankers.cjs.map +1 -1
  25. package/dist/cjs/tools/search/tool.cjs +4 -1
  26. package/dist/cjs/tools/search/tool.cjs.map +1 -1
  27. package/dist/cjs/tools/subagent/SubagentExecutionRegistry.cjs +601 -0
  28. package/dist/cjs/tools/subagent/SubagentExecutionRegistry.cjs.map +1 -0
  29. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +672 -343
  30. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
  31. package/dist/cjs/tools/subagent/SubagentReplay.cjs +3 -1
  32. package/dist/cjs/tools/subagent/SubagentReplay.cjs.map +1 -1
  33. package/dist/cjs/tools/subagent/childGraphConfig.cjs +278 -0
  34. package/dist/cjs/tools/subagent/childGraphConfig.cjs.map +1 -0
  35. package/dist/cjs/tools/subagent/index.cjs +1 -0
  36. package/dist/cjs/tools/subagent/runtimeLimits.cjs +5 -0
  37. package/dist/cjs/tools/subagent/runtimeLimits.cjs.map +1 -0
  38. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  39. package/dist/esm/graphs/Graph.mjs +57 -33
  40. package/dist/esm/graphs/Graph.mjs.map +1 -1
  41. package/dist/esm/graphs/MultiAgentGraph.mjs +81 -33
  42. package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
  43. package/dist/esm/graphs/applyGraphRuntimeConfig.mjs +14 -0
  44. package/dist/esm/graphs/applyGraphRuntimeConfig.mjs.map +1 -0
  45. package/dist/esm/graphs/createGraph.mjs +15 -0
  46. package/dist/esm/graphs/createGraph.mjs.map +1 -0
  47. package/dist/esm/graphs/index.mjs +1 -0
  48. package/dist/esm/hooks/createWorkspacePolicyHook.mjs +1 -1
  49. package/dist/esm/main.mjs +12 -10
  50. package/dist/esm/run.mjs +48 -38
  51. package/dist/esm/run.mjs.map +1 -1
  52. package/dist/esm/session/JsonlSessionStore.mjs +2 -2
  53. package/dist/esm/tools/SubagentTool.mjs +4 -4
  54. package/dist/esm/tools/SubagentTool.mjs.map +1 -1
  55. package/dist/esm/tools/ToolNode.mjs +13 -9
  56. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  57. package/dist/esm/tools/eagerEventExecution.mjs +1 -1
  58. package/dist/esm/tools/local/LocalExecutionEngine.mjs +3 -3
  59. package/dist/esm/tools/local/LocalProgrammaticToolCalling.mjs +2 -2
  60. package/dist/esm/tools/search/rerankers.mjs +154 -1
  61. package/dist/esm/tools/search/rerankers.mjs.map +1 -1
  62. package/dist/esm/tools/search/tool.mjs +4 -1
  63. package/dist/esm/tools/search/tool.mjs.map +1 -1
  64. package/dist/esm/tools/subagent/SubagentExecutionRegistry.mjs +597 -0
  65. package/dist/esm/tools/subagent/SubagentExecutionRegistry.mjs.map +1 -0
  66. package/dist/esm/tools/subagent/SubagentExecutor.mjs +672 -343
  67. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
  68. package/dist/esm/tools/subagent/SubagentReplay.mjs +3 -1
  69. package/dist/esm/tools/subagent/SubagentReplay.mjs.map +1 -1
  70. package/dist/esm/tools/subagent/childGraphConfig.mjs +272 -0
  71. package/dist/esm/tools/subagent/childGraphConfig.mjs.map +1 -0
  72. package/dist/esm/tools/subagent/index.mjs +1 -0
  73. package/dist/esm/tools/subagent/runtimeLimits.mjs +5 -0
  74. package/dist/esm/tools/subagent/runtimeLimits.mjs.map +1 -0
  75. package/dist/types/agents/AgentContext.d.ts +1 -1
  76. package/dist/types/graphs/Graph.d.ts +7 -2
  77. package/dist/types/graphs/MultiAgentGraph.d.ts +7 -1
  78. package/dist/types/graphs/applyGraphRuntimeConfig.d.ts +3 -0
  79. package/dist/types/graphs/createGraph.d.ts +13 -0
  80. package/dist/types/graphs/graphFactory.d.ts +13 -0
  81. package/dist/types/graphs/index.d.ts +2 -0
  82. package/dist/types/run.d.ts +2 -2
  83. package/dist/types/tools/SubagentTool.d.ts +1 -1
  84. package/dist/types/tools/search/rerankers.d.ts +28 -0
  85. package/dist/types/tools/search/types.d.ts +38 -1
  86. package/dist/types/tools/subagent/SubagentExecutionRegistry.d.ts +171 -0
  87. package/dist/types/tools/subagent/SubagentExecutor.d.ts +32 -37
  88. package/dist/types/tools/subagent/SubagentReplay.d.ts +5 -1
  89. package/dist/types/tools/subagent/childGraphConfig.d.ts +37 -0
  90. package/dist/types/tools/subagent/index.d.ts +1 -1
  91. package/dist/types/tools/subagent/runtimeLimits.d.ts +4 -0
  92. package/dist/types/types/graph.d.ts +184 -19
  93. package/package.json +3 -1
  94. package/src/agents/AgentContext.ts +1 -1
  95. package/src/graphs/Graph.ts +111 -60
  96. package/src/graphs/MultiAgentGraph.ts +149 -47
  97. package/src/graphs/__tests__/MultiAgentGraph.test.ts +86 -0
  98. package/src/graphs/__tests__/composition.smoke.test.ts +152 -0
  99. package/src/graphs/__tests__/createGraph.test.ts +206 -0
  100. package/src/graphs/applyGraphRuntimeConfig.ts +25 -0
  101. package/src/graphs/createGraph.ts +32 -0
  102. package/src/graphs/graphFactory.ts +12 -0
  103. package/src/graphs/index.ts +2 -0
  104. package/src/run.ts +50 -38
  105. package/src/specs/graph-subagent.live.test.ts +242 -0
  106. package/src/specs/graph-subagent.test.ts +322 -0
  107. package/src/specs/langfuse-routing.integration.test.ts +120 -1
  108. package/src/specs/subagent.test.ts +130 -4
  109. package/src/tools/SubagentTool.ts +4 -4
  110. package/src/tools/ToolNode.ts +22 -10
  111. package/src/tools/__tests__/SubagentExecutor.test.ts +198 -9
  112. package/src/tools/__tests__/SubagentReplay.test.ts +29 -0
  113. package/src/tools/__tests__/directToolHITLResumeScope.test.ts +28 -2
  114. package/src/tools/__tests__/subagentHooks.test.ts +90 -3
  115. package/src/tools/search/rag-api-reranker.test.ts +891 -0
  116. package/src/tools/search/rerankers.ts +260 -0
  117. package/src/tools/search/tool.ts +6 -0
  118. package/src/tools/search/types.ts +45 -1
  119. package/src/tools/subagent/SubagentExecutionRegistry.ts +1159 -0
  120. package/src/tools/subagent/SubagentExecutor.ts +1245 -563
  121. package/src/tools/subagent/SubagentReplay.ts +10 -1
  122. package/src/tools/subagent/__tests__/SubagentExecutionRegistry.test.ts +637 -0
  123. package/src/tools/subagent/__tests__/SubagentExecutor.lazy.test.ts +3054 -0
  124. package/src/tools/subagent/__tests__/childGraphConfig.test.ts +521 -0
  125. package/src/tools/subagent/childGraphConfig.ts +694 -0
  126. package/src/tools/subagent/index.ts +6 -0
  127. package/src/tools/subagent/runtimeLimits.ts +7 -0
  128. package/src/types/graph.ts +217 -19
@@ -0,0 +1,206 @@
1
+ import type { RunnableConfig } from '@langchain/core/runnables';
2
+ import type { ToolCall } from '@langchain/core/messages/tool';
3
+ import type { GraphFactory, GraphFactoryRequest } from '@/graphs/graphFactory';
4
+ import type * as t from '@/types';
5
+ import { MultiAgentGraph } from '@/graphs/MultiAgentGraph';
6
+ import { createFakeStreamingLLM } from '@/llm/fake';
7
+ import { createGraph } from '@/graphs/createGraph';
8
+ import { Constants, Providers } from '@/common';
9
+ import { StandardGraph } from '@/graphs/Graph';
10
+
11
+ const invokeConfig: RunnableConfig = {
12
+ configurable: { thread_id: 'graph-factory-test' },
13
+ };
14
+
15
+ const makeAgent = (agentId: string): t.AgentInputs => ({
16
+ agentId,
17
+ provider: Providers.OPENAI,
18
+ instructions: `You are ${agentId}.`,
19
+ });
20
+
21
+ const getSubagentTool = (
22
+ graph: StandardGraph,
23
+ agentId: string
24
+ ): t.GenericTool => {
25
+ graph.createWorkflow();
26
+ const tools = graph.agentContexts.get(agentId)?.graphTools as
27
+ | t.GenericTool[]
28
+ | undefined;
29
+ const tool = tools?.find(
30
+ (candidate) => 'name' in candidate && candidate.name === Constants.SUBAGENT
31
+ );
32
+ if (tool == null) {
33
+ throw new Error(`Expected subagent tool for ${agentId}`);
34
+ }
35
+ return tool;
36
+ };
37
+
38
+ describe('graph factory', () => {
39
+ it('constructs standard and multi-agent graph adapters', () => {
40
+ const standard = createGraph({
41
+ kind: 'standard',
42
+ input: { runId: 'standard-factory', agents: [makeAgent('standard')] },
43
+ });
44
+ const multiAgent = createGraph({
45
+ kind: 'multi-agent',
46
+ input: {
47
+ runId: 'multi-agent-factory',
48
+ agents: [makeAgent('multi')],
49
+ edges: [],
50
+ },
51
+ });
52
+
53
+ expect(standard).toBeInstanceOf(StandardGraph);
54
+ expect(standard).not.toBeInstanceOf(MultiAgentGraph);
55
+ expect(multiAgent).toBeInstanceOf(MultiAgentGraph);
56
+ });
57
+
58
+ it('accepts a union-typed graph factory request', () => {
59
+ const construct = (request: GraphFactoryRequest): StandardGraph =>
60
+ createGraph(request);
61
+ const graph = construct({
62
+ kind: 'standard',
63
+ input: { runId: 'union-factory', agents: [makeAgent('union')] },
64
+ });
65
+
66
+ expect(graph).toBeInstanceOf(StandardGraph);
67
+ });
68
+
69
+ it('rejects invalid per-member recursion limits', () => {
70
+ expect(() =>
71
+ createGraph({
72
+ kind: 'multi-agent',
73
+ input: {
74
+ runId: 'invalid-member-limit',
75
+ agents: [makeAgent('member')],
76
+ edges: [],
77
+ memberRecursionLimit: 0,
78
+ },
79
+ })
80
+ ).toThrow(/memberRecursionLimit must be a positive safe integer/);
81
+ });
82
+
83
+ it('keeps direct StandardGraph construction compatible with agent children', async () => {
84
+ const parent = {
85
+ ...makeAgent('parent'),
86
+ subagentConfigs: [
87
+ {
88
+ type: 'worker',
89
+ name: 'Worker',
90
+ description: 'Completes one isolated task.',
91
+ agentInputs: makeAgent('worker'),
92
+ },
93
+ ],
94
+ } satisfies t.AgentInputs;
95
+ const graph = new StandardGraph({
96
+ runId: 'direct-standard-factory',
97
+ agents: [parent],
98
+ });
99
+ graph.setSubagentModelOverride(
100
+ createFakeStreamingLLM({ responses: ['worker complete'] })
101
+ );
102
+
103
+ const result = await getSubagentTool(graph, 'parent').invoke(
104
+ { description: 'Complete the task.', subagent_type: 'worker' },
105
+ invokeConfig
106
+ );
107
+
108
+ expect(result).toBe('worker complete');
109
+ });
110
+
111
+ it('fails early when direct construction cannot instantiate a graph child', () => {
112
+ const parent = {
113
+ ...makeAgent('parent'),
114
+ subagentConfigs: [
115
+ {
116
+ kind: 'graph' as const,
117
+ type: 'team',
118
+ name: 'Team',
119
+ description: 'Runs one member.',
120
+ agents: [makeAgent('member')],
121
+ edges: [],
122
+ entryAgentId: 'member',
123
+ resultAgentId: 'member',
124
+ },
125
+ ],
126
+ } satisfies t.AgentInputs;
127
+ const graph = new StandardGraph({
128
+ runId: 'direct-graph-child-factory',
129
+ agents: [parent],
130
+ });
131
+
132
+ expect(() => graph.createWorkflow()).toThrow(
133
+ /constructing the parent with createGraph\(\)/
134
+ );
135
+ });
136
+
137
+ it('propagates an injected factory through child and grandchild graphs', async () => {
138
+ const requests: GraphFactoryRequest[] = [];
139
+ const graphFactory: GraphFactory = (request) => {
140
+ requests.push(request);
141
+ if (request.kind === 'multi-agent') {
142
+ return new MultiAgentGraph(request.input, { graphFactory });
143
+ }
144
+ return new StandardGraph(request.input, { graphFactory });
145
+ };
146
+ const nestedToolCall: ToolCall = {
147
+ id: 'call_nested_specialist',
148
+ name: Constants.SUBAGENT,
149
+ args: {
150
+ description: 'Complete the specialist step.',
151
+ subagent_type: 'specialist',
152
+ },
153
+ type: 'tool_call',
154
+ };
155
+ const router = {
156
+ ...makeAgent('router'),
157
+ subagentConfigs: [
158
+ {
159
+ type: 'specialist',
160
+ name: 'Specialist',
161
+ description: 'Completes the nested specialist step.',
162
+ agentInputs: makeAgent('specialist'),
163
+ },
164
+ ],
165
+ } satisfies t.AgentInputs;
166
+ const root = {
167
+ ...makeAgent('root'),
168
+ maxSubagentDepth: 2,
169
+ subagentConfigs: [
170
+ {
171
+ type: 'router',
172
+ name: 'Router',
173
+ description: 'Delegates one nested specialist step.',
174
+ allowNested: true,
175
+ agentInputs: router,
176
+ },
177
+ ],
178
+ } satisfies t.AgentInputs;
179
+ const graph = new StandardGraph(
180
+ { runId: 'recursive-graph-factory', agents: [root] },
181
+ { graphFactory }
182
+ );
183
+ graph.setSubagentModelOverride(
184
+ createFakeStreamingLLM({
185
+ responses: ['delegating', 'specialist complete', 'router complete'],
186
+ toolCalls: [nestedToolCall],
187
+ })
188
+ );
189
+
190
+ const result = await getSubagentTool(graph, 'root').invoke(
191
+ { description: 'Route the task.', subagent_type: 'router' },
192
+ invokeConfig
193
+ );
194
+
195
+ expect(result).toBe('router complete');
196
+ expect(
197
+ requests.map(({ kind, input }) => ({
198
+ kind,
199
+ agentIds: input.agents.map(({ agentId }) => agentId),
200
+ }))
201
+ ).toEqual([
202
+ { kind: 'standard', agentIds: ['router'] },
203
+ { kind: 'standard', agentIds: ['specialist'] },
204
+ ]);
205
+ });
206
+ });
@@ -0,0 +1,25 @@
1
+ import type { StandardGraph } from './Graph';
2
+
3
+ export type GraphRuntimeConfig = Pick<
4
+ StandardGraph,
5
+ | 'hookRegistry'
6
+ | 'humanInTheLoop'
7
+ | 'toolOutputReferences'
8
+ | 'eagerEventToolExecution'
9
+ | 'codeSessionToolNames'
10
+ | 'interruptingToolNames'
11
+ | 'toolExecution'
12
+ >;
13
+
14
+ export function applyGraphRuntimeConfig(
15
+ graph: StandardGraph,
16
+ config: GraphRuntimeConfig
17
+ ): void {
18
+ graph.hookRegistry = config.hookRegistry;
19
+ graph.humanInTheLoop = config.humanInTheLoop;
20
+ graph.toolOutputReferences = config.toolOutputReferences;
21
+ graph.eagerEventToolExecution = config.eagerEventToolExecution;
22
+ graph.codeSessionToolNames = config.codeSessionToolNames;
23
+ graph.interruptingToolNames = config.interruptingToolNames;
24
+ graph.toolExecution = config.toolExecution;
25
+ }
@@ -0,0 +1,32 @@
1
+ import type {
2
+ GraphFactory,
3
+ GraphFactoryDependencies,
4
+ GraphFactoryRequest,
5
+ } from '@/graphs/graphFactory';
6
+ import type { MultiAgentGraphInput, StandardGraphInput } from '@/types';
7
+ import { MultiAgentGraph } from '@/graphs/MultiAgentGraph';
8
+ import { StandardGraph } from '@/graphs/Graph';
9
+
10
+ const createGraphByKind: GraphFactory = (request) => {
11
+ if (request.kind === 'multi-agent') {
12
+ return new MultiAgentGraph(request.input, dependencies);
13
+ }
14
+ return new StandardGraph(request.input, dependencies);
15
+ };
16
+
17
+ const dependencies: GraphFactoryDependencies = {
18
+ graphFactory: createGraphByKind,
19
+ };
20
+
21
+ export function createGraph(request: {
22
+ kind: 'standard';
23
+ input: StandardGraphInput;
24
+ }): StandardGraph;
25
+ export function createGraph(request: {
26
+ kind: 'multi-agent';
27
+ input: MultiAgentGraphInput;
28
+ }): MultiAgentGraph;
29
+ export function createGraph(request: GraphFactoryRequest): StandardGraph;
30
+ export function createGraph(request: GraphFactoryRequest): StandardGraph {
31
+ return createGraphByKind(request);
32
+ }
@@ -0,0 +1,12 @@
1
+ import type { StandardGraph } from './Graph';
2
+ import type * as t from '@/types';
3
+
4
+ export type GraphFactoryRequest =
5
+ | { kind: 'standard'; input: t.StandardGraphInput }
6
+ | { kind: 'multi-agent'; input: t.MultiAgentGraphInput };
7
+
8
+ export type GraphFactory = (request: GraphFactoryRequest) => StandardGraph;
9
+
10
+ export type GraphFactoryDependencies = {
11
+ graphFactory: GraphFactory;
12
+ };
@@ -1,2 +1,4 @@
1
1
  export * from './Graph';
2
2
  export * from './MultiAgentGraph';
3
+ export * from './createGraph';
4
+ export type * from './graphFactory';
package/src/run.ts CHANGED
@@ -18,6 +18,8 @@ import {
18
18
  import type { StringPromptValue } from '@langchain/core/prompt_values';
19
19
  import type { MessageContentComplex } from '@langchain/core/messages';
20
20
  import type { RunnableConfig } from '@langchain/core/runnables';
21
+ import type { MultiAgentGraph } from '@/graphs/MultiAgentGraph';
22
+ import type { StandardGraph } from '@/graphs/Graph';
21
23
  import type { HookRegistry } from '@/hooks';
22
24
  import type * as t from '@/types';
23
25
  import {
@@ -62,12 +64,12 @@ import {
62
64
  createCompletionTitleRunnable,
63
65
  createTitleRunnable,
64
66
  } from '@/utils/title';
67
+ import { applyGraphRuntimeConfig } from '@/graphs/applyGraphRuntimeConfig';
65
68
  import { createTokenCounter, encodingForModel } from '@/utils/tokens';
66
69
  import { initializeLangfuseTracing } from './instrumentation';
67
- import { MultiAgentGraph } from '@/graphs/MultiAgentGraph';
68
70
  import { getTraceIdSeed } from '@/langfuseRuntimeContext';
71
+ import { createGraph } from '@/graphs/createGraph';
69
72
  import { resolveMaxSeals } from '@/llm/preempt';
70
- import { StandardGraph } from '@/graphs/Graph';
71
73
  import { initializeModel } from '@/llm/init';
72
74
  import { HandlerRegistry } from '@/events';
73
75
  import { isOpenAILike } from '@/utils/llm';
@@ -346,30 +348,35 @@ export class Run<_T extends t.BaseGraphState> {
346
348
  signal = legacySignal;
347
349
  }
348
350
 
349
- const standardGraph = new StandardGraph({
350
- signal,
351
- runId: this.id,
352
- agents: [agentConfig],
353
- langfuse: this.langfuse,
354
- tokenCounter: this.tokenCounter,
355
- indexTokenCountMap: this.indexTokenCountMap,
356
- calibrationRatio: this.calibrationRatio,
357
- subagentUsageSink: this.subagentUsageSink,
358
- preemption: this.preemption,
359
- streamLimits: this.streamLimits,
351
+ const standardGraph = createGraph({
352
+ kind: 'standard',
353
+ input: {
354
+ signal,
355
+ runId: this.id,
356
+ agents: [agentConfig],
357
+ langfuse: this.langfuse,
358
+ tokenCounter: this.tokenCounter,
359
+ indexTokenCountMap: this.indexTokenCountMap,
360
+ calibrationRatio: this.calibrationRatio,
361
+ subagentUsageSink: this.subagentUsageSink,
362
+ preemption: this.preemption,
363
+ streamLimits: this.streamLimits,
364
+ },
360
365
  });
361
366
  /** Propagate compile options from graph config */
362
367
  standardGraph.compileOptions = this.applyHITLCheckpointerFallback(
363
368
  config.compileOptions
364
369
  );
365
370
  this.hasCheckpointer = standardGraph.compileOptions?.checkpointer != null;
366
- standardGraph.hookRegistry = this.hookRegistry;
367
- standardGraph.humanInTheLoop = this.humanInTheLoop;
368
- standardGraph.toolOutputReferences = this.toolOutputReferences;
369
- standardGraph.eagerEventToolExecution = this.eagerEventToolExecution;
370
- standardGraph.codeSessionToolNames = this.codeSessionToolNames;
371
- standardGraph.interruptingToolNames = this.interruptingToolNames;
372
- standardGraph.toolExecution = this.toolExecution;
371
+ applyGraphRuntimeConfig(standardGraph, {
372
+ hookRegistry: this.hookRegistry,
373
+ humanInTheLoop: this.humanInTheLoop,
374
+ toolOutputReferences: this.toolOutputReferences,
375
+ eagerEventToolExecution: this.eagerEventToolExecution,
376
+ codeSessionToolNames: this.codeSessionToolNames,
377
+ interruptingToolNames: this.interruptingToolNames,
378
+ toolExecution: this.toolExecution,
379
+ });
373
380
  this.Graph = standardGraph;
374
381
  return standardGraph.createWorkflow();
375
382
  }
@@ -379,30 +386,35 @@ export class Run<_T extends t.BaseGraphState> {
379
386
  ): t.CompiledStateWorkflow {
380
387
  const { agents, edges, compileOptions } = config;
381
388
 
382
- const multiAgentGraph = new MultiAgentGraph({
383
- runId: this.id,
384
- agents,
385
- edges,
386
- langfuse: this.langfuse,
387
- tokenCounter: this.tokenCounter,
388
- indexTokenCountMap: this.indexTokenCountMap,
389
- calibrationRatio: this.calibrationRatio,
390
- subagentUsageSink: this.subagentUsageSink,
391
- preemption: this.preemption,
392
- streamLimits: this.streamLimits,
389
+ const multiAgentGraph = createGraph({
390
+ kind: 'multi-agent',
391
+ input: {
392
+ runId: this.id,
393
+ agents,
394
+ edges,
395
+ langfuse: this.langfuse,
396
+ tokenCounter: this.tokenCounter,
397
+ indexTokenCountMap: this.indexTokenCountMap,
398
+ calibrationRatio: this.calibrationRatio,
399
+ subagentUsageSink: this.subagentUsageSink,
400
+ preemption: this.preemption,
401
+ streamLimits: this.streamLimits,
402
+ },
393
403
  });
394
404
 
395
405
  multiAgentGraph.compileOptions =
396
406
  this.applyHITLCheckpointerFallback(compileOptions);
397
407
  this.hasCheckpointer = multiAgentGraph.compileOptions?.checkpointer != null;
398
408
 
399
- multiAgentGraph.hookRegistry = this.hookRegistry;
400
- multiAgentGraph.humanInTheLoop = this.humanInTheLoop;
401
- multiAgentGraph.toolOutputReferences = this.toolOutputReferences;
402
- multiAgentGraph.eagerEventToolExecution = this.eagerEventToolExecution;
403
- multiAgentGraph.codeSessionToolNames = this.codeSessionToolNames;
404
- multiAgentGraph.interruptingToolNames = this.interruptingToolNames;
405
- multiAgentGraph.toolExecution = this.toolExecution;
409
+ applyGraphRuntimeConfig(multiAgentGraph, {
410
+ hookRegistry: this.hookRegistry,
411
+ humanInTheLoop: this.humanInTheLoop,
412
+ toolOutputReferences: this.toolOutputReferences,
413
+ eagerEventToolExecution: this.eagerEventToolExecution,
414
+ codeSessionToolNames: this.codeSessionToolNames,
415
+ interruptingToolNames: this.interruptingToolNames,
416
+ toolExecution: this.toolExecution,
417
+ });
406
418
  this.Graph = multiAgentGraph;
407
419
  return multiAgentGraph.createWorkflow();
408
420
  }
@@ -0,0 +1,242 @@
1
+ import { config as dotenvConfig } from 'dotenv';
2
+
3
+ dotenvConfig(
4
+ process.env.LIVE_ENV_PATH == null
5
+ ? undefined
6
+ : { path: process.env.LIVE_ENV_PATH }
7
+ );
8
+ dotenvConfig();
9
+
10
+ import { describe, expect, it, jest } from '@jest/globals';
11
+ import type { RunnableConfig } from '@langchain/core/runnables';
12
+ import type * as t from '@/types';
13
+ import { Constants, GraphEvents, Providers } from '@/common';
14
+ import { StandardGraph } from '@/graphs/Graph';
15
+ import { Run } from '@/run';
16
+
17
+ jest.setTimeout(180_000);
18
+
19
+ const liveEnabled = process.env.RUN_GRAPH_SUBAGENT_LIVE_TESTS === '1';
20
+ const requestedProvider = process.env.GRAPH_SUBAGENT_LIVE_PROVIDER;
21
+ const describeIfLive = liveEnabled ? describe : describe.skip;
22
+
23
+ type LiveProviderCase = {
24
+ label: string;
25
+ provider: Providers;
26
+ model: string;
27
+ apiKey?: string;
28
+ };
29
+
30
+ const LIVE_PROVIDER_CASES: readonly LiveProviderCase[] = [
31
+ {
32
+ label: 'Anthropic Haiku',
33
+ provider: Providers.ANTHROPIC,
34
+ model:
35
+ process.env.ANTHROPIC_GRAPH_SUBAGENT_LIVE_MODEL ?? 'claude-haiku-4-5',
36
+ apiKey: process.env.ANTHROPIC_API_KEY,
37
+ },
38
+ {
39
+ label: 'OpenAI GPT-4.1 mini',
40
+ provider: Providers.OPENAI,
41
+ model: process.env.OPENAI_GRAPH_SUBAGENT_LIVE_MODEL ?? 'gpt-4.1-mini',
42
+ apiKey: process.env.OPENAI_API_KEY,
43
+ },
44
+ ];
45
+
46
+ const explicitlyRequestedCase =
47
+ requestedProvider == null || requestedProvider === ''
48
+ ? undefined
49
+ : LIVE_PROVIDER_CASES.find(
50
+ ({ provider }) =>
51
+ provider.toLowerCase() === requestedProvider.toLowerCase()
52
+ );
53
+
54
+ if (
55
+ liveEnabled &&
56
+ requestedProvider != null &&
57
+ requestedProvider !== '' &&
58
+ explicitlyRequestedCase == null
59
+ ) {
60
+ throw new Error(
61
+ `Unknown GRAPH_SUBAGENT_LIVE_PROVIDER "${requestedProvider}".`
62
+ );
63
+ }
64
+ if (
65
+ liveEnabled &&
66
+ explicitlyRequestedCase != null &&
67
+ (explicitlyRequestedCase.apiKey == null ||
68
+ explicitlyRequestedCase.apiKey === '')
69
+ ) {
70
+ throw new Error(
71
+ `Missing credentials for GRAPH_SUBAGENT_LIVE_PROVIDER "${requestedProvider}".`
72
+ );
73
+ }
74
+ if (
75
+ liveEnabled &&
76
+ explicitlyRequestedCase == null &&
77
+ !LIVE_PROVIDER_CASES.some(({ apiKey }) => apiKey != null && apiKey !== '')
78
+ ) {
79
+ throw new Error(
80
+ 'No graph-subagent live-provider credentials are configured.'
81
+ );
82
+ }
83
+
84
+ function createLiveAgent(
85
+ testCase: LiveProviderCase,
86
+ agentId: string,
87
+ marker: string
88
+ ): t.AgentInputs {
89
+ return {
90
+ agentId,
91
+ provider: testCase.provider,
92
+ clientOptions: {
93
+ modelName: testCase.model,
94
+ apiKey: testCase.apiKey,
95
+ temperature: 0,
96
+ maxTokens: 64,
97
+ streaming: true,
98
+ streamUsage: true,
99
+ },
100
+ instructions: `You are the ${agentId} validation stage. When asked to proceed, reply with exactly ${marker} and no other text.`,
101
+ maxContextTokens: 8_000,
102
+ };
103
+ }
104
+
105
+ function getGraphSubagentTool(run: Run<t.IState>): t.GenericTool {
106
+ const tools = (run.Graph as StandardGraph).agentContexts.get('parent')
107
+ ?.graphTools as t.GenericTool[] | undefined;
108
+ const subagentTool = tools?.find(
109
+ (tool) => 'name' in tool && tool.name === Constants.SUBAGENT
110
+ );
111
+ if (subagentTool == null) {
112
+ throw new Error('Expected graph subagent tool');
113
+ }
114
+ return subagentTool;
115
+ }
116
+
117
+ describeIfLive('Graph subagent live providers', () => {
118
+ for (const testCase of LIVE_PROVIDER_CASES) {
119
+ const providerSelected =
120
+ requestedProvider == null ||
121
+ requestedProvider === '' ||
122
+ requestedProvider.toLowerCase() === testCase.provider.toLowerCase();
123
+ const itIfAvailable =
124
+ providerSelected && testCase.apiKey != null && testCase.apiKey !== ''
125
+ ? it
126
+ : it.skip;
127
+
128
+ itIfAvailable(
129
+ `${testCase.label} runs a bounded team and returns only its result member`,
130
+ async () => {
131
+ const usageEvents: t.SubagentUsageEvent[] = [];
132
+ const updateEvents: t.SubagentUpdateEvent[] = [];
133
+ const entry = createLiveAgent(testCase, 'entry', 'ENTRY_MARKER');
134
+ const worker = createLiveAgent(testCase, 'worker', 'WORKER_MARKER');
135
+ const resultAgent = createLiveAgent(testCase, 'result', 'FINAL_MARKER');
136
+ const parent: t.AgentInputs = {
137
+ ...createLiveAgent(testCase, 'parent', 'PARENT_MARKER'),
138
+ maxSubagentDepth: 1,
139
+ subagentConfigs: [
140
+ {
141
+ kind: 'graph',
142
+ type: 'live-team',
143
+ name: 'Live Team',
144
+ description: 'Runs a three-member live-provider chain.',
145
+ maxTurns: 2,
146
+ agents: [entry, worker, resultAgent],
147
+ edges: [
148
+ {
149
+ from: 'entry',
150
+ to: 'worker',
151
+ edgeType: 'direct',
152
+ prompt: 'Proceed with the worker validation stage.',
153
+ },
154
+ {
155
+ from: 'worker',
156
+ to: 'result',
157
+ edgeType: 'direct',
158
+ prompt: 'Proceed with the result validation stage.',
159
+ },
160
+ ],
161
+ entryAgentId: 'entry',
162
+ resultAgentId: 'result',
163
+ },
164
+ ],
165
+ };
166
+ const rootRunId = `graph-subagent-live-${testCase.provider}-${Date.now()}`;
167
+ const run = await Run.create<t.IState>({
168
+ runId: rootRunId,
169
+ graphConfig: { type: 'standard', agents: [parent] },
170
+ returnContent: true,
171
+ skipCleanup: true,
172
+ subagentUsageSink: (event) => {
173
+ usageEvents.push(event);
174
+ },
175
+ customHandlers: {
176
+ [GraphEvents.ON_SUBAGENT_UPDATE]: {
177
+ handle: (_event, data): void => {
178
+ updateEvents.push(data as t.SubagentUpdateEvent);
179
+ },
180
+ },
181
+ },
182
+ });
183
+ const invokeConfig: RunnableConfig = {
184
+ configurable: {
185
+ thread_id: `graph-subagent-live-${testCase.provider}`,
186
+ },
187
+ };
188
+
189
+ const output = await getGraphSubagentTool(run).invoke(
190
+ {
191
+ description: 'Begin the entry validation stage.',
192
+ subagent_type: 'live-team',
193
+ },
194
+ invokeConfig
195
+ );
196
+ const outputText = String(output);
197
+
198
+ expect(outputText).toContain('FINAL_MARKER');
199
+ expect(outputText).not.toContain('ENTRY_MARKER');
200
+ expect(outputText).not.toContain('WORKER_MARKER');
201
+ expect(usageEvents.map((event) => event.memberAgentId).sort()).toEqual([
202
+ 'entry',
203
+ 'result',
204
+ 'worker',
205
+ ]);
206
+ for (const event of usageEvents) {
207
+ expect(event.runId).toBe(rootRunId);
208
+ expect(event.parentRunId).toBe(rootRunId);
209
+ expect(event.depth).toBe(1);
210
+ expect(event.ancestry?.map((entry) => entry.subagentType)).toEqual([
211
+ 'live-team',
212
+ ]);
213
+ expect(event.subagentKind).toBe('graph');
214
+ expect(event.provider).toBe(testCase.provider);
215
+ expect(event.model).toBeTruthy();
216
+ expect(event.usage.total_tokens).toBeGreaterThan(0);
217
+ }
218
+ expect(
219
+ updateEvents.filter((event) => event.phase === 'start')
220
+ ).toHaveLength(1);
221
+ expect(
222
+ updateEvents.filter((event) => event.phase === 'stop')
223
+ ).toHaveLength(1);
224
+ for (const event of updateEvents) {
225
+ expect(event.runId).toBe(rootRunId);
226
+ expect(event.parentRunId).toBe(rootRunId);
227
+ expect(event.depth).toBe(1);
228
+ expect(event.ancestry?.map((entry) => entry.subagentType)).toEqual([
229
+ 'live-team',
230
+ ]);
231
+ }
232
+ expect(
233
+ new Set(
234
+ updateEvents
235
+ .map((event) => event.memberAgentId)
236
+ .filter((agentId): agentId is string => agentId != null)
237
+ )
238
+ ).toEqual(new Set(['entry', 'worker', 'result']));
239
+ }
240
+ );
241
+ }
242
+ });