@librechat/agents 3.6.3 → 3.6.5

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 (72) hide show
  1. package/dist/cjs/graphs/Graph.cjs +44 -19
  2. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  3. package/dist/cjs/hooks/HookRegistry.cjs +7 -1
  4. package/dist/cjs/hooks/HookRegistry.cjs.map +1 -1
  5. package/dist/cjs/hooks/index.cjs +1 -1
  6. package/dist/cjs/langchain/index.cjs +12 -0
  7. package/dist/cjs/langchain/messages.cjs +12 -0
  8. package/dist/cjs/langfuseConfig.cjs +16 -0
  9. package/dist/cjs/langfuseConfig.cjs.map +1 -1
  10. package/dist/cjs/langfuseSpanRegistry.cjs +16 -4
  11. package/dist/cjs/langfuseSpanRegistry.cjs.map +1 -1
  12. package/dist/cjs/main.cjs +15 -1
  13. package/dist/cjs/run.cjs +4 -0
  14. package/dist/cjs/run.cjs.map +1 -1
  15. package/dist/cjs/tools/SubagentTool.cjs +14 -4
  16. package/dist/cjs/tools/SubagentTool.cjs.map +1 -1
  17. package/dist/cjs/tools/ToolNode.cjs +1 -1
  18. package/dist/cjs/tools/subagent/InMemorySubagentTaskStore.cjs +403 -0
  19. package/dist/cjs/tools/subagent/InMemorySubagentTaskStore.cjs.map +1 -0
  20. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +201 -61
  21. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
  22. package/dist/cjs/tools/subagent/index.cjs +1 -0
  23. package/dist/esm/graphs/Graph.mjs +44 -19
  24. package/dist/esm/graphs/Graph.mjs.map +1 -1
  25. package/dist/esm/hooks/HookRegistry.mjs +7 -1
  26. package/dist/esm/hooks/HookRegistry.mjs.map +1 -1
  27. package/dist/esm/hooks/index.mjs +1 -1
  28. package/dist/esm/langchain/index.mjs +2 -2
  29. package/dist/esm/langchain/messages.mjs +2 -2
  30. package/dist/esm/langfuseConfig.mjs +16 -0
  31. package/dist/esm/langfuseConfig.mjs.map +1 -1
  32. package/dist/esm/langfuseSpanRegistry.mjs +16 -4
  33. package/dist/esm/langfuseSpanRegistry.mjs.map +1 -1
  34. package/dist/esm/main.mjs +4 -3
  35. package/dist/esm/run.mjs +4 -0
  36. package/dist/esm/run.mjs.map +1 -1
  37. package/dist/esm/tools/SubagentTool.mjs +14 -4
  38. package/dist/esm/tools/SubagentTool.mjs.map +1 -1
  39. package/dist/esm/tools/ToolNode.mjs +1 -1
  40. package/dist/esm/tools/subagent/InMemorySubagentTaskStore.mjs +403 -0
  41. package/dist/esm/tools/subagent/InMemorySubagentTaskStore.mjs.map +1 -0
  42. package/dist/esm/tools/subagent/SubagentExecutor.mjs +201 -61
  43. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
  44. package/dist/esm/tools/subagent/index.mjs +1 -0
  45. package/dist/types/graphs/Graph.d.ts +6 -3
  46. package/dist/types/hooks/HookRegistry.d.ts +8 -0
  47. package/dist/types/langchain/messages.d.ts +2 -2
  48. package/dist/types/langfuseSpanRegistry.d.ts +9 -4
  49. package/dist/types/run.d.ts +1 -0
  50. package/dist/types/tools/SubagentTool.d.ts +5 -2
  51. package/dist/types/tools/subagent/InMemorySubagentTaskStore.d.ts +45 -0
  52. package/dist/types/tools/subagent/SubagentExecutor.d.ts +30 -3
  53. package/dist/types/tools/subagent/index.d.ts +2 -0
  54. package/dist/types/types/graph.d.ts +22 -4
  55. package/dist/types/types/index.d.ts +1 -0
  56. package/dist/types/types/run.d.ts +6 -0
  57. package/dist/types/types/subagentTasks.d.ts +172 -0
  58. package/package.json +1 -1
  59. package/src/graphs/Graph.ts +75 -30
  60. package/src/hooks/HookRegistry.ts +18 -0
  61. package/src/langchain/messages.ts +3 -0
  62. package/src/langfuseConfig.ts +43 -0
  63. package/src/langfuseSpanRegistry.ts +42 -4
  64. package/src/run.ts +4 -0
  65. package/src/tools/SubagentTool.ts +37 -3
  66. package/src/tools/subagent/InMemorySubagentTaskStore.ts +632 -0
  67. package/src/tools/subagent/SubagentExecutor.ts +411 -74
  68. package/src/tools/subagent/index.ts +2 -0
  69. package/src/types/graph.ts +22 -4
  70. package/src/types/index.ts +1 -0
  71. package/src/types/run.ts +6 -0
  72. package/src/types/subagentTasks.ts +162 -0
