@librechat/agents 3.5.0 → 3.6.0

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 (188) 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/run.cjs +29 -11
  9. package/dist/cjs/run.cjs.map +1 -1
  10. package/dist/cjs/stream.cjs +8 -6
  11. package/dist/cjs/stream.cjs.map +1 -1
  12. package/dist/cjs/tools/BashExecutor.cjs +8 -7
  13. package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
  14. package/dist/cjs/tools/BashProgrammaticToolCalling.cjs +5 -4
  15. package/dist/cjs/tools/BashProgrammaticToolCalling.cjs.map +1 -1
  16. package/dist/cjs/tools/CodeExecutor.cjs +26 -7
  17. package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
  18. package/dist/cjs/tools/ProgrammaticToolCalling.cjs +7 -6
  19. package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
  20. package/dist/cjs/tools/ToolNode.cjs +33 -10
  21. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  22. package/dist/cjs/tools/ToolSearch.cjs +1 -1
  23. package/dist/cjs/tools/ToolSearch.cjs.map +1 -1
  24. package/dist/cjs/tools/runStepResume.cjs +51 -0
  25. package/dist/cjs/tools/runStepResume.cjs.map +1 -0
  26. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +15 -43
  27. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
  28. package/dist/cjs/tools/subagent/SubagentReplay.cjs +6 -1
  29. package/dist/cjs/tools/subagent/SubagentReplay.cjs.map +1 -1
  30. package/dist/cjs/utils/toolSessions.cjs +63 -0
  31. package/dist/cjs/utils/toolSessions.cjs.map +1 -0
  32. package/dist/esm/agents/AgentContext.mjs +5 -2
  33. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  34. package/dist/esm/graphs/Graph.mjs +148 -22
  35. package/dist/esm/graphs/Graph.mjs.map +1 -1
  36. package/dist/esm/graphs/MultiAgentGraph.mjs +2 -1
  37. package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
  38. package/dist/esm/main.mjs +2 -2
  39. package/dist/esm/run.mjs +29 -11
  40. package/dist/esm/run.mjs.map +1 -1
  41. package/dist/esm/stream.mjs +8 -6
  42. package/dist/esm/stream.mjs.map +1 -1
  43. package/dist/esm/tools/BashExecutor.mjs +9 -8
  44. package/dist/esm/tools/BashExecutor.mjs.map +1 -1
  45. package/dist/esm/tools/BashProgrammaticToolCalling.mjs +6 -5
  46. package/dist/esm/tools/BashProgrammaticToolCalling.mjs.map +1 -1
  47. package/dist/esm/tools/CodeExecutor.mjs +23 -8
  48. package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
  49. package/dist/esm/tools/ProgrammaticToolCalling.mjs +8 -7
  50. package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
  51. package/dist/esm/tools/ToolNode.mjs +33 -10
  52. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  53. package/dist/esm/tools/ToolSearch.mjs +2 -2
  54. package/dist/esm/tools/ToolSearch.mjs.map +1 -1
  55. package/dist/esm/tools/runStepResume.mjs +48 -0
  56. package/dist/esm/tools/runStepResume.mjs.map +1 -0
  57. package/dist/esm/tools/subagent/SubagentExecutor.mjs +15 -43
  58. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
  59. package/dist/esm/tools/subagent/SubagentReplay.mjs +6 -1
  60. package/dist/esm/tools/subagent/SubagentReplay.mjs.map +1 -1
  61. package/dist/esm/utils/toolSessions.mjs +62 -0
  62. package/dist/esm/utils/toolSessions.mjs.map +1 -0
  63. package/dist/types/agents/AgentContext.d.ts +7 -4
  64. package/dist/types/agents/projection.d.ts +1 -1
  65. package/dist/types/events.d.ts +2 -2
  66. package/dist/types/graphs/Graph.d.ts +22 -14
  67. package/dist/types/graphs/MultiAgentGraph.d.ts +2 -2
  68. package/dist/types/graphs/createGraph.d.ts +4 -4
  69. package/dist/types/graphs/graphFactory.d.ts +1 -1
  70. package/dist/types/hitl/askUserQuestion.d.ts +1 -1
  71. package/dist/types/hitl/askUserQuestions.d.ts +1 -1
  72. package/dist/types/hitl/askUserQuestionsInterrupt.d.ts +1 -1
  73. package/dist/types/hooks/types.d.ts +1 -1
  74. package/dist/types/instrumentation.d.ts +1 -1
  75. package/dist/types/langfuse.d.ts +3 -3
  76. package/dist/types/langfuseConfig.d.ts +2 -2
  77. package/dist/types/langfuseRuntimeContext.d.ts +1 -1
  78. package/dist/types/langfuseRuntimeScope.d.ts +2 -2
  79. package/dist/types/langfuseSpanRegistry.d.ts +1 -1
  80. package/dist/types/langfuseToolOutputTracing.d.ts +3 -3
  81. package/dist/types/llm/anthropic/index.d.ts +1 -1
  82. package/dist/types/llm/bedrock/index.d.ts +1 -1
  83. package/dist/types/llm/bedrock/toolCache.d.ts +2 -2
  84. package/dist/types/llm/contextOverflowRecovery.d.ts +2 -2
  85. package/dist/types/llm/google/index.d.ts +1 -1
  86. package/dist/types/llm/init.d.ts +2 -2
  87. package/dist/types/llm/invoke.d.ts +6 -6
  88. package/dist/types/llm/mistral/index.d.ts +1 -1
  89. package/dist/types/llm/openai/index.d.ts +2 -2
  90. package/dist/types/llm/openrouter/index.d.ts +2 -2
  91. package/dist/types/llm/openrouter/toolCache.d.ts +2 -2
  92. package/dist/types/llm/providers.d.ts +2 -2
  93. package/dist/types/llm/request.d.ts +2 -2
  94. package/dist/types/llm/stream/chunkAdapters.d.ts +1 -1
  95. package/dist/types/llm/streamLimits.d.ts +1 -1
  96. package/dist/types/llm/truncation.d.ts +1 -1
  97. package/dist/types/llm/vertexai/index.d.ts +1 -1
  98. package/dist/types/messages/alternation.d.ts +1 -1
  99. package/dist/types/messages/anthropicToolCache.d.ts +2 -2
  100. package/dist/types/messages/assistantPhase.d.ts +3 -3
  101. package/dist/types/messages/budget.d.ts +1 -1
  102. package/dist/types/messages/cache.d.ts +1 -1
  103. package/dist/types/messages/contextPruning.d.ts +2 -2
  104. package/dist/types/messages/core.d.ts +2 -2
  105. package/dist/types/messages/format.d.ts +2 -2
  106. package/dist/types/messages/ids.d.ts +2 -2
  107. package/dist/types/messages/injected.d.ts +1 -1
  108. package/dist/types/messages/langchain.d.ts +1 -1
  109. package/dist/types/messages/prune.d.ts +3 -3
  110. package/dist/types/openai/index.d.ts +1 -1
  111. package/dist/types/prompts/activityLabel.d.ts +2 -2
  112. package/dist/types/responses/index.d.ts +1 -1
  113. package/dist/types/run.d.ts +3 -3
  114. package/dist/types/session/handlers.d.ts +1 -1
  115. package/dist/types/session/types.d.ts +1 -1
  116. package/dist/types/stream.d.ts +4 -4
  117. package/dist/types/summarization/index.d.ts +1 -1
  118. package/dist/types/summarization/node.d.ts +6 -6
  119. package/dist/types/tools/BashExecutor.d.ts +2 -2
  120. package/dist/types/tools/BashProgrammaticToolCalling.d.ts +2 -2
  121. package/dist/types/tools/CodeExecutor.d.ts +7 -3
  122. package/dist/types/tools/CodeSessionFileSummary.d.ts +1 -1
  123. package/dist/types/tools/ProgrammaticToolCalling.d.ts +3 -3
  124. package/dist/types/tools/ReadFile.d.ts +1 -1
  125. package/dist/types/tools/SkillTool.d.ts +1 -1
  126. package/dist/types/tools/SubagentTool.d.ts +3 -3
  127. package/dist/types/tools/ToolNode.d.ts +8 -6
  128. package/dist/types/tools/ToolSearch.d.ts +2 -2
  129. package/dist/types/tools/cloudflare/CloudflareBridgeRuntime.d.ts +1 -1
  130. package/dist/types/tools/cloudflare/CloudflareProgrammaticToolCalling.d.ts +1 -1
  131. package/dist/types/tools/cloudflare/CloudflareSandboxExecutionEngine.d.ts +2 -2
  132. package/dist/types/tools/cloudflare/CloudflareSandboxTools.d.ts +1 -1
  133. package/dist/types/tools/eagerEventExecution.d.ts +1 -1
  134. package/dist/types/tools/handlers.d.ts +3 -3
  135. package/dist/types/tools/intentArg.d.ts +1 -1
  136. package/dist/types/tools/local/CompileCheckTool.d.ts +2 -2
  137. package/dist/types/tools/local/FileCheckpointer.d.ts +1 -1
  138. package/dist/types/tools/local/LocalCodingTools.d.ts +2 -2
  139. package/dist/types/tools/local/LocalExecutionEngine.d.ts +1 -1
  140. package/dist/types/tools/local/LocalExecutionTools.d.ts +1 -1
  141. package/dist/types/tools/local/LocalProgrammaticToolCalling.d.ts +1 -1
  142. package/dist/types/tools/local/bashAst.d.ts +1 -1
  143. package/dist/types/tools/local/resolveLocalExecutionTools.d.ts +1 -1
  144. package/dist/types/tools/local/syntaxCheck.d.ts +1 -1
  145. package/dist/types/tools/ptcTimeout.d.ts +1 -1
  146. package/dist/types/tools/runStepResume.d.ts +5 -0
  147. package/dist/types/tools/schema.d.ts +1 -1
  148. package/dist/types/tools/search/anthropic.d.ts +1 -1
  149. package/dist/types/tools/skillCatalog.d.ts +1 -1
  150. package/dist/types/tools/subagent/SubagentExecutor.d.ts +30 -5
  151. package/dist/types/tools/subagent/SubagentReplay.d.ts +4 -3
  152. package/dist/types/tools/subagent/childGraphConfig.d.ts +2 -2
  153. package/dist/types/types/activityLabel.d.ts +3 -3
  154. package/dist/types/types/graph.d.ts +16 -7
  155. package/dist/types/types/llm.d.ts +9 -9
  156. package/dist/types/types/run.d.ts +7 -7
  157. package/dist/types/types/stream.d.ts +23 -5
  158. package/dist/types/types/summarize.d.ts +2 -2
  159. package/dist/types/types/tools.d.ts +41 -21
  160. package/dist/types/utils/errors.d.ts +1 -1
  161. package/dist/types/utils/events.d.ts +1 -1
  162. package/dist/types/utils/handlers.d.ts +3 -3
  163. package/dist/types/utils/llm.d.ts +1 -1
  164. package/dist/types/utils/llmConfig.d.ts +1 -1
  165. package/dist/types/utils/title.d.ts +1 -1
  166. package/dist/types/utils/toolSessions.d.ts +18 -0
  167. package/package.json +28 -32
  168. package/src/agents/AgentContext.ts +7 -0
  169. package/src/graphs/Graph.ts +192 -13
  170. package/src/graphs/MultiAgentGraph.ts +1 -0
  171. package/src/run.ts +62 -11
  172. package/src/stream.ts +28 -9
  173. package/src/tools/BashExecutor.ts +31 -13
  174. package/src/tools/BashProgrammaticToolCalling.ts +19 -9
  175. package/src/tools/CodeExecutor.ts +57 -16
  176. package/src/tools/ProgrammaticToolCalling.ts +26 -12
  177. package/src/tools/ToolNode.ts +58 -8
  178. package/src/tools/ToolSearch.ts +2 -2
  179. package/src/tools/runStepResume.ts +121 -0
  180. package/src/tools/subagent/SubagentExecutor.ts +67 -65
  181. package/src/tools/subagent/SubagentReplay.ts +22 -1
  182. package/src/types/graph.ts +10 -0
  183. package/src/types/stream.ts +17 -0
  184. package/src/types/tools.ts +50 -21
  185. package/src/utils/toolSessions.ts +113 -0
  186. package/dist/types/agents/__tests__/promptCacheLiveHelpers.d.ts +0 -52
  187. package/dist/types/test/mockTools.d.ts +0 -28
  188. package/dist/types/utils/__tests__/fixtures/contextOverflowSignatures.d.ts +0 -40
