@librechat/agents 3.5.1 → 3.6.1

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 (106) hide show
  1. package/dist/cjs/agents/AgentContext.cjs +5 -2
  2. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  3. package/dist/cjs/graphs/Graph.cjs +148 -22
  4. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  5. package/dist/cjs/graphs/MultiAgentGraph.cjs +2 -1
  6. package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
  7. package/dist/cjs/main.cjs +4 -0
  8. package/dist/cjs/prompts/activityLabel.cjs +1 -0
  9. package/dist/cjs/prompts/reasoningLabel.cjs +60 -0
  10. package/dist/cjs/prompts/reasoningLabel.cjs.map +1 -0
  11. package/dist/cjs/run.cjs +161 -11
  12. package/dist/cjs/run.cjs.map +1 -1
  13. package/dist/cjs/stream.cjs +8 -6
  14. package/dist/cjs/stream.cjs.map +1 -1
  15. package/dist/cjs/tools/BashExecutor.cjs +8 -7
  16. package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
  17. package/dist/cjs/tools/BashProgrammaticToolCalling.cjs +5 -4
  18. package/dist/cjs/tools/BashProgrammaticToolCalling.cjs.map +1 -1
  19. package/dist/cjs/tools/CodeExecutor.cjs +26 -7
  20. package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
  21. package/dist/cjs/tools/ProgrammaticToolCalling.cjs +7 -6
  22. package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
  23. package/dist/cjs/tools/ToolNode.cjs +33 -10
  24. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  25. package/dist/cjs/tools/ToolSearch.cjs +1 -1
  26. package/dist/cjs/tools/ToolSearch.cjs.map +1 -1
  27. package/dist/cjs/tools/runStepResume.cjs +51 -0
  28. package/dist/cjs/tools/runStepResume.cjs.map +1 -0
  29. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +15 -43
  30. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
  31. package/dist/cjs/tools/subagent/SubagentReplay.cjs +6 -1
  32. package/dist/cjs/tools/subagent/SubagentReplay.cjs.map +1 -1
  33. package/dist/cjs/utils/toolSessions.cjs +63 -0
  34. package/dist/cjs/utils/toolSessions.cjs.map +1 -0
  35. package/dist/esm/agents/AgentContext.mjs +5 -2
  36. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  37. package/dist/esm/graphs/Graph.mjs +148 -22
  38. package/dist/esm/graphs/Graph.mjs.map +1 -1
  39. package/dist/esm/graphs/MultiAgentGraph.mjs +2 -1
  40. package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
  41. package/dist/esm/main.mjs +2 -2
  42. package/dist/esm/prompts/activityLabel.mjs +1 -1
  43. package/dist/esm/prompts/reasoningLabel.mjs +57 -0
  44. package/dist/esm/prompts/reasoningLabel.mjs.map +1 -0
  45. package/dist/esm/run.mjs +161 -11
  46. package/dist/esm/run.mjs.map +1 -1
  47. package/dist/esm/stream.mjs +8 -6
  48. package/dist/esm/stream.mjs.map +1 -1
  49. package/dist/esm/tools/BashExecutor.mjs +9 -8
  50. package/dist/esm/tools/BashExecutor.mjs.map +1 -1
  51. package/dist/esm/tools/BashProgrammaticToolCalling.mjs +6 -5
  52. package/dist/esm/tools/BashProgrammaticToolCalling.mjs.map +1 -1
  53. package/dist/esm/tools/CodeExecutor.mjs +23 -8
  54. package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
  55. package/dist/esm/tools/ProgrammaticToolCalling.mjs +8 -7
  56. package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
  57. package/dist/esm/tools/ToolNode.mjs +33 -10
  58. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  59. package/dist/esm/tools/ToolSearch.mjs +2 -2
  60. package/dist/esm/tools/ToolSearch.mjs.map +1 -1
  61. package/dist/esm/tools/runStepResume.mjs +48 -0
  62. package/dist/esm/tools/runStepResume.mjs.map +1 -0
  63. package/dist/esm/tools/subagent/SubagentExecutor.mjs +15 -43
  64. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
  65. package/dist/esm/tools/subagent/SubagentReplay.mjs +6 -1
  66. package/dist/esm/tools/subagent/SubagentReplay.mjs.map +1 -1
  67. package/dist/esm/utils/toolSessions.mjs +62 -0
  68. package/dist/esm/utils/toolSessions.mjs.map +1 -0
  69. package/dist/types/agents/AgentContext.d.ts +4 -1
  70. package/dist/types/graphs/Graph.d.ts +11 -3
  71. package/dist/types/prompts/reasoningLabel.d.ts +17 -0
  72. package/dist/types/run.d.ts +8 -0
  73. package/dist/types/tools/CodeExecutor.d.ts +4 -0
  74. package/dist/types/tools/ProgrammaticToolCalling.d.ts +1 -1
  75. package/dist/types/tools/ToolNode.d.ts +3 -1
  76. package/dist/types/tools/runStepResume.d.ts +5 -0
  77. package/dist/types/tools/subagent/SubagentExecutor.d.ts +25 -0
  78. package/dist/types/tools/subagent/SubagentReplay.d.ts +2 -1
  79. package/dist/types/types/graph.d.ts +10 -1
  80. package/dist/types/types/index.d.ts +1 -0
  81. package/dist/types/types/reasoningLabel.d.ts +56 -0
  82. package/dist/types/types/stream.d.ts +18 -0
  83. package/dist/types/types/tools.d.ts +36 -16
  84. package/dist/types/utils/toolSessions.d.ts +18 -0
  85. package/package.json +1 -1
  86. package/src/agents/AgentContext.ts +7 -0
  87. package/src/graphs/Graph.ts +192 -13
  88. package/src/graphs/MultiAgentGraph.ts +1 -0
  89. package/src/prompts/reasoningLabel.ts +118 -0
  90. package/src/run.ts +330 -12
  91. package/src/stream.ts +28 -9
  92. package/src/tools/BashExecutor.ts +31 -13
  93. package/src/tools/BashProgrammaticToolCalling.ts +19 -9
  94. package/src/tools/CodeExecutor.ts +57 -16
  95. package/src/tools/ProgrammaticToolCalling.ts +26 -12
  96. package/src/tools/ToolNode.ts +58 -8
  97. package/src/tools/ToolSearch.ts +2 -2
  98. package/src/tools/runStepResume.ts +121 -0
  99. package/src/tools/subagent/SubagentExecutor.ts +67 -65
  100. package/src/tools/subagent/SubagentReplay.ts +22 -1
  101. package/src/types/graph.ts +10 -0
  102. package/src/types/index.ts +1 -0
  103. package/src/types/reasoningLabel.ts +59 -0
  104. package/src/types/stream.ts +17 -0
  105. package/src/types/tools.ts +50 -21
  106. package/src/utils/toolSessions.ts +113 -0