@@ -1312,6 +1312,8 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1312
1312
  subagentUsageSink?: t.SubagentUsageSink;
1313
1313
  /** See {@link t.StandardGraphInput.subagentScope}. */
1314
1314
  subagentScope: boolean;
1315
+ /** See {@link t.StandardGraphInput.subagentTasks}. */
1316
+ subagentTasks: t.SubagentTaskConfig | undefined;
1315
1317
  /** See {@link t.StandardGraphInput.subagentExecutionContext}. */
1316
1318
  private readonly subagentExecutionContext?: t.SubagentExecutionContext;
1317
1319
  /** See {@link t.StandardGraphInput.preemption}. */
@@ -1445,6 +1447,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1445
1447
  indexTokenCountMap,
1446
1448
  calibrationRatio,
1447
1449
  subagentUsageSink,
1450
+ subagentTasks,
1448
1451
  subagentScope,
1449
1452
  subagentExecutionContext,
1450
1453
  preemption,
@@ -1469,6 +1472,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1469
1472
  this.signal = signal;
1470
1473
  this.langfuse = langfuse;
1471
1474
  this.subagentUsageSink = subagentUsageSink;
1475
+ this.subagentTasks = subagentTasks;
1472
1476
  this.subagentScope = subagentScope === true;
1473
1477
  this.subagentExecutionContext = subagentExecutionContext;
1474
1478
  this.preemption = preemption;
@@ -1695,8 +1699,9 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1695
1699
  * budget is taken by {@link claimPreemptSeal} once the accumulated chunk is
1696
1700
  * known to be safe, so a chunk that cannot seal never spends budget.
1697
1701
  *
1698
- * Subagent scopes never seal: a steer targets the top-level conversation,
1699
- * and a child run must finish so its parent sees a complete result.
1702
+ * Ordinary subagent scopes never receive `preemption`. A detached child may
1703
+ * receive a dedicated parent-control preemption source, in which case the
1704
+ * same provider-safe seal path is intentionally reused inside that child.
1700
1705
  */
1701
1706
  /** Internal seal preconditions only — no host callback, no side effects. */