package/src/run.ts CHANGED
@@ -29,6 +29,10 @@ import {
29
29
  SUBAGENT_RESUME_ATTEMPT_CONFIG_KEY,
30
30
  SUBAGENT_RESUME_MANIFEST_CONFIG_KEY,
31
31
  } from '@/tools/subagent/SubagentReplay';
32
+ import {
33
+ getRunStepResumeState,
34
+ stripRunStepResumeState,
35
+ } from '@/tools/runStepResume';
32
36
  import {
33
37
  ACTIVITY_PHASE_LABEL_PROMPT,
34
38
  ACTIVITY_LABEL_PROMPT,
@@ -72,6 +76,7 @@ import {
72
76
  import { applyGraphRuntimeConfig } from '@/graphs/applyGraphRuntimeConfig';
73
77
  import { createTokenCounter, encodingForModel } from '@/utils/tokens';
74
78
  import { initializeLangfuseTracing } from './instrumentation';
79
+ import { seedRunInitialSessions } from '@/utils/toolSessions';
75
80
  import { getTraceIdSeed } from '@/langfuseRuntimeContext';
76
81
  import { createGraph } from '@/graphs/createGraph';
77
82
  import { resolveMaxSeals } from '@/llm/preempt';
@@ -173,7 +178,9 @@ function isLangGraphResumeMapForInterrupt(
173
178
  }
174
179
 
175
180
  function getInterruptHookSessionId(payload: unknown): string | undefined {
176
- const publicPayload = stripSubagentResumeManifest(payload);
181
+ const publicPayload = stripSubagentResumeManifest(
182
+ stripRunStepResumeState(payload)
183
+ );
177
184
  if (
178
185
  publicPayload == null ||
179
186
  typeof publicPayload !== 'object' ||
@@ -193,11 +200,15 @@ type InterruptStateSnapshot = {
193
200
  values?: { messages?: BaseMessage[] };
194
201
  tasks?: Array<{
195
202
  interrupts?: Array<{ id?: string; value?: unknown }>;
203
+ state?: RunnableConfig | InterruptStateSnapshot;
196
204
  }>;
197
205
  };
198
206
 
199
207
  type WorkflowWithStateHistory = {
200
- getState?(config: RunnableConfig): Promise<InterruptStateSnapshot>;
208
+ getState?(
209
+ config: RunnableConfig,
210
+ options?: { subgraphs?: boolean }
211
+ ): Promise<InterruptStateSnapshot>;
201
212
  getStateHistory?(
202
213
  config: RunnableConfig
203
214
  ): AsyncIterableIterator<InterruptStateSnapshot>;
@@ -215,6 +226,17 @@ function getFirstPersistedInterrupt(
215
226
  return { id: pendingInterrupt.id, value: pendingInterrupt.value };
216
227
  }
217
228
  }
229
+ const nestedState = task.state;
230
+ if (
231
+ nestedState != null &&
232
+ 'tasks' in nestedState &&
233
+ Array.isArray(nestedState.tasks)
234
+ ) {
235
+ const nestedInterrupt = getFirstPersistedInterrupt(nestedState);
236
+ if (nestedInterrupt != null) {
237
+ return nestedInterrupt;
238
+ }
239
+ }
218
240
  }
219
241
  return undefined;
220
242
  }
@@ -351,9 +373,25 @@ export class Run<_T extends t.BaseGraphState> {
351
373
  }
352
374
 
353
375
  if (config.initialSessions && this.Graph) {
354
- for (const [key, value] of config.initialSessions) {
355
- this.Graph.sessions.set(key, value);
356
- }
376
+ const configuredAgents =
377
+ 'agents' in config.graphConfig &&
378
+ Array.isArray(config.graphConfig.agents)
379
+ ? config.graphConfig.agents
380
+ : undefined;
381
+ const agents: Array<Pick<t.AgentInputs, 'codeSessionKey'>> =
382
+ configuredAgents ?? [
383
+ {
384
+ codeSessionKey:
385
+ 'codeSessionKey' in config.graphConfig
386
+ ? config.graphConfig.codeSessionKey
387
+ : undefined,
388
+ },
389
+ ];
390
+ seedRunInitialSessions({
391
+ sessions: this.Graph.sessions,
392
+ initialSessions: config.initialSessions,
393
+ agents,
394
+ });
357
395
  }
358
396
 
359
397
  this.returnContent = config.returnContent ?? false;
@@ -949,6 +987,12 @@ export class Run<_T extends t.BaseGraphState> {
949
987
  delete config.configurable?.[SUBAGENT_RESUME_ATTEMPT_CONFIG_KEY];
950
988
  delete config.configurable?.[SUBAGENT_RESUME_MANIFEST_CONFIG_KEY];
951
989
  }
990
+ if (isResume) {
991
+ await this.restoreInterruptFromCheckpoint(
992
+ config,
993
+ (inputs as Command).update
994
+ );
995
+ }
952
996
 
953
997
  /**
954
998
  * Cancellation can arrive either at graph construction or per-call through
@@ -1503,7 +1547,9 @@ export class Run<_T extends t.BaseGraphState> {
1503
1547
  }
1504
1548
  return {
1505
1549
  ...this._interrupt,
1506
- payload: stripSubagentResumeManifest(this._interrupt.payload),
1550
+ payload: stripSubagentResumeManifest(
1551
+ stripRunStepResumeState(this._interrupt.payload)
1552
+ ),
1507
1553
  } as t.RunInterruptResult<TPayload>;
1508
1554
  }
1509
1555
 
@@ -1622,7 +1668,7 @@ export class Run<_T extends t.BaseGraphState> {
1622
1668
  await this.restoreInterruptFromCheckpoint(callerConfig, resumeUpdate);
1623
1669
  const interrupt = this._interrupt;
1624
1670
  const resumeManifest = requireValidSubagentResumeManifest(
1625
- interrupt?.payload
1671
+ stripRunStepResumeState(interrupt?.payload)
1626
1672
  );
1627
1673
  const resumeConfigurable = { ...callerConfig.configurable };
1628
1674
  delete resumeConfigurable[SUBAGENT_RESUME_ATTEMPT_CONFIG_KEY];
@@ -1709,7 +1755,7 @@ export class Run<_T extends t.BaseGraphState> {
1709
1755
  callerConfig: t.RunStreamConfig,
1710
1756
  resumeUpdate?: ResumeCommandUpdate
1711
1757
  ): Promise<void> {
1712
- if (this._interrupt != null || this.humanInTheLoop?.enabled !== true) {
1758
+ if (this._interrupt != null || !this.hasCheckpointer) {
1713
1759
  return;
1714
1760
  }
1715
1761
  const workflow = this.graphRunnable as
@@ -1719,11 +1765,16 @@ export class Run<_T extends t.BaseGraphState> {
1719
1765
  return;
1720
1766
  }
1721
1767
 
1722
- const snapshot = await workflow.getState(callerConfig as RunnableConfig);
1768
+ const snapshot = await workflow.getState(callerConfig as RunnableConfig, {
1769
+ subgraphs: true,
1770
+ });
1723
1771
  const persistedInterrupt = getFirstPersistedInterrupt(snapshot);
1724
1772
  if (persistedInterrupt == null) {
1725
1773
  return;
1726
1774
  }
1775
+ this.Graph?.restoreRunStepResumeState(
1776
+ getRunStepResumeState(persistedInterrupt.value)
1777
+ );
1727
1778
  const persistedMessages = getPersistedMessages(snapshot);
1728
1779
  if (persistedMessages != null) {
1729
1780
  const resumeMessages = getResumeUpdateMessages(resumeUpdate);
@@ -2661,8 +2712,8 @@ function findActivityPhaseTraceInput(
2661
2712
  const message = messages[i];
2662
2713
  if (
2663
2714
  message.getType() !== 'human' ||
2664
- message.additional_kwargs?.role === 'system' ||
2665
- message.additional_kwargs?.isMeta === true
2715
+ message.additional_kwargs.role === 'system' ||
2716
+ message.additional_kwargs.isMeta === true
2666
2717
  ) {
2667
2718
  continue;
2668
2719
  }
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
 
@@ -8,8 +8,10 @@ import {
8
8
  CODE_ARTIFACT_PATH_GUIDANCE,
9
9
  appendFailedExecutionFileReminder,
10
10
  buildCodeApiExecutionErrorMessage,
11
+ buildCodeApiEndpoint,
11
12
  CodeApiRequestError,
12
13
  getCodeBaseURL,
14
+ selectRuntimeSessionHint,
13
15
  } from './CodeExecutor';
14
16
  import {
15
17
  clampCodeApiRunTimeoutMs,
@@ -296,7 +298,7 @@ export function createBashProgrammaticToolCallingTool(
296
298
  const maxRunTimeoutMs = resolveCodeApiRunTimeoutMs(initParams.runTimeoutMs);
297
299
  const proxy = initParams.proxy ?? process.env.PROXY;
298
300
  const debug = initParams.debug ?? process.env.BASH_PTC_DEBUG === 'true';
299
- const EXEC_ENDPOINT = `${baseUrl}/exec/programmatic`;
301
+ const EXEC_ENDPOINT = buildCodeApiEndpoint(baseUrl, 'exec/programmatic');
300
302
 
301
303
  return tool(
302
304
  async (rawParams, config) => {
@@ -362,13 +364,19 @@ export function createBashProgrammaticToolCallingTool(
362
364
  );
363
365
  }
364
366
 
365
- /* Stateful sessions: hint on the INITIAL request only (continuations
366
- * bind via continuation_token). Wire-only in v1 BashPTC keeps its
367
- * stateless prompt. */
367
+ /* The hint rides the INITIAL request only; continuation_token binds
368
+ * later round-trips. Prefer trusted per-agent factory context over
369
+ * legacy ToolNode injection. Explicit default profiles always drop it.
370
+ * BashPTC keeps its stateless runtime prompt in v1. */
371
+ const selectedRuntimeSessionHint = selectRuntimeSessionHint(
372
+ initParams.runtimeSessionHint,
373
+ _runtime_session_hint
374
+ );
368
375
  const runtimeSessionHint =
369
- typeof _runtime_session_hint === 'string' &&
370
- _runtime_session_hint !== ''
371
- ? _runtime_session_hint
376
+ initParams.executionProfile !== 'default' &&
377
+ typeof selectedRuntimeSessionHint === 'string' &&
378
+ selectedRuntimeSessionHint !== ''
379
+ ? selectedRuntimeSessionHint
372
380
  : undefined;
373
381
 
374
382
  let response = await makeRequest(
@@ -385,7 +393,8 @@ export function createBashProgrammaticToolCallingTool(
385
393
  : {}),
386
394
  },
387
395
  proxy,
388
- initParams.authHeaders
396
+ initParams.authHeaders,
397
+ initParams.executionProfile
389
398
  );
390
399
 
391
400
  // ====================================================================
@@ -425,7 +434,8 @@ export function createBashProgrammaticToolCallingTool(
425
434
  tool_results: toolResults,
426
435
  },
427
436
  proxy,
428
- initParams.authHeaders
437
+ initParams.authHeaders,
438
+ initParams.executionProfile
429
439
  );