package/src/run.ts CHANGED
@@ -16,8 +16,11 @@ import {
16
16
  HumanMessage,
17
17
  SystemMessage,
18
18
  } from '@langchain/core/messages';
19
+ import type {
20
+ MessageContentComplex,
21
+ UsageMetadata,
22
+ } from '@langchain/core/messages';
19
23
  import type { StringPromptValue } from '@langchain/core/prompt_values';
20
- import type { MessageContentComplex } from '@langchain/core/messages';
21
24
  import type { RunnableConfig } from '@langchain/core/runnables';
22
25
  import type { MultiAgentGraph } from '@/graphs/MultiAgentGraph';
23
26
  import type { StandardGraph } from '@/graphs/Graph';
@@ -44,6 +47,12 @@ import {
44
47
  isLangfuseCallbackHandler,
45
48
  withLangfuseAttributes,
46
49
  } from '@/langfuse';
50
+ import {
51
+ REASONING_LABEL_PROMPT,
52
+ buildReasoningLabelTraceSeed,
53
+ buildReasoningLabelPrompt,
54
+ normalizeReasoningLabel,
55
+ } from '@/prompts/reasoningLabel';
47
56
  import {
48
57
  hasToolOutputTracingConfig,
49
58
  resolveLangfuseConfig,
@@ -59,6 +68,10 @@ import {
59
68
  resolveLangfuseRuntimeScope,
60
69
  withLangfuseRuntimeScope,
61
70
  } from '@/langfuseRuntimeScope';
71
+ import {
72
+ getRunStepResumeState,
73
+ stripRunStepResumeState,
74
+ } from '@/tools/runStepResume';
62
75
  import {
63
76
  Callback,
64
77
  GraphEvents,
@@ -72,6 +85,7 @@ import {
72
85
  import { applyGraphRuntimeConfig } from '@/graphs/applyGraphRuntimeConfig';
73
86
  import { createTokenCounter, encodingForModel } from '@/utils/tokens';
74
87
  import { initializeLangfuseTracing } from './instrumentation';
88
+ import { seedRunInitialSessions } from '@/utils/toolSessions';
75
89
  import { getTraceIdSeed } from '@/langfuseRuntimeContext';
76
90
  import { createGraph } from '@/graphs/createGraph';
77
91
  import { resolveMaxSeals } from '@/llm/preempt';
@@ -95,6 +109,7 @@ export const defaultOmitOptions = new Set([
95
109
 
96
110
  const ACTIVITY_LABEL_TRACE_NAME = 'LibreChat Activity Label';
97
111
  const ACTIVITY_PHASE_TRACE_NAME = 'LibreChat Activity Phase';
112
+ const REASONING_LABEL_TRACE_NAME = 'LibreChat Reasoning Label';
98
113
 
99
114
  const CUSTOM_GRAPH_EVENTS = new Set<string>([
100
115
  GraphEvents.ON_AGENT_UPDATE,
@@ -173,7 +188,9 @@ function isLangGraphResumeMapForInterrupt(
173
188
  }
174
189
 
175
190
  function getInterruptHookSessionId(payload: unknown): string | undefined {
176
- const publicPayload = stripSubagentResumeManifest(payload);
191
+ const publicPayload = stripSubagentResumeManifest(
192
+ stripRunStepResumeState(payload)
193
+ );
177
194
  if (
178
195
  publicPayload == null ||
179
196
  typeof publicPayload !== 'object' ||
@@ -193,11 +210,15 @@ type InterruptStateSnapshot = {
193
210
  values?: { messages?: BaseMessage[] };
194
211
  tasks?: Array<{
195
212
  interrupts?: Array<{ id?: string; value?: unknown }>;
213
+ state?: RunnableConfig | InterruptStateSnapshot;
196
214
  }>;
197
215
  };
198
216
 
199
217
  type WorkflowWithStateHistory = {
200
- getState?(config: RunnableConfig): Promise<InterruptStateSnapshot>;
218
+ getState?(
219
+ config: RunnableConfig,
220
+ options?: { subgraphs?: boolean }
221
+ ): Promise<InterruptStateSnapshot>;
201
222
  getStateHistory?(
202
223
  config: RunnableConfig
203
224
  ): AsyncIterableIterator<InterruptStateSnapshot>;
@@ -215,6 +236,17 @@ function getFirstPersistedInterrupt(
215
236
  return { id: pendingInterrupt.id, value: pendingInterrupt.value };
216
237
  }
217
238
  }
239
+ const nestedState = task.state;
240
+ if (
241
+ nestedState != null &&
242
+ 'tasks' in nestedState &&
243
+ Array.isArray(nestedState.tasks)
244
+ ) {
245
+ const nestedInterrupt = getFirstPersistedInterrupt(nestedState);
246
+ if (nestedInterrupt != null) {
247
+ return nestedInterrupt;
248
+ }
249
+ }
218
250
  }
219
251
  return undefined;
220
252
  }
@@ -290,6 +322,8 @@ export class Run<_T extends t.BaseGraphState> {
290
322
  private activityLabelSeq = 0;
291
323
  /** Per-run sequence for parent activity-phase trace and invocation ids. */
292
324
  private activityPhaseLabelSeq = 0;
325
+ /** Per-run sequence for reasoning-label trace and invocation ids. */
326
+ private reasoningLabelSeq = 0;
293
327
  /** Latest user turn used to keep detached phase roots conversation-shaped. */
294
328
  private activityPhaseTraceInput?: string;
295
329
  /** Distinguishes sibling forks started from the same explicit checkpoint. */
@@ -351,9 +385,25 @@ export class Run<_T extends t.BaseGraphState> {
351
385
  }
352
386
 
353
387
  if (config.initialSessions && this.Graph) {
354
- for (const [key, value] of config.initialSessions) {
355
- this.Graph.sessions.set(key, value);
356
- }
388
+ const configuredAgents =
389
+ 'agents' in config.graphConfig &&
390
+ Array.isArray(config.graphConfig.agents)
391
+ ? config.graphConfig.agents
392
+ : undefined;
393
+ const agents: Array<Pick<t.AgentInputs, 'codeSessionKey'>> =
394
+ configuredAgents ?? [
395
+ {
396
+ codeSessionKey:
397
+ 'codeSessionKey' in config.graphConfig
398
+ ? config.graphConfig.codeSessionKey
399
+ : undefined,
400
+ },
401
+ ];
402
+ seedRunInitialSessions({
403
+ sessions: this.Graph.sessions,
404
+ initialSessions: config.initialSessions,
405
+ agents,
406
+ });
357
407
  }
358
408
 
359
409
  this.returnContent = config.returnContent ?? false;
@@ -949,6 +999,12 @@ export class Run<_T extends t.BaseGraphState> {
949
999
  delete config.configurable?.[SUBAGENT_RESUME_ATTEMPT_CONFIG_KEY];
950
1000
  delete config.configurable?.[SUBAGENT_RESUME_MANIFEST_CONFIG_KEY];
951
1001
  }
1002
+ if (isResume) {
1003
+ await this.restoreInterruptFromCheckpoint(
1004
+ config,
1005
+ (inputs as Command).update
1006
+ );
1007
+ }
952
1008
 
953
1009
  /**
954
1010
  * Cancellation can arrive either at graph construction or per-call through
@@ -1503,7 +1559,9 @@ export class Run<_T extends t.BaseGraphState> {
1503
1559
  }
1504
1560
  return {
1505
1561
  ...this._interrupt,
1506
- payload: stripSubagentResumeManifest(this._interrupt.payload),
1562
+ payload: stripSubagentResumeManifest(
1563
+ stripRunStepResumeState(this._interrupt.payload)
1564
+ ),
1507
1565
  } as t.RunInterruptResult<TPayload>;
1508
1566
  }
1509
1567
 
@@ -1622,7 +1680,7 @@ export class Run<_T extends t.BaseGraphState> {
1622
1680
  await this.restoreInterruptFromCheckpoint(callerConfig, resumeUpdate);
1623
1681
  const interrupt = this._interrupt;
1624
1682
  const resumeManifest = requireValidSubagentResumeManifest(
1625
- interrupt?.payload
1683
+ stripRunStepResumeState(interrupt?.payload)
1626
1684
  );
1627
1685
  const resumeConfigurable = { ...callerConfig.configurable };
1628
1686
  delete resumeConfigurable[SUBAGENT_RESUME_ATTEMPT_CONFIG_KEY];
@@ -1709,7 +1767,7 @@ export class Run<_T extends t.BaseGraphState> {
1709
1767
  callerConfig: t.RunStreamConfig,
1710
1768
  resumeUpdate?: ResumeCommandUpdate
1711
1769
  ): Promise<void> {
1712
- if (this._interrupt != null || this.humanInTheLoop?.enabled !== true) {
1770
+ if (this._interrupt != null || !this.hasCheckpointer) {
1713
1771
  return;
1714
1772
  }
1715
1773
  const workflow = this.graphRunnable as
@@ -1719,11 +1777,16 @@ export class Run<_T extends t.BaseGraphState> {
1719
1777
  return;
1720
1778
  }
1721
1779
 
1722
- const snapshot = await workflow.getState(callerConfig as RunnableConfig);
1780
+ const snapshot = await workflow.getState(callerConfig as RunnableConfig, {
1781
+ subgraphs: true,
1782
+ });
1723
1783
  const persistedInterrupt = getFirstPersistedInterrupt(snapshot);
1724
1784
  if (persistedInterrupt == null) {
1725
1785
  return;
1726
1786
  }
1787
+ this.Graph?.restoreRunStepResumeState(
1788
+ getRunStepResumeState(persistedInterrupt.value)
1789
+ );
1727
1790
  const persistedMessages = getPersistedMessages(snapshot);
1728
1791
  if (persistedMessages != null) {
1729
1792
  const resumeMessages = getResumeUpdateMessages(resumeUpdate);
@@ -2303,6 +2366,261 @@ export class Run<_T extends t.BaseGraphState> {
2303
2366
  }
2304
2367
  }
2305
2368
 
2369
+ /**
2370
+ * Generates one replacement title for a user-visible reasoning step. Hosts
2371
+ * own accumulation, scheduling, revision ordering, durable delivery, and
2372
+ * billing; the SDK only performs one bounded, redaction-aware generation.
2373
+ */
2374
+ async generateReasoningLabel({
2375
+ provider,
2376
+ clientOptions,
2377
+ visibleReasoning,
2378
+ reasoningStepId,
2379
+ revision,
2380
+ status = 'streaming',
2381
+ previousLabel,
2382
+ agentId,
2383
+ prompt,
2384
+ charLimit = 6_000,
2385
+ chainOptions,
2386
+ traceSeed,
2387
+ sourceRunId,
2388
+ sourceTraceId,
2389
+ responseId,
2390
+ }: t.RunReasoningLabelOptions): Promise<t.ReasoningLabelResult> {
2391
+ const normalizedStepId = reasoningStepId.trim();
2392
+ const snapshotChars = visibleReasoning.trim().length;
2393
+ if (
2394
+ normalizedStepId === '' ||
2395
+ snapshotChars === 0 ||
2396
+ !Number.isInteger(revision) ||
2397
+ revision < 0
2398
+ ) {
2399
+ return {};
2400
+ }
2401
+
2402
+ const reasoningSeq = ++this.reasoningLabelSeq;
2403
+ const requestedContext =
2404
+ this.Graph == null || agentId == null
2405
+ ? undefined
2406
+ : this.Graph.agentContexts.get(agentId);
2407
+ if (agentId != null && requestedContext == null) {
2408
+ return {};
2409
+ }
2410
+ if (agentId == null && (this.Graph?.agentContexts.size ?? 0) > 1) {
2411
+ return {};
2412
+ }
2413
+ const reasoningContext =
2414
+ this.Graph == null
2415
+ ? undefined
2416
+ : (requestedContext ??
2417
+ this.Graph.agentContexts.get(this.Graph.defaultAgentId));
2418
+ const reasoningChainOptions = {
2419
+ ...(chainOptions ?? {}),
2420
+ } as Partial<RunnableConfig> & {
2421
+ configurable?: Record<string, unknown> & {
2422
+ requestBody?: { parentMessageId?: unknown };
2423
+ };
2424
+ };
2425
+ const reasoningUserId =
2426
+ typeof reasoningChainOptions.configurable?.user_id === 'string'
2427
+ ? reasoningChainOptions.configurable.user_id
2428
+ : undefined;
2429
+ const reasoningSessionId =
2430
+ typeof reasoningChainOptions.configurable?.thread_id === 'string'
2431
+ ? reasoningChainOptions.configurable.thread_id
2432
+ : undefined;
2433
+ const reasoningParentMessageId =
2434
+ reasoningChainOptions.configurable?.requestBody?.parentMessageId;
2435
+ const reasoningAgentId =
2436
+ agentId ??
2437
+ (this.Graph?.agentContexts.size === 1
2438
+ ? this.Graph.defaultAgentId
2439
+ : undefined);
2440
+ const reasoningAgentName =
2441
+ reasoningAgentId == null ? undefined : reasoningContext?.name;
2442
+ const resolvedSourceRunId = sourceRunId ?? this.id;
2443
+ const reasoningResponseId = responseId ?? this.id;
2444
+ const reasoningMetadata: Record<string, unknown> = {
2445
+ sourceRunId: resolvedSourceRunId,
2446
+ ...(sourceTraceId == null ? {} : { sourceTraceId }),
2447
+ responseId: reasoningResponseId,
2448
+ reasoningStepId: normalizedStepId,
2449
+ revision,
2450
+ status,
2451
+ snapshotChars,
2452
+ ...(typeof reasoningParentMessageId === 'string'
2453
+ ? { parentMessageId: reasoningParentMessageId }
2454
+ : {}),
2455
+ ...(reasoningAgentId == null ? {} : { agentId: reasoningAgentId }),
2456
+ ...(reasoningAgentName == null ? {} : { agentName: reasoningAgentName }),
2457
+ };
2458
+ const traceMetadata = {
2459
+ ...createLangfuseTraceMetadata({
2460
+ messageId: `reasoning-label-${reasoningResponseId}`,
2461
+ parentMessageId: reasoningParentMessageId,
2462
+ agentId: reasoningAgentId,
2463
+ agentName: reasoningAgentName,
2464
+ }),
2465
+ sourceRunId: resolvedSourceRunId,
2466
+ ...(sourceTraceId == null ? {} : { sourceTraceId }),
2467
+ responseId: reasoningResponseId,
2468
+ reasoningStepId: normalizedStepId,
2469
+ revision: String(revision),
2470
+ status,
2471
+ snapshotChars: String(snapshotChars),
2472
+ };
2473
+ const reasoningRunName =
2474
+ reasoningChainOptions.runName ?? REASONING_LABEL_TRACE_NAME;
2475
+ const reasoningTags = ['librechat', 'reasoning-label', 'reasoning-step'];
2476
+ const reasoningLangfuseConfig = resolveLangfuseConfig(
2477
+ this.langfuse,
2478
+ reasoningContext?.langfuse
2479
+ );
2480
+ initializeLangfuseTracing(reasoningLangfuseConfig);
2481
+
2482
+ const inheritedTraceSeed = getTraceIdSeed();
2483
+ const reasoningTraceSeed =
2484
+ reasoningLangfuseConfig?.deterministicTraceId === true ||
2485
+ inheritedTraceSeed != null
2486
+ ? (traceSeed ??
2487
+ buildReasoningLabelTraceSeed(
2488
+ resolvedSourceRunId,
2489
+ normalizedStepId,
2490
+ revision
2491
+ ))
2492
+ : undefined;
2493
+ const reasoningScopeRunId = `reasoning-label:${this.id}:${reasoningSeq}:${nanoid()}`;
2494
+ const reasoningRuntimeScope = resolveLangfuseRuntimeScope({
2495
+ runLangfuse: this.langfuse,
2496
+ langfuseOverlay: reasoningContext?.langfuse,
2497
+ traceIdSeed: reasoningTraceSeed,
2498
+ runId: reasoningScopeRunId,
2499
+ });
2500
+ let reasoningLangfuseHandler: CallbackEntry | undefined;
2501
+ if (reasoningSessionId != null) {
2502
+ reasoningLangfuseHandler = createLangfuseHandler({
2503
+ langfuse: reasoningLangfuseConfig,
2504
+ userId: reasoningUserId,
2505
+ sessionId: reasoningSessionId,
2506
+ traceMetadata,
2507
+ tags: reasoningTags,
2508
+ traceIdSeed:
2509
+ reasoningLangfuseConfig?.deterministicTraceId === true
2510
+ ? reasoningTraceSeed
2511
+ : undefined,
2512
+ runId: reasoningScopeRunId,
2513
+ toolOutputTracing: reasoningRuntimeScope.toolOutputTracing,
2514
+ traceName: reasoningRunName,
2515
+ });
2516
+ }
2517
+ if (reasoningLangfuseHandler != null) {
2518
+ reasoningChainOptions.callbacks = appendCallbacks(
2519
+ reasoningChainOptions.callbacks,
2520
+ [reasoningLangfuseHandler]
2521
+ );
2522
+ }
2523
+
2524
+ const redaction = hasToolOutputTracingConfig(
2525
+ this.langfuse,
2526
+ reasoningContext?.langfuse
2527
+ )
2528
+ ? resolveToolOutputTracingConfig(
2529
+ this.langfuse,
2530
+ reasoningContext?.langfuse
2531
+ )
2532
+ : undefined;
2533
+ const userPrompt = buildReasoningLabelPrompt({
2534
+ visibleReasoning,
2535
+ status,
2536
+ charLimit,
2537
+ previousLabel,
2538
+ redaction,
2539
+ });
2540
+ if (userPrompt === '') {
2541
+ await disposeLangfuseHandler(reasoningLangfuseHandler);
2542
+ return {};
2543
+ }
2544
+
2545
+ const model = initializeModel({
2546
+ provider,
2547
+ clientOptions: {
2548
+ ...(clientOptions ?? {}),
2549
+ streaming: false,
2550
+ } as t.ClientOptions,
2551
+ }) as t.ChatModelInstance;
2552
+ const reasoningRunId = `${this.id}-reasoning-${reasoningSeq}`;
2553
+ const invokeConfig = Object.assign({}, reasoningChainOptions, {
2554
+ run_id: reasoningRunId,
2555
+ runId: reasoningRunId,
2556
+ runName: reasoningRunName,
2557
+ tags: [
2558
+ ...new Set([...(reasoningChainOptions.tags ?? []), ...reasoningTags]),
2559
+ ],
2560
+ metadata: {
2561
+ ...(reasoningChainOptions.metadata ?? {}),
2562
+ ...reasoningMetadata,
2563
+ },
2564
+ }) as Partial<RunnableConfig>;
2565
+ const invokeLabel = (
2566
+ runtimeConfig: Partial<RunnableConfig>
2567
+ ): Promise<unknown> =>
2568
+ withLangfuseAttributes(
2569
+ {
2570
+ langfuse: reasoningLangfuseConfig,
2571
+ userId: reasoningUserId,
2572
+ sessionId: reasoningSessionId,
2573
+ traceName: reasoningRunName,
2574
+ traceMetadata,
2575
+ tags: reasoningTags,
2576
+ },
2577
+ () =>
2578
+ model.invoke(
2579
+ [
2580
+ new SystemMessage(prompt ?? REASONING_LABEL_PROMPT),
2581
+ new HumanMessage(userPrompt),
2582
+ ],
2583
+ runtimeConfig
2584
+ )
2585
+ );
2586
+ const extractResult = (response: unknown): t.ReasoningLabelResult => {
2587
+ const result = response as {
2588
+ content?: unknown;
2589
+ usage_metadata?: UsageMetadata;
2590
+ } | null;
2591
+ const content = result?.content;
2592
+ let text = '';
2593
+ if (typeof content === 'string') {
2594
+ text = content;
2595
+ } else if (Array.isArray(content)) {
2596
+ text = content
2597
+ .map((block) =>
2598
+ typeof block === 'string'
2599
+ ? block
2600
+ : ((block as { text?: string }).text ?? '')
2601
+ )
2602
+ .join('');
2603
+ }
2604
+ const label = normalizeReasoningLabel(text);
2605
+ return {
2606
+ ...(label === '' ? {} : { label }),
2607
+ ...(result?.usage_metadata == null
2608
+ ? {}
2609
+ : { usage: result.usage_metadata }),
2610
+ };
2611
+ };
2612
+
2613
+ try {
2614
+ const response = await withLangfuseRuntimeScope(
2615
+ reasoningRuntimeScope,
2616
+ () => invokeLabel(invokeConfig)
2617
+ );
2618
+ return extractResult(response);
2619
+ } finally {
2620
+ await disposeLangfuseHandler(reasoningLangfuseHandler);
2621
+ }
2622
+ }
2623
+
2306
2624
  /**
2307
2625
  * Generates one parent summary for two or more logical activities. The
2308
2626
  * summary model is traced as a dedicated activity-phase chain root in the
@@ -2661,8 +2979,8 @@ function findActivityPhaseTraceInput(
2661
2979
  const message = messages[i];
2662
2980
  if (
2663
2981
  message.getType() !== 'human' ||
2664
- message.additional_kwargs?.role === 'system' ||
2665
- message.additional_kwargs?.isMeta === true
2982
+ message.additional_kwargs.role === 'system' ||
2983
+ message.additional_kwargs.isMeta === true
2666
2984
  ) {
2667
2985
  continue;
2668
2986
  }
package/src/stream.ts CHANGED
@@ -169,7 +169,8 @@ function hasToolOutputReference(value: unknown): boolean {
169
169
 
170
170
  function isEagerExecutionExcludedTool(
171
171
  name: string,
172
- graph: StandardGraph
172
+ graph: StandardGraph,
173
+ agentContext?: AgentContext
173
174
  ): boolean {
174
175
  if (name === '') {
175
176
  return false;
@@ -200,9 +201,21 @@ function isEagerExecutionExcludedTool(
200
201
  // args, ToolNode discards the eager result but the mutation has already
201
202
  // landed in the session workspace, corrupting later runs. Stateless mode
202
203
  // uses a throwaway VM per call, so eager prestart stays safe there.
204
+ if (!CODE_EXECUTION_TOOLS.has(name)) {
205
+ return false;
206
+ }
207
+ if (graph.toolExecution?.sandbox?.statefulSessions === true) {
208
+ return true;
209
+ }
210
+ // A non-default code-session partition is the trusted per-agent signal that
211
+ // this call must remain isolated from the graph-wide stateless session. The
212
+ // actual tool factory may route it to a durable backend, which the stream
213
+ // layer cannot inspect in event-driven mode. Conservatively avoid speculative
214
+ // execution for these calls so discarded eager results cannot mutate that
215
+ // agent's workspace.
203
216
  return (
204
- graph.toolExecution?.sandbox?.statefulSessions === true &&
205
- CODE_EXECUTION_TOOLS.has(name)
217
+ agentContext?.codeSessionKey != null &&
218
+ agentContext.codeSessionKey !== Constants.EXECUTE_CODE
206
219
  );
207
220
  }
208
221
 
@@ -258,7 +271,8 @@ function toCodeEnvFile(file: t.FileRef, execSessionId: string): t.CodeEnvFile {
258
271
 
259
272
  function getCodeSessionContext(
260
273
  graph: StandardGraph,
261
- name: string
274
+ name: string,
275
+ agentContext?: AgentContext
262
276
  ): t.ToolCallRequest['codeSessionContext'] | undefined {
263
277
  if (
264
278
  !CODE_EXECUTION_TOOLS.has(name) &&
@@ -269,9 +283,9 @@ function getCodeSessionContext(
269
283
  return undefined;
270
284
  }
271
285
 
272
- const codeSession = graph.sessions.get(Constants.EXECUTE_CODE) as
273
- | t.CodeSessionContext
274
- | undefined;
286
+ const codeSession = graph.sessions.get(
287
+ agentContext?.codeSessionKey ?? Constants.EXECUTE_CODE
288
+ ) as t.CodeSessionContext | undefined;
275
289
  if (codeSession?.session_id == null || codeSession.session_id === '') {
276
290
  return undefined;
277
291
  }
@@ -716,7 +730,8 @@ function createEagerToolExecutionPlan(args: {
716
730
  // tool from `hasDirectToolCallInBatch`. Excluded calls fall through to normal
717
731
  // ToolNode execution; siblings may still eager-execute.
718
732
  const candidateToolCalls = unstartedToolCalls.filter(
719
- (toolCall) => !isEagerExecutionExcludedTool(toolCall.name, graph)
733
+ (toolCall) =>
734
+ !isEagerExecutionExcludedTool(toolCall.name, graph, agentContext)
720
735
  );
721
736
  if (candidateToolCalls.length === 0) {
722
737
  return [];
@@ -748,7 +763,11 @@ function createEagerToolExecutionPlan(args: {
748
763
  name: toolCall.name,
749
764
  args: toolCall.args,
750
765
  stepId: graph.toolCallStepIds.get(toolCall.id!) ?? '',
751
- codeSessionContext: getCodeSessionContext(graph, toolCall.name),
766
+ codeSessionContext: getCodeSessionContext(
767
+ graph,
768
+ toolCall.name,
769
+ agentContext
770
+ ),
752
771
  })),
753
772
  usageCount: graph.getEagerEventToolUsageCount(agentContext?.agentId),
754
773
  });
@@ -8,12 +8,15 @@ import {
8
8
  appendFailedExecutionFileReminder,
9
9
  appendTmpScratchReminder,
10
10
  appendCodeSessionFileSummary,
11
+ addCodeApiExecutionProfileHeader,
11
12
  emptyOutputMessage,
12
13
  buildCodeApiHttpErrorMessage,
13
14
  CodeApiRequestError,
15
+ buildCodeApiEndpoint,
14
16
  getCodeBaseURL,
15
17
  normalizeCodeApiRequestError,
16
18
  resolveCodeApiAuthHeaders,
19
+ selectRuntimeSessionHint,
17
20
  } from './CodeExecutor';
18
21
  import { resolveFetchProxyAgent } from '@/utils/proxy';
19
22
  import { INTENT_PROPERTY } from '@/tools/intentArg';
@@ -21,9 +24,6 @@ import { Constants } from '@/common';
21
24
 
22
25
  config();
23
26
 
24
- const baseEndpoint = getCodeBaseURL();
25
- const EXEC_ENDPOINT = `${baseEndpoint}/exec`;
26
-
27
27
  export const BashExecutionToolSchema = {
28
28
  type: 'object',
29
29
  properties: {
@@ -177,15 +177,24 @@ export const BashExecutionToolDefinition = {
177
177
  function createBashExecutionTool(
178
178
  params: t.BashExecutionToolParams | null = {}
179
179
  ): DynamicStructuredTool {
180
+ const execEndpoint = buildCodeApiEndpoint(
181
+ params?.baseUrl ?? getCodeBaseURL(),
182
+ 'exec'
183
+ );
184
+
180
185
  return tool(
181
186
  async (rawInput, config) => {
182
- /* `statefulSessions` is prompt-only keep it out of the wire body. */
187
+ /* `statefulSessions` drives the prompt and gates runtime affinity hints;
188
+ * keep the flag itself out of the wire body. */
183
189
  const {
184
190
  authHeaders,
185
- statefulSessions: _statefulSessions,
191
+ baseUrl: _baseUrl,
192
+ executionProfile,
193
+ runtimeSessionHint,
194
+ statefulSessions,
186
195
  ...executionParams
187
196
  } = params ?? {};
188
- void _statefulSessions;
197
+ void _baseUrl;
189
198
  /* Drop any model-supplied `runtime_session_hint` from the raw args: the
190
199
  * hint must only come from ToolNode's injected `_runtime_session_hint`
191
200
  * (below), never from the tool call itself. */
@@ -217,11 +226,17 @@ function createBashExecutionTool(
217
226
  ...executionParams,
218
227
  };
219
228
 
229
+ const effectiveRuntimeSessionHint = selectRuntimeSessionHint(
230
+ runtimeSessionHint,
231
+ _runtime_session_hint
232
+ );
220
233
  if (
221
- typeof _runtime_session_hint === 'string' &&
222
- _runtime_session_hint !== ''
234
+ statefulSessions === true &&
235
+ executionProfile !== 'default' &&
236
+ typeof effectiveRuntimeSessionHint === 'string' &&
237
+ effectiveRuntimeSessionHint !== ''
223
238
  ) {
224
- postData.runtime_session_hint = _runtime_session_hint;
239
+ postData.runtime_session_hint = effectiveRuntimeSessionHint;
225
240
  }
226
241
 
227
242
  /* See `CodeExecutor.ts` for the rationale — `/files/<session_id>`
@@ -248,19 +263,22 @@ function createBashExecutionTool(
248
263
  headers: {
249
264
  'Content-Type': 'application/json',
250
265
  'User-Agent': 'LibreChat/1.0',
251
- ...resolvedAuthHeaders,
266
+ ...addCodeApiExecutionProfileHeader(
267
+ resolvedAuthHeaders,
268
+ executionProfile
269
+ ),
252
270
  },
253
271
  body: JSON.stringify(postData),
254
272
  };
255
273
 
256
- const proxyAgent = resolveFetchProxyAgent(EXEC_ENDPOINT);
274
+ const proxyAgent = resolveFetchProxyAgent(execEndpoint);
257
275
  if (proxyAgent) {
258
276
  fetchOptions.agent = proxyAgent;
259
277
  }
260
- const response = await fetch(EXEC_ENDPOINT, fetchOptions);
278
+ const response = await fetch(execEndpoint, fetchOptions);
261
279
  if (!response.ok) {
262
280
  throw new CodeApiRequestError(
263
- await buildCodeApiHttpErrorMessage('POST', EXEC_ENDPOINT, response)
281
+ await buildCodeApiHttpErrorMessage('POST', execEndpoint, response)
264
282
  );
265
283
  }
266
284