1702
1707
  private canClaimPreemptSeal(): boolean {
@@ -1710,7 +1715,6 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1710
1715
  const runId =
1711
1716
  (this.config?.configurable?.run_id as string | undefined) ?? this.runId;
1712
1717
  return (
1713
- !this.subagentScope &&
1714
1718
  this.preemption != null &&
1715
1719
  !this.preemptSealInFlight &&
1716
1720
  this.preemptSealBudgetUsed < resolveMaxSeals(this.preemption.maxSeals) &&
@@ -4769,19 +4773,12 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
4769
4773
  }
4770
4774
  const getParentHandlerRegistry = (): HandlerRegistry | undefined =>
4771
4775
  this.handlerRegistry ?? this.parentToolHandlerRegistry;
4772
- const createConfiguredChildGraph: GraphFactory = (request) => {
4773
- const childGraph = this.graphFactory(request);
4774
- if (this.subagentModelOverride != null) {
4775
- childGraph.overrideModel = this.subagentModelOverride;
4776
- childGraph.setSubagentModelOverride(this.subagentModelOverride);
4777
- }
4778
- const childHandlerRegistry = createChildHandlerRegistry(
4779
- getParentHandlerRegistry()
4780
- );
4781
- // Pure execution-ordering hint (unlike `humanInTheLoop`). It only
4782
- // reorders tools already in the child's direct group; it does not
4783
- // force a schema-only event tool onto the direct execution path.
4784
- applyGraphRuntimeConfig(childGraph, {
4776
+ const snapshotChildGraphFactory = (
4777
+ parentHandlerRegistry: HandlerRegistry | undefined
4778
+ ): GraphFactory => {
4779
+ const graphFactory = this.graphFactory;
4780
+ const subagentModelOverride = this.subagentModelOverride;
4781
+ const runtimeConfig = {
4785
4782
  hookRegistry: this.hookRegistry,
4786
4783
  humanInTheLoop: this.humanInTheLoop,
4787
4784
  toolOutputReferences: this.toolOutputReferences,
@@ -4789,18 +4786,33 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
4789
4786
  codeSessionToolNames: this.codeSessionToolNames,
4790
4787
  interruptingToolNames: this.interruptingToolNames,
4791
4788
  toolExecution: this.toolExecution,
4792
- });
4793
- if (this.humanInTheLoop?.enabled === true) {
4794
- childGraph.compileOptions = {
4795
- checkpointer: this.compileOptions?.checkpointer,
4796
- };
4797
- }
4798
- childGraph.parentToolHandlerRegistry = childHandlerRegistry;
4799
- childGraph.eventToolExecutionAvailable =
4800
- childHandlerRegistry?.getHandler(GraphEvents.ON_TOOL_EXECUTE) !=
4801
- null;
4802
- return childGraph;
4789
+ };
4790
+ const checkpointer = this.compileOptions?.checkpointer;
4791
+ return (request): StandardGraph => {
4792
+ const childGraph = graphFactory(request);
4793
+ if (subagentModelOverride != null) {
4794
+ childGraph.overrideModel = subagentModelOverride;
4795
+ childGraph.setSubagentModelOverride(subagentModelOverride);
4796
+ }
4797
+ const childHandlerRegistry = createChildHandlerRegistry(
4798
+ parentHandlerRegistry
4799
+ );
4800
+ // Pure execution-ordering hint (unlike `humanInTheLoop`). It only
4801
+ // reorders tools already in the child's direct group; it does not
4802
+ // force a schema-only event tool onto the direct execution path.
4803
+ applyGraphRuntimeConfig(childGraph, runtimeConfig);
4804
+ if (runtimeConfig.humanInTheLoop?.enabled === true) {
4805
+ childGraph.compileOptions = { checkpointer };
4806
+ }
4807
+ childGraph.parentToolHandlerRegistry = childHandlerRegistry;
4808
+ childGraph.eventToolExecutionAvailable =
4809
+ childHandlerRegistry?.getHandler(GraphEvents.ON_TOOL_EXECUTE) !=
4810
+ null;
4811
+ return childGraph;
4812
+ };
4803
4813
  };
4814
+ const createConfiguredChildGraph: GraphFactory = (request) =>
4815
+ snapshotChildGraphFactory(getParentHandlerRegistry())(request);
4804
4816
  const executor = new SubagentExecutor({
4805
4817
  configs: new Map(
4806
4818
  executableConfigs.map((config) => [config.type, config])
@@ -4820,6 +4832,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
4820
4832
  langfuse: this.langfuse,
4821
4833
  tokenCounter: agentContext.tokenCounter,
4822
4834
  usageSink: this.subagentUsageSink,
4835
+ taskConfig: this.subagentTasks,
4823
4836
  streamLimits: this.streamLimits,
4824
4837
  humanInTheLoop: this.humanInTheLoop,
4825
4838
  checkpointer: this.compileOptions?.checkpointer,
@@ -4830,6 +4843,10 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
4830
4843
  input,
4831
4844
  }),
4832
4845
  createChildGraphByKind: createConfiguredChildGraph,
4846
+ createDetachedChildGraphFactory: (
4847
+ parentHandlerRegistry
4848
+ ): GraphFactory =>
4849
+ snapshotChildGraphFactory(parentHandlerRegistry),
4833
4850
  });
4834
4851
  this.registerSubagentExecutor(executor);
4835
4852
 
@@ -4837,6 +4854,8 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
4837
4854
  const input = rawInput as {
4838
4855
  description?: string;
4839
4856
  subagent_type?: string;
4857
+ subagent_thread_id?: string;
4858
+ run_in_background?: boolean;
4840
4859
  };
4841
4860
  const description =
4842
4861
  typeof input.description === 'string' &&
@@ -4845,6 +4864,11 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
4845
4864
  : DEFAULT_SUBAGENT_DESCRIPTION;
4846
4865
  const subagentType =
4847
4866
  typeof input.subagent_type === 'string' ? input.subagent_type : '';
4867
+ const subagentThreadId =
4868
+ typeof input.subagent_thread_id === 'string' &&
4869
+ input.subagent_thread_id.trim() !== ''
4870
+ ? input.subagent_thread_id.trim()
4871
+ : undefined;
4848
4872
  const threadId = config.configurable?.thread_id as string | undefined;
4849
4873
  /** Surface the parent call id so child checkpoints, interrupts, and
4850
4874
  * update events remain correlated across replay and resume. */
@@ -4869,7 +4893,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
4869
4893
  const batchScope = config.configurable?.[
4870
4894
  RUN_BREAKER_SCOPE_CONFIG_KEY
4871
4895
  ] as RunBreakerScope | undefined;
4872
- const result = await executor.execute({
4896
+ const executeParams = {
4873
4897
  description,
4874
4898
  subagentType,
4875
4899
  threadId,
@@ -4885,9 +4909,30 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
4885
4909
  parentConfigurable: config.configurable as
4886
4910
  | Record<string, unknown>
4887
4911
  | undefined,
4888
- });
4912
+ };
4913
+ if (input.run_in_background === true) {
4914
+ return executor.executeInBackground({
4915
+ ...executeParams,
4916
+ ...(subagentThreadId == null
4917
+ ? {}
4918
+ : { subagentThreadId }),
4919
+ });
4920
+ }
4921
+ if (subagentThreadId != null) {
4922
+ return JSON.stringify({
4923
+ status: 'rejected',
4924
+ tool: Constants.SUBAGENT,
4925
+ message:
4926
+ 'Child-thread continuation requires run_in_background.',
4927
+ });
4928
+ }
4929
+ const result = await executor.execute(executeParams);
4889
4930
  return result.content;
4890
- }, buildSubagentToolParams(executableConfigs));
4931
+ }, buildSubagentToolParams(executableConfigs, {
4932
+ background: this.subagentTasks != null,
4933
+ threadContinuation:
4934
+ this.subagentTasks?.store.supportsThreadContinuation === true,
4935
+ }));
4891
4936
  const replayableSubagentTool = subagentTool as typeof subagentTool &