430
440
  }
431
441
 
@@ -19,6 +19,17 @@ export const getCodeBaseURL = (): string =>
19
19
  getEnvironmentVariable(EnvVar.CODE_BASEURL) ??
20
20
  Constants.OFFICIAL_CODE_BASEURL;
21
21
 
22
+ export function buildCodeApiEndpoint(baseUrl: string, route: string): string {
23
+ return `${baseUrl.replace(/\/+$/, '')}/${route.replace(/^\/+/, '')}`;
24
+ }
25
+
26
+ export function selectRuntimeSessionHint(
27
+ factoryHint?: string,
28
+ injectedHint?: string
29
+ ): string | undefined {
30
+ return factoryHint != null && factoryHint !== '' ? factoryHint : injectedHint;
31
+ }
32
+
22
33
  export const emptyOutputMessage =
23
34
  'stdout: Empty. Ensure you\'re writing output explicitly.\n';
24
35
 
@@ -108,8 +119,8 @@ export const CodeExecutionToolSchema = {
108
119
  required: ['lang', 'code'],
109
120
  } as const;
110
121
 
111
- const baseEndpoint = getCodeBaseURL();
112
- const EXEC_ENDPOINT = `${baseEndpoint}/exec`;
122
+ export const CODE_API_EXPECTED_PROFILE_HEADER =
123
+ 'X-CodeAPI-Expected-Profile';
113
124
 