4892
4937
  ReplayableSubagentTool;
4893
4938
  replayableSubagentTool[SUBAGENT_REPLAY_CONTROLLER] = {
@@ -6,6 +6,7 @@ import type {
6
6
  ToolApprovalReplaySnapshot,
7
7
  AggregatedHookResult,
8
8
  } from './types';
9
+ import { HOOK_EVENTS } from './types';
9
10
 
10
11
  function serializeApprovalKey(key: ToolApprovalReplayKey): string {
11
12
  return JSON.stringify([key.executionScope, key.agentId, key.toolUseId]);
@@ -227,6 +228,23 @@ export class HookRegistry {
227
228
  }
228
229
  }
229
230
 
231
+ /**
232
+ * Takes an isolated policy snapshot for work that may outlive the source
233
+ * run. Global and source-session matchers become global to the returned
234
+ * task-local registry, so parent cleanup and one-shot hook consumption
235
+ * cannot mutate the detached child (or vice versa). Runtime halt signals
236
+ * and pending approvals are intentionally not copied.
237
+ */
238
+ forkSession(sourceSessionId: string): HookRegistry {
239
+ const fork = new HookRegistry();
240
+ for (const event of HOOK_EVENTS) {
241
+ for (const matcher of this.getMatchers(event, sourceSessionId)) {
242
+ fork.register(event, matcher);
243
+ }
244
+ }
245
+ return fork;
246
+ }
247
+
230
248
  getPendingToolApproval(
231
249
  sessionId: string,
232
250
  key: ToolApprovalReplayKey
@@ -10,6 +10,8 @@ export {
10
10
  isAIMessage,
11
11
  isBaseMessage,
12
12
  isToolMessage,
13
+ mapChatMessagesToStoredMessages,
14
+ mapStoredMessagesToChatMessages,
13
15
  } from '@langchain/core/messages';
14
16
 
15
17
  export type {
@@ -17,5 +19,6 @@ export type {
17
19
  MessageContent,
18
20
  MessageContentText,
19
21
  MessageContentImageUrl,
22
+ StoredMessage,
20
23
  UsageMetadata,
21
24
  } from '@langchain/core/messages';
@@ -175,6 +175,44 @@ export function resolveToolOutputTracingConfig(
175
175
  };
176
176
  }
177
177
 
178
+ /**
179
+ * Merges header maps case-insensitively, keeping the override's casing.
180
+ *
181
+ * A plain spread would keep both `X-Proxy-Token` and `x-proxy-token`, and
182
+ * filling a fetch `Headers` from that record *appends* rather than replaces —
183
+ * the exporter would send one comma-joined `run-token, agent-token` value, so
184
+ * the agent override never cleanly wins and a gateway sees a malformed
185
+ * credential. Matches the case-insensitive identity already used for the
186
+ * destination key.
187
+ */
188
+ function mergeAdditionalHeaders(
189
+ base?: Record<string, string>,
190
+ override?: Record<string, string>
191
+ ): Record<string, string> | undefined {
192
+ if (base == null && override == null) {
193
+ return undefined;
194
+ }
195
+
196
+ const merged: Record<string, string> = { ...base };
197
+ if (override == null) {
198
+ return merged;
199
+ }
200
+
201
+ const baseKeyByLower = new Map<string, string>(
202
+ Object.keys(merged).map((key) => [key.toLowerCase(), key])
203
+ );
204
+ for (const [key, value] of Object.entries(override)) {
205
+ const lower = key.toLowerCase();
206
+ const existingKey = baseKeyByLower.get(lower);
207
+ if (existingKey != null && existingKey !== key) {
208
+ delete merged[existingKey];
209
+ }
210
+ merged[key] = value;
211
+ baseKeyByLower.set(lower, key);
212
+ }
213
+ return merged;
214
+ }
215
+
178
216
  export function resolveLangfuseConfig(
179
217
  runLangfuse?: t.LangfuseConfig,
180
218
  agentLangfuse?: t.LangfuseConfig
@@ -208,6 +246,10 @@ export function resolveLangfuseConfig(
208
246
  ...agentLangfuse.metadata,
209
247
  }
210
248
  : undefined;
249
+ const additionalHeaders = mergeAdditionalHeaders(
250
+ runLangfuse.additionalHeaders,
251
+ agentLangfuse.additionalHeaders
252
+ );
211
253
  const librechatTraceAttributes =
212
254
  runLangfuse.librechatTraceAttributes != null ||
213
255
  agentLangfuse.librechatTraceAttributes != null
@@ -230,6 +272,7 @@ export function resolveLangfuseConfig(
230
272
  ...runLangfuse,
231
273
  ...agentLangfuse,
232
274
  ...(metadata != null ? { metadata } : {}),
275
+ ...(additionalHeaders != null ? { additionalHeaders } : {}),
233
276
  ...(librechatTraceAttributes != null ? { librechatTraceAttributes } : {}),
234
277
  ...(tags != null ? { tags } : {}),
235
278
  ...(toolNodeTracing != null ? { toolNodeTracing } : {}),
@@ -59,6 +59,12 @@ function resolveLangfuseEnvironment(
59
59
  return undefined;
60
60
  }
61
61
 
62
+ function hasAdditionalHeaders(
63
+ headers?: Record<string, string>
64
+ ): headers is Record<string, string> {
65
+ return headers != null && Object.keys(headers).length > 0;
66
+ }
67
+
62
68
  export function getLangfuseSpanProcessorParams(
63
69
  langfuse?: t.LangfuseConfig
64
70
  ): LangfuseSpanProcessorParams | undefined {
@@ -66,6 +72,9 @@ export function getLangfuseSpanProcessorParams(
66
72
  return undefined;
67
73
  }
68
74
  const environment = resolveLangfuseEnvironment(langfuse);
75
+ const additionalHeaders = hasAdditionalHeaders(langfuse?.additionalHeaders)
76
+ ? { additionalHeaders: langfuse.additionalHeaders }
77
+ : {};
69
78
  if (hasLangfuseConfigCredentials(langfuse)) {
70
79
  return {
71
80
  publicKey: langfuse.publicKey,
@@ -75,6 +84,7 @@ export function getLangfuseSpanProcessorParams(
75
84
  ...(langfuse.mediaUploadEnabled != null
76
85
  ? { mediaUploadEnabled: langfuse.mediaUploadEnabled }
77
86
  : {}),
87
+ ...additionalHeaders,
78
88
  };
79
89
  }
80
90
  if (hasLangfuseEnvConfig()) {
@@ -90,6 +100,7 @@ export function getLangfuseSpanProcessorParams(
90
100
  ...(langfuse?.mediaUploadEnabled != null
91
101
  ? { mediaUploadEnabled: langfuse.mediaUploadEnabled }
92
102
  : {}),
103
+ ...additionalHeaders,
93
104
  };
94
105
  }
95
106
  if (isPresent(langfuse?.baseUrl) && hasLangfuseEnvCredentials()) {
@@ -101,6 +112,7 @@ export function getLangfuseSpanProcessorParams(
101
112
  ...(langfuse.mediaUploadEnabled != null
102
113
  ? { mediaUploadEnabled: langfuse.mediaUploadEnabled }
103
114
  : {}),
115
+ ...additionalHeaders,
104
116
  };
105
117
  }
106
118
  return undefined;
@@ -112,12 +124,37 @@ function hashCacheKeyValue(value: string | undefined): string | undefined {
112
124
  : undefined;
113
125
  }
114
126
 
127
+ /**
128
+ * Order- and case-insensitive digest of the custom headers sent to a
129
+ * destination, so header maps that differ only in key order or header-name
130
+ * casing resolve to one destination instead of duplicating its exporter.
131
+ * Hashed because these values are credentials (proxy tokens, gateway keys).
132
+ * Absent and empty both yield `undefined`, keeping keys stable for the
133
+ * overwhelmingly common no-headers case.
134
+ */
135
+ function hashAdditionalHeaders(
136
+ headers: Record<string, string> | undefined
137
+ ): string | undefined {
138
+ if (!hasAdditionalHeaders(headers)) {
139
+ return undefined;
140
+ }
141
+ const normalized = Object.entries(headers)
142
+ .map(([name, value]) => JSON.stringify([name.trim().toLowerCase(), value]))
143
+ .sort();
144
+ return hashCacheKeyValue(normalized.join('\n'));
145
+ }
146
+
115
147
  /**
116
148
  * Identity of an export destination (project credentials + endpoint +
117
- * environment) only. Processor-level policies like `toolOutputTracing` are
118
- * deliberately excluded: two spans exporting to the same project under
119
- * different redaction settings still share a destination and may parent one
120
- * another.
149
+ * environment + custom headers) only. Processor-level policies like
150
+ * `toolOutputTracing` are deliberately excluded: two spans exporting to the
151
+ * same project under different redaction settings still share a destination
152
+ * and may parent one another.
153
+ *
154
+ * Custom headers are included because a gateway may route on them, making two
155
+ * otherwise-identical configs different projects. Treating them as part of the
156
+ * destination keeps a run from inheriting a parent span bound elsewhere, and
157
+ * keeps a rotated proxy credential from reusing the stale exporter.
121
158
  */
122
159
  export function getLangfuseDestinationKey(
123
160
  params: LangfuseSpanProcessorParams
@@ -127,6 +164,7 @@ export function getLangfuseDestinationKey(
127
164
  secretKeyHash: hashCacheKeyValue(params.secretKey),
128
165
  baseUrl: params.baseUrl,
129
166
  environment: params.environment,
167
+ additionalHeadersHash: hashAdditionalHeaders(params.additionalHeaders),
130
168
  });
131
169
  }
132
170
 
package/src/run.ts CHANGED
@@ -296,6 +296,7 @@ export class Run<_T extends t.BaseGraphState> {
296
296
  private subagentUsageSink?: t.SubagentUsageSink;
297
297
  private preemption?: t.StreamPreemption;
298
298
  private streamLimits?: t.StreamLimits;
299
+ private subagentTasks?: t.SubagentTaskConfig;
299
300
  private indexTokenCountMap?: Record<string, number>;
300
301
  calibrationRatio: number = 1;
301
302
  graphRunnable?: t.CompiledStateWorkflow;
@@ -363,6 +364,7 @@ export class Run<_T extends t.BaseGraphState> {
363
364
  this.interruptingToolNames = config.interruptingToolNames;
364
365
  this.toolExecution = config.toolExecution;
365
366
  this.subagentUsageSink = config.subagentUsageSink;
367
+ this.subagentTasks = config.subagentTasks;
366
368
  this.preemption = config.preemption;
367
369
  this.streamLimits = config.streamLimits;
368
370
 
@@ -455,6 +457,7 @@ export class Run<_T extends t.BaseGraphState> {
455
457
  indexTokenCountMap: this.indexTokenCountMap,
456
458
  calibrationRatio: this.calibrationRatio,
457
459
  subagentUsageSink: this.subagentUsageSink,
460
+ subagentTasks: this.subagentTasks,
458
461
  preemption: this.preemption,
459
462
  streamLimits: this.streamLimits,
460
463
  },
@@ -493,6 +496,7 @@ export class Run<_T extends t.BaseGraphState> {
493
496
  indexTokenCountMap: this.indexTokenCountMap,
494
497
  calibrationRatio: this.calibrationRatio,
495
498
  subagentUsageSink: this.subagentUsageSink,
499
+ subagentTasks: this.subagentTasks,
496
500
  preemption: this.preemption,
497
501
  streamLimits: this.streamLimits,
498
502
  },
@@ -13,7 +13,7 @@ WHEN TO USE:
13
13
  - A specialized subagent is available for the task domain.
14
14
 
15
15
  WHAT HAPPENS:
16
- - A fresh agent or configured agent graph is created with the task description as its only input.
16
+ - A fresh agent or configured agent graph is created with the task description as its only input, unless the host enables child-thread continuation and you provide a saved thread id.
17
17
  - The delegated agent or team runs to completion using isolated tools and context.
18
18
  - Only the single agent's final response or the graph's designated result-agent response is returned to you.
19
19
 
@@ -27,6 +27,12 @@ const DESCRIPTION_PROP_DESCRIPTION =
27
27
  const SUBAGENT_TYPE_PROP_DESCRIPTION =
28
28
  'Which subagent type to delegate to. Must be one of the available types.';
29
29
 
30
+ const RUN_IN_BACKGROUND_PROP_DESCRIPTION =
31
+ 'Set true to start the subagent as a detached process-local task and return a background_task_id immediately. Poll the host background-task tool to collect its result. The task can outlive this turn but does not survive a process restart.';
32
+
33
+ const SUBAGENT_THREAD_PROP_DESCRIPTION =
34
+ 'Continue a host-owned child thread using a fresh execution lease. The saved thread must belong to this scope and subagent type. Only available with run_in_background.';
35
+
30
36
  export const SubagentToolSchema = {
31
37
  type: 'object',
32
38
  properties: {
@@ -54,7 +60,10 @@ export const SubagentToolDefinition: LCTool = {
54
60
  * Used by `Graph.createAgentNode()` when constructing the runtime tool instance.
55
61
  * Extends `SubagentToolSchema` by populating `subagent_type.enum` dynamically.
56
62
  */
57
- export function buildSubagentToolParams(configs: SubagentConfig[]): {
63
+ export function buildSubagentToolParams(
64
+ configs: SubagentConfig[],
65
+ options: { background?: boolean; threadContinuation?: boolean } = {}
66
+ ): {
58
67
  name: string;
59
68
  schema: JsonSchemaType;
60
69
  description: string;
@@ -79,10 +88,35 @@ export function buildSubagentToolParams(configs: SubagentConfig[]): {
79
88
  enum: types,
80
89
  description: `${SUBAGENT_TYPE_PROP_DESCRIPTION} Available: ${types.join(', ')}.`,
81
90
  },
91
+ ...(options.background === true
92
+ ? {
93
+ run_in_background: {
94
+ type: 'boolean',
95
+ description: RUN_IN_BACKGROUND_PROP_DESCRIPTION,
96
+ },
97
+ }
98
+ : {}),
99
+ ...(options.background === true &&
100
+ options.threadContinuation === true
101
+ ? {
102
+ subagent_thread_id: {
103
+ type: 'string',
104
+ description: SUBAGENT_THREAD_PROP_DESCRIPTION,
105
+ },
106
+ }
107
+ : {}),
82
108
  },
83
109
  required: ['description', 'subagent_type'],
84
110
  },
85
- description: `${SubagentToolDescription}\n\nAvailable types:\n${typeDescriptions}`,
111
+ description: `${SubagentToolDescription}${
112
+ options.background === true
113
+ ? '\n\nBACKGROUND EXECUTION:\n- Set run_in_background to true when you do not need the result immediately. The call returns a background_task_id; use the host background-task tools to poll, steer, queue, interrupt, or cancel it.'
114
+ : ''
115
+ }${
116
+ options.background === true && options.threadContinuation === true
117
+ ? '\n- To continue a completed child later, set run_in_background and pass its subagent_thread_id. The host starts a fresh execution from saved history; it does not restore a completed runtime.'
118
+ : ''
119
+ }\n\nAvailable types:\n${typeDescriptions}`,
86
120
  };
87
121
  }
88
122