114
125
  type SupportedLanguage = (typeof SUPPORTED_LANGUAGES)[number];
115
126
 
@@ -226,6 +237,19 @@ export async function resolveCodeApiAuthHeaders(
226
237
  return authHeaders;
227
238
  }
228
239
 
240
+ export function addCodeApiExecutionProfileHeader(
241
+ headers: t.CodeApiAuthHeaderMap,
242
+ executionProfile?: t.CodeApiExecutionProfile
243
+ ): t.CodeApiAuthHeaderMap {
244
+ if (executionProfile == null) {
245
+ return headers;
246
+ }
247
+ return {
248
+ ...headers,
249
+ [CODE_API_EXPECTED_PROFILE_HEADER]: executionProfile,
250
+ };
251
+ }
252
+
229
253
  export async function buildCodeApiHttpErrorMessage(
230
254
  _method: string,
231
255
  _endpoint: string,
@@ -333,16 +357,24 @@ export const CodeExecutionToolDefinition = {
333
357
  function createCodeExecutionTool(
334
358
  params: t.CodeExecutionToolParams | null = {}
335
359
  ): DynamicStructuredTool {
360
+ const execEndpoint = buildCodeApiEndpoint(
361
+ params?.baseUrl ?? getCodeBaseURL(),
362
+ 'exec'
363
+ );
364
+
336
365
  return tool(
337
366
  async (rawInput, config) => {
338
- /* `statefulSessions` is a prompt-only flag (drives the description);
339
- * keep it out of the wire body. */
367
+ /* `statefulSessions` drives the description and gates trusted runtime
368
+ * affinity hints. Keep the flag itself out of the wire body. */
340
369
  const {
341
370
  authHeaders,
342
- statefulSessions: _statefulSessions,
371
+ baseUrl: _baseUrl,
372
+ executionProfile,
373
+ runtimeSessionHint,
374
+ statefulSessions,
343
375
  ...executionParams
344
376
  } = params ?? {};
345
- void _statefulSessions;
377
+ void _baseUrl;
346
378
  /* Drop any model-supplied `runtime_session_hint` from the raw args: the
347
379
  * hint is host-controlled and must only ever come from ToolNode's
348
380
  * injected `_runtime_session_hint` (below). Spreading `...rest` into
@@ -383,14 +415,20 @@ function createCodeExecutionTool(
383
415
  ...executionParams,
384
416
  };
385
417
 
386
- /* Stateful sessions: forward the hint so the Code API can route this
387
- * execution to a warm per-session runtime. Additive — stateless
388
- * servers ignore the unknown field. */
418
+ /* Stateful sessions: prefer the per-agent factory hint, falling back to
419
+ * legacy ToolNode injection. Explicit default-profile tools always drop
420
+ * the hint so a mixed graph cannot promote them to the stateful path. */
421
+ const effectiveRuntimeSessionHint = selectRuntimeSessionHint(
422
+ runtimeSessionHint,
423
+ _runtime_session_hint
424
+ );
389
425
  if (
390
- typeof _runtime_session_hint === 'string' &&
391
- _runtime_session_hint !== ''
426
+ statefulSessions === true &&
427
+ executionProfile !== 'default' &&
428
+ typeof effectiveRuntimeSessionHint === 'string' &&
429
+ effectiveRuntimeSessionHint !== ''
392
430
  ) {
393
- postData.runtime_session_hint = _runtime_session_hint;
431
+ postData.runtime_session_hint = effectiveRuntimeSessionHint;
394
432
  }
395
433
 
396
434
  /* File injection: `_injected_files` from ToolNode (set when host
@@ -421,19 +459,22 @@ function createCodeExecutionTool(
421
459
  headers: {
422
460
  'Content-Type': 'application/json',
423
461
  'User-Agent': 'LibreChat/1.0',
424
- ...resolvedAuthHeaders,
462
+ ...addCodeApiExecutionProfileHeader(
463
+ resolvedAuthHeaders,
464
+ executionProfile
465
+ ),
425
466
  },
426
467
  body: JSON.stringify(postData),
427
468
  };
428
469
 
429
- const proxyAgent = resolveFetchProxyAgent(EXEC_ENDPOINT);
470
+ const proxyAgent = resolveFetchProxyAgent(execEndpoint);
430
471
  if (proxyAgent) {
431
472
  fetchOptions.agent = proxyAgent;
432
473
  }
433
- const response = await fetch(EXEC_ENDPOINT, fetchOptions);
474
+ const response = await fetch(execEndpoint, fetchOptions);
434
475
  if (!response.ok) {
435
476
  throw new CodeApiRequestError(
436
- await buildCodeApiHttpErrorMessage('POST', EXEC_ENDPOINT, response)
477
+ await buildCodeApiHttpErrorMessage('POST', execEndpoint, response)
437
478
  );
438
479
  }
439
480
 
@@ -7,16 +7,19 @@ import type { ProgrammaticToolCallingJsonSchema } from './ptcTimeout';
7
7
  import type * as t from '@/types';
8
8
  import {
9
9
  CODE_ARTIFACT_PATH_GUIDANCE,
10
+ addCodeApiExecutionProfileHeader,
10
11
  appendCodeSessionFileSummary,
11
12
  appendFailedExecutionFileReminder,
12
13
  buildCodeApiExecutionErrorMessage,
13
14
  buildCodeApiHttpErrorMessage,
14
15
  CodeApiRequestError,
16
+ buildCodeApiEndpoint,
15
17
  emptyOutputMessage,
16
18
  getCodeBaseURL,
17
19
  appendTmpScratchReminder,
18
20
  normalizeCodeApiRequestError,
19
21
  resolveCodeApiAuthHeaders,
22
+ selectRuntimeSessionHint,
20
23
  } from './CodeExecutor';
21
24
  import {
22
25
  clampCodeApiRunTimeoutMs,
@@ -478,7 +481,8 @@ export async function makeRequest(
478
481
  endpoint: string,
479
482
  body: Record<string, unknown>,
480
483
  proxy?: string,
481
- authHeaders?: t.CodeApiAuthHeaders
484
+ authHeaders?: t.CodeApiAuthHeaders,
485
+ executionProfile?: t.CodeApiExecutionProfile
482
486
  ): Promise<t.ProgrammaticExecutionResponse> {
483
487
  try {
484
488
  const resolvedAuthHeaders = await resolveCodeApiAuthHeaders(authHeaders);
@@ -487,7 +491,10 @@ export async function makeRequest(
487
491
  headers: {
488
492
  'Content-Type': 'application/json',
489
493
  'User-Agent': 'LibreChat/1.0',
490
- ...resolvedAuthHeaders,
494
+ ...addCodeApiExecutionProfileHeader(
495
+ resolvedAuthHeaders,
496
+ executionProfile
497
+ ),
491
498
  },
492
499
  body: JSON.stringify(body),
493
500
  };
@@ -882,7 +889,7 @@ export function createProgrammaticToolCallingTool(
882
889
  const maxRunTimeoutMs = resolveCodeApiRunTimeoutMs(initParams.runTimeoutMs);
883
890
  const proxy = initParams.proxy ?? process.env.PROXY;
884
891
  const debug = initParams.debug ?? process.env.PTC_DEBUG === 'true';
885
- const EXEC_ENDPOINT = `${baseUrl}/exec/programmatic`;
892
+ const EXEC_ENDPOINT = buildCodeApiEndpoint(baseUrl, 'exec/programmatic');
886
893
 
887
894
  return tool(
888
895
  async (rawParams, config) => {
@@ -952,14 +959,19 @@ export function createProgrammaticToolCallingTool(
952
959
  );
953
960
  }
954
961
 
955
- /* Stateful sessions: hint rides the INITIAL request only; the server
956
- * binds continuation round-trips to the same runtime via the
957
- * continuation_token. Additive ignored by stateless servers. PTC
958
- * keeps its stateless prompt in v1; only the wire hint plumbs here. */
962
+ /* The hint rides the INITIAL request only; continuation_token binds
963
+ * later round-trips. Prefer trusted per-agent factory context over
964
+ * legacy ToolNode injection. Explicit default profiles always drop it.
965
+ * PTC keeps its stateless runtime prompt in v1. */
966
+ const selectedRuntimeSessionHint = selectRuntimeSessionHint(
967
+ initParams.runtimeSessionHint,
968
+ _runtime_session_hint
969
+ );
959
970
  const runtimeSessionHint =
960
- typeof _runtime_session_hint === 'string' &&
961
- _runtime_session_hint !== ''
962
- ? _runtime_session_hint
971
+ initParams.executionProfile !== 'default' &&
972
+ typeof selectedRuntimeSessionHint === 'string' &&
973
+ selectedRuntimeSessionHint !== ''
974
+ ? selectedRuntimeSessionHint
963
975
  : undefined;
964
976
 
965
977
  let response = await makeRequest(
@@ -975,7 +987,8 @@ export function createProgrammaticToolCallingTool(
975
987
  : {}),
976
988
  },
977
989
  proxy,
978
- initParams.authHeaders
990
+ initParams.authHeaders,
991
+ initParams.executionProfile
979
992
  );
980
993
 
981
994
  // ====================================================================
@@ -1012,7 +1025,8 @@ export function createProgrammaticToolCallingTool(
1012
1025
  tool_results: toolResults,
1013
1026
  },
1014
1027
  proxy,
1015
- initParams.authHeaders
1028
+ initParams.authHeaders,
1029
+ initParams.executionProfile
1016
1030
  );
1017
1031
  }
1018
1032