@librechat/agents 4.0.2 → 4.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. package/dist/cjs/agents/AgentContext.cjs +3 -2
  2. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  3. package/dist/cjs/decisions/structuredChat.cjs +2 -8
  4. package/dist/cjs/decisions/structuredChat.cjs.map +1 -1
  5. package/dist/cjs/decisions/structuredOutput.cjs +18 -0
  6. package/dist/cjs/decisions/structuredOutput.cjs.map +1 -0
  7. package/dist/cjs/graphs/Graph.cjs +25 -9
  8. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  9. package/dist/cjs/llm/google/index.cjs +1 -1
  10. package/dist/cjs/llm/invoke.cjs +34 -12
  11. package/dist/cjs/llm/invoke.cjs.map +1 -1
  12. package/dist/cjs/llm/openai/index.cjs +7 -0
  13. package/dist/cjs/llm/openai/index.cjs.map +1 -1
  14. package/dist/cjs/llm/providerTextBoundary.cjs +355 -0
  15. package/dist/cjs/llm/providerTextBoundary.cjs.map +1 -0
  16. package/dist/cjs/llm/providerTextChunk.cjs +14 -0
  17. package/dist/cjs/llm/providerTextChunk.cjs.map +1 -0
  18. package/dist/cjs/llm/providerTextControls.cjs +236 -0
  19. package/dist/cjs/llm/providerTextControls.cjs.map +1 -0
  20. package/dist/cjs/llm/stream/smoother.cjs +2 -1
  21. package/dist/cjs/llm/stream/smoother.cjs.map +1 -1
  22. package/dist/cjs/main.cjs +18 -1
  23. package/dist/cjs/protection/providerText.cjs +224 -0
  24. package/dist/cjs/protection/providerText.cjs.map +1 -0
  25. package/dist/cjs/protection/providerTextInput.cjs +25 -0
  26. package/dist/cjs/protection/providerTextInput.cjs.map +1 -0
  27. package/dist/cjs/run.cjs +7 -1
  28. package/dist/cjs/run.cjs.map +1 -1
  29. package/dist/cjs/stream.cjs +42 -14
  30. package/dist/cjs/stream.cjs.map +1 -1
  31. package/dist/cjs/summarization/node.cjs +26 -8
  32. package/dist/cjs/summarization/node.cjs.map +1 -1
  33. package/dist/cjs/tools/GitHubCompare.cjs +181 -0
  34. package/dist/cjs/tools/GitHubCompare.cjs.map +1 -0
  35. package/dist/cjs/tools/SubagentTool.cjs +11 -3
  36. package/dist/cjs/tools/SubagentTool.cjs.map +1 -1
  37. package/dist/cjs/tools/ToolNode.cjs +6 -5
  38. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  39. package/dist/cjs/tools/subagent/SubagentExecutionRegistry.cjs +2 -2
  40. package/dist/cjs/tools/subagent/SubagentExecutionRegistry.cjs.map +1 -1
  41. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +66 -16
  42. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
  43. package/dist/cjs/tools/subagent/SubagentReplay.cjs +4 -2
  44. package/dist/cjs/tools/subagent/SubagentReplay.cjs.map +1 -1
  45. package/dist/cjs/tools/subagent/childGraphConfig.cjs +3 -1
  46. package/dist/cjs/tools/subagent/childGraphConfig.cjs.map +1 -1
  47. package/dist/cjs/tools/subagent/diagnostics.cjs +48 -5
  48. package/dist/cjs/tools/subagent/diagnostics.cjs.map +1 -1
  49. package/dist/cjs/tools/subagent/hostArgs.cjs +214 -0
  50. package/dist/cjs/tools/subagent/hostArgs.cjs.map +1 -0
  51. package/dist/cjs/tools/subagent/index.cjs +1 -0
  52. package/dist/cjs/tools/toolErrorContent.cjs +1 -1
  53. package/dist/esm/agents/AgentContext.mjs +3 -2
  54. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  55. package/dist/esm/decisions/structuredChat.mjs +2 -8
  56. package/dist/esm/decisions/structuredChat.mjs.map +1 -1
  57. package/dist/esm/decisions/structuredOutput.mjs +18 -0
  58. package/dist/esm/decisions/structuredOutput.mjs.map +1 -0
  59. package/dist/esm/graphs/Graph.mjs +25 -9
  60. package/dist/esm/graphs/Graph.mjs.map +1 -1
  61. package/dist/esm/llm/google/index.mjs +1 -1
  62. package/dist/esm/llm/invoke.mjs +34 -12
  63. package/dist/esm/llm/invoke.mjs.map +1 -1
  64. package/dist/esm/llm/openai/index.mjs +7 -0
  65. package/dist/esm/llm/openai/index.mjs.map +1 -1
  66. package/dist/esm/llm/providerTextBoundary.mjs +355 -0
  67. package/dist/esm/llm/providerTextBoundary.mjs.map +1 -0
  68. package/dist/esm/llm/providerTextChunk.mjs +13 -0
  69. package/dist/esm/llm/providerTextChunk.mjs.map +1 -0
  70. package/dist/esm/llm/providerTextControls.mjs +230 -0
  71. package/dist/esm/llm/providerTextControls.mjs.map +1 -0
  72. package/dist/esm/llm/stream/smoother.mjs +2 -1
  73. package/dist/esm/llm/stream/smoother.mjs.map +1 -1
  74. package/dist/esm/main.mjs +6 -3
  75. package/dist/esm/protection/providerText.mjs +218 -0
  76. package/dist/esm/protection/providerText.mjs.map +1 -0
  77. package/dist/esm/protection/providerTextInput.mjs +24 -0
  78. package/dist/esm/protection/providerTextInput.mjs.map +1 -0
  79. package/dist/esm/run.mjs +7 -1
  80. package/dist/esm/run.mjs.map +1 -1
  81. package/dist/esm/stream.mjs +42 -14
  82. package/dist/esm/stream.mjs.map +1 -1
  83. package/dist/esm/summarization/node.mjs +26 -8
  84. package/dist/esm/summarization/node.mjs.map +1 -1
  85. package/dist/esm/tools/GitHubCompare.mjs +175 -0
  86. package/dist/esm/tools/GitHubCompare.mjs.map +1 -0
  87. package/dist/esm/tools/SubagentTool.mjs +11 -3
  88. package/dist/esm/tools/SubagentTool.mjs.map +1 -1
  89. package/dist/esm/tools/ToolNode.mjs +6 -5
  90. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  91. package/dist/esm/tools/subagent/SubagentExecutionRegistry.mjs +2 -2
  92. package/dist/esm/tools/subagent/SubagentExecutionRegistry.mjs.map +1 -1
  93. package/dist/esm/tools/subagent/SubagentExecutor.mjs +66 -16
  94. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
  95. package/dist/esm/tools/subagent/SubagentReplay.mjs +4 -2
  96. package/dist/esm/tools/subagent/SubagentReplay.mjs.map +1 -1
  97. package/dist/esm/tools/subagent/childGraphConfig.mjs +3 -1
  98. package/dist/esm/tools/subagent/childGraphConfig.mjs.map +1 -1
  99. package/dist/esm/tools/subagent/diagnostics.mjs +46 -6
  100. package/dist/esm/tools/subagent/diagnostics.mjs.map +1 -1
  101. package/dist/esm/tools/subagent/hostArgs.mjs +207 -0
  102. package/dist/esm/tools/subagent/hostArgs.mjs.map +1 -0
  103. package/dist/esm/tools/subagent/index.mjs +1 -0
  104. package/dist/esm/tools/toolErrorContent.mjs +1 -1
  105. package/dist/types/decisions/structuredChat.d.ts +3 -2
  106. package/dist/types/decisions/structuredOutput.d.ts +4 -0
  107. package/dist/types/graphs/Graph.d.ts +6 -3
  108. package/dist/types/index.d.ts +3 -0
  109. package/dist/types/llm/invoke.d.ts +4 -1
  110. package/dist/types/llm/providerTextBoundary.d.ts +4 -0
  111. package/dist/types/llm/providerTextChunk.d.ts +7 -0
  112. package/dist/types/llm/providerTextControls.d.ts +18 -0
  113. package/dist/types/protection/providerText.d.ts +77 -0
  114. package/dist/types/protection/providerTextInput.d.ts +3 -0
  115. package/dist/types/run.d.ts +1 -0
  116. package/dist/types/tools/BashExecutor.d.ts +4 -0
  117. package/dist/types/tools/CodeExecutor.d.ts +4 -0
  118. package/dist/types/tools/GitHubCompare.d.ts +104 -0
  119. package/dist/types/tools/ReadFile.d.ts +4 -0
  120. package/dist/types/tools/SkillTool.d.ts +4 -0
  121. package/dist/types/tools/SubagentTool.d.ts +2 -0
  122. package/dist/types/tools/ToolSearch.d.ts +4 -0
  123. package/dist/types/tools/search/schema.d.ts +4 -0
  124. package/dist/types/tools/subagent/SubagentExecutionRegistry.d.ts +2 -0
  125. package/dist/types/tools/subagent/SubagentExecutor.d.ts +10 -2
  126. package/dist/types/tools/subagent/SubagentReplay.d.ts +3 -1
  127. package/dist/types/tools/subagent/diagnostics.d.ts +27 -3
  128. package/dist/types/tools/subagent/hostArgs.d.ts +57 -0
  129. package/dist/types/tools/subagent/index.d.ts +3 -2
  130. package/dist/types/types/graph.d.ts +36 -0
  131. package/dist/types/types/run.d.ts +4 -2
  132. package/dist/types/types/tools.d.ts +2 -0
  133. package/package.json +4 -4
  134. package/src/agents/AgentContext.ts +3 -2
  135. package/src/decisions/structuredChat.ts +9 -15
  136. package/src/decisions/structuredOutput.ts +25 -0
  137. package/src/graphs/Graph.ts +53 -9
  138. package/src/index.ts +8 -0
  139. package/src/llm/invoke.ts +48 -7
  140. package/src/llm/openai/index.ts +8 -0
  141. package/src/llm/providerTextBoundary.ts +446 -0
  142. package/src/llm/providerTextChunk.ts +15 -0
  143. package/src/llm/providerTextControls.ts +173 -0
  144. package/src/llm/stream/smoother.ts +4 -2
  145. package/src/protection/providerText.ts +325 -0
  146. package/src/protection/providerTextInput.ts +21 -0
  147. package/src/run.ts +9 -0
  148. package/src/stream.ts +33 -7
  149. package/src/summarization/node.ts +32 -14
  150. package/src/tools/GitHubCompare.ts +319 -0
  151. package/src/tools/SubagentTool.ts +14 -3
  152. package/src/tools/ToolNode.ts +7 -3
  153. package/src/tools/subagent/SubagentExecutionRegistry.ts +6 -2
  154. package/src/tools/subagent/SubagentExecutor.ts +145 -18
  155. package/src/tools/subagent/SubagentReplay.ts +10 -3
  156. package/src/tools/subagent/childGraphConfig.ts +12 -1
  157. package/src/tools/subagent/diagnostics.ts +98 -6
  158. package/src/tools/subagent/hostArgs.ts +413 -0
  159. package/src/tools/subagent/index.ts +9 -0
  160. package/src/types/graph.ts +41 -0
  161. package/src/types/run.ts +4 -2
  162. package/src/types/tools.ts +6 -1
@@ -60,6 +60,7 @@ import type {
60
60
  SubagentTaskConfig,
61
61
  SubagentTaskRuntime,
62
62
  SubagentResolveConfigurable,
63
+ SubagentHostArgs,
63
64
  SubagentResolveRequestContext,
64
65
  SubagentResolveUserContext,
65
66
  StepCompleted,
@@ -80,6 +81,12 @@ import type {
80
81
  SubagentExecutionPreparationLease,
81
82
  SubagentExecutionRecord,
82
83
  } from './SubagentExecutionRegistry';
84
+ import type {
85
+ SubagentResolutionPhase,
86
+ SubagentResolutionCause,
87
+ SubagentHostArgumentFailure,
88
+ SubagentResolutionFailureHandler,
89
+ } from './diagnostics';
83
90
  import type {
84
91
  SubagentResumeExecution,
85
92
  SubagentResumeManifest,
@@ -92,11 +99,6 @@ import type {
92
99
  PreemptBoundaryHookOutput,
93
100
  ToolApprovalReplaySnapshot,
94
101
  } from '@/hooks';
95
- import type {
96
- SubagentResolutionPhase,
97
- SubagentResolutionCause,
98
- SubagentResolutionFailureHandler,
99
- } from './diagnostics';
100
102
  import type { RunBreakerScope } from '@/llm/streamLimits';
101
103
  import type { GraphFactory } from '@/graphs/graphFactory';
102
104
  import type { StandardGraph } from '@/graphs/Graph';
@@ -114,6 +116,18 @@ import {
114
116
  SUBAGENT_RESUME_ATTEMPT_CONFIG_KEY,
115
117
  SUBAGENT_RESUME_MANIFEST_CONFIG_KEY,
116
118
  } from './SubagentReplay';
119
+ import {
120
+ logSubagentResolutionFailure,
121
+ SubagentResolutionError,
122
+ getSubagentHostArgumentFailure,
123
+ getSubagentHostArgumentFailureMessage,
124
+ } from './diagnostics';
125
+ import {
126
+ getSubagentHostArgSpecs,
127
+ resolveSubagentHostArgs,
128
+ pickSubagentHostArgInput,
129
+ getSubagentHostArgsDigest,
130
+ } from './hostArgs';
117
131
  import {
118
132
  rebindToolBatchReplayScope,
119
133
  rebindToolBatchReplayPayload,
@@ -132,10 +146,6 @@ import {
132
146
  DEFAULT_SUBAGENT_MAX_TURNS,
133
147
  SUBAGENT_RECURSION_MULTIPLIER,
134
148
  } from './runtimeLimits';
135
- import {
136
- logSubagentResolutionFailure,
137
- SubagentResolutionError,
138
- } from './diagnostics';
139
149
  import {
140
150
  ContentTypes,
141
151
  Constants,
@@ -147,6 +157,7 @@ import {
147
157
  createChildGraphPlan,
148
158
  isGraphSubagentConfig,
149
159
  } from './childGraphConfig';
160
+ import { ProviderTextProtectionError } from '@/protection/providerText';
150
161
  import { stripRunStepResumeState } from '@/tools/runStepResume';
151
162
  import { seedAgentInitialSessions } from '@/utils/toolSessions';
152
163
  import { stableStringify } from '@/tools/eagerEventExecution';
@@ -178,7 +189,11 @@ const SUBAGENT_INVOCATION_CHANGED_MESSAGE =
178
189
 
179
190
  function isSubagentResolutionControlFlow(error: unknown): boolean {
180
191
  try {
181
- return error instanceof StreamLimitExceededError || isGraphInterrupt(error);
192
+ return (
193
+ error instanceof StreamLimitExceededError ||
194
+ error instanceof ProviderTextProtectionError ||
195
+ isGraphInterrupt(error)
196
+ );
182
197
  } catch {
183
198
  return false;
184
199
  }
@@ -678,7 +693,8 @@ function getSettlementFingerprint(output: PersistedToolOutput): string {
678
693
  function getBackgroundTaskFingerprint(
679
694
  description: string,
680
695
  subagentType: string,
681
- threadId?: string
696
+ threadId?: string,
697
+ hostArgsDigest?: string
682
698
  ): string {
683
699
  return createHash('sha256')
684
700
  .update(
@@ -686,11 +702,29 @@ function getBackgroundTaskFingerprint(
686
702
  description,
687
703
  subagentType,
688
704
  ...(threadId == null || threadId === '' ? {} : { threadId }),
705
+ ...(hostArgsDigest == null ? {} : { hostArgsDigest }),
689
706
  })
690
707
  )
691
708
  .digest('hex');
692
709
  }
693
710
 
711
+ /** Host-argument digest of persisted lifecycle args, when they are well formed. */
712
+ function getHostArgsDigestFromArgs(
713
+ config: ExecutableSubagentConfigEntry | undefined,
714
+ args: unknown
715
+ ): string | undefined {
716
+ if (config == null || !isObjectLike(args)) {
717
+ return undefined;
718
+ }
719
+ const names = new Set(getSubagentHostArgSpecs(config).map(([name]) => name));
720
+ const picked = pickSubagentHostArgInput(args, names);
721
+ if (!picked.ok) {
722
+ return undefined;
723
+ }
724
+ const resolved = resolveSubagentHostArgs(config, picked.hostArgs);
725
+ return resolved.ok ? getSubagentHostArgsDigest(resolved.hostArgs) : undefined;
726
+ }
727
+
694
728
  function getBackgroundTaskHookSessionId(
695
729
  sourceHookSessionId: string,
696
730
  taskId: string
@@ -877,6 +911,11 @@ export type SubagentExecuteParams = {
877
911
  taskRuntime?: SubagentTaskRuntime;
878
912
  /** Host-restored child transcript for a fresh continuation run. @internal */
879
913
  initialMessages?: BaseMessage[];
914
+ /**
915
+ * Values for the selected subagent's declared `hostArgs`. Checked against
916
+ * the declaration before an execution opens, then bound to it.
917
+ */
918
+ hostArgs?: SubagentHostArgs;
880
919
  };
881
920
 
882
921
  export type SubagentExecuteResult = SubagentContextResult & {
@@ -886,6 +925,7 @@ export type SubagentExecuteResult = SubagentContextResult & {
886
925
  resolutionFailure?: {
887
926
  phase: SubagentResolutionPhase;
888
927
  cause: SubagentResolutionCause;
928
+ hostArgument?: SubagentHostArgumentFailure;
889
929
  };
890
930
  /** Completed child work whose host projection must be retried without re-execution. */
891
931
  retryableDelivery?: true;
@@ -957,6 +997,7 @@ export type SubagentExecutorOptions = {
957
997
  * graph's own resolved limits; without this the child would silently
958
998
  * revert to the defaults.
959
999
  */
1000
+ providerTextProtection?: StandardGraphInput['providerTextProtection'];
960
1001
  streamLimits?: StandardGraphInput['streamLimits'];
961
1002
  humanInTheLoop?: HumanInTheLoopConfig;
962
1003
  /** Shared durable saver used to recover outer tool lifecycle results before
@@ -1034,6 +1075,7 @@ export class SubagentExecutor {
1034
1075
  private readonly executionContext: SubagentExecutionContext;
1035
1076
  private readonly langfuse?: StandardGraphInput['langfuse'];
1036
1077
  private readonly tokenCounter?: TokenCounter;
1078
+ private readonly providerTextProtection?: StandardGraphInput['providerTextProtection'];
1037
1079
  private readonly streamLimits?: StandardGraphInput['streamLimits'];
1038
1080
  private readonly humanInTheLoop?: HumanInTheLoopConfig;
1039
1081
  private readonly checkpointer?: BaseCheckpointSaver;
@@ -1074,6 +1116,7 @@ export class SubagentExecutor {
1074
1116
  this.langfuse = options.langfuse;
1075
1117
  this.tokenCounter = options.tokenCounter;
1076
1118
  this.streamLimits = options.streamLimits;
1119
+ this.providerTextProtection = options.providerTextProtection;
1077
1120
  this.humanInTheLoop = options.humanInTheLoop;
1078
1121
  this.checkpointer = isCheckpointSaver(options.checkpointer)
1079
1122
  ? options.checkpointer
@@ -1148,6 +1191,18 @@ export class SubagentExecutor {
1148
1191
  message: `Unknown subagent type "${params.subagentType}".`,
1149
1192
  });
1150
1193
  }
1194
+ const hostArgResult = resolveSubagentHostArgs(
1195
+ executableConfig,
1196
+ params.hostArgs
1197
+ );
1198
+ if (!hostArgResult.ok) {
1199
+ return JSON.stringify({
1200
+ status: 'rejected',
1201
+ tool: Constants.SUBAGENT,
1202
+ message: hostArgResult.message,
1203
+ });
1204
+ }
1205
+ const { hostArgs } = hostArgResult;
1151
1206
  if (this.maxDepth <= 0) {
1152
1207
  return JSON.stringify({
1153
1208
  status: 'rejected',
@@ -1213,7 +1268,8 @@ export class SubagentExecutor {
1213
1268
  requestFingerprint: getBackgroundTaskFingerprint(
1214
1269
  params.description,
1215
1270
  params.subagentType,
1216
- subagentThreadId
1271
+ subagentThreadId,
1272
+ getSubagentHostArgsDigest(hostArgs)
1217
1273
  ),
1218
1274
  ...(subagentThreadId == null || subagentThreadId === ''
1219
1275
  ? {}
@@ -1228,6 +1284,7 @@ export class SubagentExecutor {
1228
1284
  this.executeDetached(
1229
1285
  {
1230
1286
  ...params,
1287
+ hostArgs,
1231
1288
  ...(initialMessages == null ? {} : { initialMessages }),
1232
1289
  },
1233
1290
  runtime,
@@ -1344,6 +1401,7 @@ export class SubagentExecutor {
1344
1401
  subagentContext: this.subagentContext,
1345
1402
  onResolutionFailure: this.onResolutionFailure,
1346
1403
  streamLimits: this.streamLimits,
1404
+ providerTextProtection: this.providerTextProtection,
1347
1405
  humanInTheLoop:
1348
1406
  this.humanInTheLoop?.enabled === true ||
1349
1407
  this.humanInTheLoop?.backgroundPausePolicy === 'deny'
@@ -1397,7 +1455,8 @@ export class SubagentExecutor {
1397
1455
  if (result.resolutionFailure != null) {
1398
1456
  throw new SubagentResolutionError(
1399
1457
  result.resolutionFailure.phase,
1400
- result.resolutionFailure.cause
1458
+ result.resolutionFailure.cause,
1459
+ result.resolutionFailure.hostArgument
1401
1460
  );
1402
1461
  }
1403
1462
  throw new Error(result.error);
@@ -1461,6 +1520,7 @@ export class SubagentExecutor {
1461
1520
  threadId?: string;
1462
1521
  parentToolCallId?: string;
1463
1522
  parentConfigurable?: Record<string, unknown>;
1523
+ hostArgs?: SubagentHostArgs;
1464
1524
  }
1465
1525
  ): Promise<ResolvedSubagentConfigEntry> {
1466
1526
  if (isGraphSubagentConfig(config) || config.agentInputs != null) {
@@ -1489,6 +1549,9 @@ export class SubagentExecutor {
1489
1549
  context.parentConfigurable == null
1490
1550
  ? undefined
1491
1551
  : sanitizeResolverConfigurable(context.parentConfigurable),
1552
+ ...(context.hostArgs == null
1553
+ ? {}
1554
+ : { hostArgs: context.hostArgs }),
1492
1555
  }),
1493
1556
  context.childSignal
1494
1557
  ).then(
@@ -1937,7 +2000,7 @@ export class SubagentExecutor {
1937
2000
  }
1938
2001
  const executions: SubagentResumeExecution[] = [];
1939
2002
  for (const record of records) {
1940
- const { identity, binding, activeRun } = record.snapshot;
2003
+ const { identity, binding, invocation, activeRun } = record.snapshot;
1941
2004
  if (identity == null) {
1942
2005
  continue;
1943
2006
  }
@@ -1981,6 +2044,9 @@ export class SubagentExecutor {
1981
2044
  ? {}
1982
2045
  : { subagentType: binding.subagentType }),
1983
2046
  ...(binding?.configId == null ? {} : { configId: binding.configId }),
2047
+ ...(invocation?.hostArgsDigest == null
2048
+ ? {}
2049
+ : { hostArgsDigest: invocation.hostArgsDigest }),
1984
2050
  approvalExecutionScope: identity.approvalExecutionScope,
1985
2051
  checkpoints,
1986
2052
  graphState,
@@ -2333,6 +2399,22 @@ export class SubagentExecutor {
2333
2399
  ) {
2334
2400
  return;
2335
2401
  }
2402
+ const resolvedHostArgsDigest =
2403
+ settled.resolvedArgs == null
2404
+ ? undefined
2405
+ : getHostArgsDigestFromArgs(
2406
+ this.configs.get(
2407
+ resolvedSubagentType ?? execution.binding?.subagentType ?? ''
2408
+ ),
2409
+ settled.resolvedArgs
2410
+ );
2411
+ if (
2412
+ boundInvocation != null &&
2413
+ settled.resolvedArgs != null &&
2414
+ resolvedHostArgsDigest !== boundInvocation.hostArgsDigest
2415
+ ) {
2416
+ return;
2417
+ }
2336
2418
  const description =
2337
2419
  boundInvocation?.description ??
2338
2420
  resolvedDescription ??
@@ -2364,10 +2446,15 @@ export class SubagentExecutor {
2364
2446
  return;
2365
2447
  }
2366
2448
  const persistedOutput = serializeToolOutput(settled);
2449
+ const hostArgsDigest =
2450
+ settled.resolvedArgs == null
2451
+ ? getHostArgsDigestFromArgs(executableConfig, call.args)
2452
+ : resolvedHostArgsDigest;
2367
2453
  const invocation = boundInvocation ?? {
2368
2454
  description,
2369
2455
  subagentType,
2370
2456
  ...(configId == null ? {} : { configId }),
2457
+ ...(hostArgsDigest == null ? {} : { hostArgsDigest }),
2371
2458
  };
2372
2459
  try {
2373
2460
  await execution.settle(
@@ -2484,6 +2571,15 @@ export class SubagentExecutor {
2484
2571
  )
2485
2572
  );
2486
2573
  }
2574
+ const hostArgResult = resolveSubagentHostArgs(
2575
+ executableConfig,
2576
+ params.hostArgs
2577
+ );
2578
+ if (!hostArgResult.ok) {
2579
+ return Promise.resolve(createSubagentFailure(hostArgResult.message));
2580
+ }
2581
+ const { hostArgs } = hostArgResult;
2582
+ const hostArgsDigest = getSubagentHostArgsDigest(hostArgs);
2487
2583
  if (this.maxDepth <= 0) {
2488
2584
  return Promise.resolve(
2489
2585
  createSubagentFailure('Error: Maximum subagent nesting depth exceeded.')
@@ -2522,8 +2618,10 @@ export class SubagentExecutor {
2522
2618
  ...(executableConfig.configId == null
2523
2619
  ? {}
2524
2620
  : { configId: executableConfig.configId }),
2621
+ ...(hostArgsDigest == null ? {} : { hostArgsDigest }),
2525
2622
  },
2526
- () => this.executeOnce(params, execution, executableConfig)
2623
+ () =>
2624
+ this.executeOnce({ ...params, hostArgs }, execution, executableConfig)
2527
2625
  );
2528
2626
  } catch (error) {
2529
2627
  if (error instanceof SubagentDefinitionBindingError) {
@@ -2616,8 +2714,21 @@ export class SubagentExecutor {
2616
2714
  : execution.address.branchChildThreadId),
2617
2715
  taskId: params.taskRuntime?.taskId,
2618
2716
  },
2619
- this.onResolutionFailure
2717
+ this.onResolutionFailure,
2718
+ params.hostArgs
2620
2719
  );
2720
+ const hostArgument =
2721
+ phase === 'config'
2722
+ ? getSubagentHostArgumentFailure(error, params.hostArgs)
2723
+ : undefined;
2724
+ if (hostArgument != null) {
2725
+ return {
2726
+ ...createSubagentFailure(
2727
+ getSubagentHostArgumentFailureMessage(hostArgument)
2728
+ ),
2729
+ resolutionFailure: { phase, cause: detail.cause, hostArgument },
2730
+ };
2731
+ }
2621
2732
  return {
2622
2733
  ...createSubagentFailure(detail.message),
2623
2734
  resolutionFailure: { phase, cause: detail.cause },
@@ -2652,6 +2763,14 @@ export class SubagentExecutor {
2652
2763
  this.executions.remove(execution);
2653
2764
  return createSubagentFailure(SUBAGENT_CONFIG_CHANGED_MESSAGE);
2654
2765
  }
2766
+ if (
2767
+ resumeExecution != null &&
2768
+ resumeExecution.hostArgsDigest !==
2769
+ getSubagentHostArgsDigest(params.hostArgs)
2770
+ ) {
2771
+ this.executions.remove(execution);
2772
+ return createSubagentFailure(SUBAGENT_INVOCATION_CHANGED_MESSAGE);
2773
+ }
2655
2774
  let identity: SubagentExecutionIdentity;
2656
2775
  try {
2657
2776
  identity = await this.resolveChildExecutionIdentity(execution);
@@ -2690,6 +2809,7 @@ export class SubagentExecutor {
2690
2809
  threadId,
2691
2810
  parentToolCallId,
2692
2811
  parentConfigurable: params.parentConfigurable,
2812
+ hostArgs: params.hostArgs,
2693
2813
  });
2694
2814
  execution.assertUsable(childSignal);
2695
2815
  } catch (error) {
@@ -2831,6 +2951,7 @@ export class SubagentExecutor {
2831
2951
  langfuse: this.langfuse,
2832
2952
  tokenCounter: this.tokenCounter,
2833
2953
  streamLimits: this.streamLimits,
2954
+ providerTextProtection: this.providerTextProtection,
2834
2955
  subagentScope: true,
2835
2956
  subagentExecutionContext: childExecutionContext,
2836
2957
  subagentContext: this.subagentContext,
@@ -3277,7 +3398,10 @@ export class SubagentExecutor {
3277
3398
  * quota for that entire interval. Trips the ENTRY-captured controller:
3278
3399
  * after a reset, a straggler must break its own dead run, not the
3279
3400
  * current one. */
3280
- if (error instanceof StreamLimitExceededError) {
3401
+ if (
3402
+ error instanceof StreamLimitExceededError ||
3403
+ error instanceof ProviderTextProtectionError
3404
+ ) {
3281
3405
  childBreaker.abort(error);
3282
3406
  }
3283
3407
  const errorMessage = truncateErrorMessage(error);
@@ -3316,7 +3440,10 @@ export class SubagentExecutor {
3316
3440
  * fired. Rethrown here and passed through ToolNode's error conversion,
3317
3441
  * so the parent run rejects with the child's limit error.
3318
3442
  */
3319
- if (error instanceof StreamLimitExceededError) {
3443
+ if (
3444
+ error instanceof StreamLimitExceededError ||
3445
+ error instanceof ProviderTextProtectionError
3446
+ ) {
3320
3447
  throw error;
3321
3448
  }
3322
3449
  return createSubagentFailure(
@@ -1,20 +1,20 @@
1
1
  import type { ToolCall, ToolMessage } from '@langchain/core/messages/tool';
2
2
  import type { RunnableConfig } from '@langchain/core/runnables';
3
- import type { ToolOutputReferenceState } from '@/tools/toolOutputReferences';
4
- import type { ToolApprovalReplaySnapshot } from '@/hooks';
5
3
  import type {
6
4
  FadingTier,
7
5
  FadingTiers,
8
6
  RunStepResumeState,
9
7
  ToolSessionContext,
10
8
  } from '@/types';
11
- import { isFadingTier, isLegacyFadingTier } from '@/messages/fading';
9
+ import type { ToolOutputReferenceState } from '@/tools/toolOutputReferences';
10
+ import type { ToolApprovalReplaySnapshot } from '@/hooks';
12
11
  import {
13
12
  attachRunStepResumeState,
14
13
  getRunStepResumeState,
15
14
  isRunStepResumeState,
16
15
  stripRunStepResumeState,
17
16
  } from '@/tools/runStepResume';
17
+ import { isFadingTier, isLegacyFadingTier } from '@/messages/fading';
18
18
 
19
19
  export const SUBAGENT_RESUME_MANIFEST_CONFIG_KEY =
20
20
  '__librechat_subagent_resume_manifest';
@@ -35,6 +35,7 @@ const SUBAGENT_RESUME_PRIVATE_PAYLOAD_KEYS = [
35
35
  SUBAGENT_RESUME_WRAPPER_VERSION_KEY,
36
36
  ] as const;
37
37
  const MAX_RESUME_MANIFEST_DEPTH = 32;
38
+ const HOST_ARGS_DIGEST_PATTERN = /^[a-f0-9]{64}$/;
38
39
 
39
40
  export interface SubagentCheckpointReference {
40
41
  threadId: string;
@@ -83,6 +84,8 @@ export interface SubagentResumeExecution {
83
84
  subagentType?: string;
84
85
  /** Lazy child revision bound when this execution first resolved. */
85
86
  configId?: string;
87
+ /** SHA-256 identity of the host arguments bound to this execution; never the values. */
88
+ hostArgsDigest?: string;
86
89
  approvalExecutionScope: string;
87
90
  checkpoints: SubagentCheckpointReference[];
88
91
  graphState: SubagentGraphResumeState;
@@ -354,6 +357,7 @@ function isSubagentResumeExecution(
354
357
  const childRunId = execution.childRunId;
355
358
  const subagentType = execution.subagentType;
356
359
  const configId = execution.configId;
360
+ const hostArgsDigest = execution.hostArgsDigest;
357
361
  const approvalExecutionScope = execution.approvalExecutionScope;
358
362
  const checkpoints = execution.checkpoints;
359
363
  const graphState = execution.graphState;
@@ -363,6 +367,9 @@ function isSubagentResumeExecution(
363
367
  !isString(childRunId) ||
364
368
  (subagentType != null && !isString(subagentType)) ||
365
369
  (configId != null && !isString(configId)) ||
370
+ (hostArgsDigest != null &&
371
+ (typeof hostArgsDigest !== 'string' ||
372
+ !HOST_ARGS_DIGEST_PATTERN.test(hostArgsDigest))) ||
366
373
  !isString(approvalExecutionScope) ||
367
374
  !Array.isArray(checkpoints) ||
368
375
  checkpoints.length === 0 ||
@@ -15,6 +15,7 @@ import {
15
15
  MAX_GRAPH_SUBAGENT_MEMBERS,
16
16
  MAX_GRAPH_SUBAGENT_TURNS,
17
17
  } from './runtimeLimits';
18
+ import { getSubagentHostArgSpecs } from './hostArgs';
18
19
 
19
20
  const RESERVED_GRAPH_MEMBER_IDS = new Set([
20
21
  '__start__',
@@ -69,6 +70,7 @@ type GraphSubagentConfigCandidate = {
69
70
  configId?: unknown;
70
71
  agentInputs?: unknown;
71
72
  resolveAgentInputs?: unknown;
73
+ hostArgs?: unknown;
72
74
  self?: unknown;
73
75
  };
74
76
 
@@ -156,10 +158,11 @@ function assertGraphSubagentConfigShape(
156
158
  candidate.agentInputs !== undefined ||
157
159
  candidate.resolveAgentInputs !== undefined ||
158
160
  candidate.configId !== undefined ||
161
+ candidate.hostArgs !== undefined ||
159
162
  candidate.self !== undefined
160
163
  ) {
161
164
  throw new Error(
162
- `Graph subagent "${configType}" cannot define agentInputs or self, or lazy fields configId/resolveAgentInputs.`
165
+ `Graph subagent "${configType}" cannot define agentInputs or self, or lazy fields configId/resolveAgentInputs/hostArgs.`
163
166
  );
164
167
  }
165
168
  }
@@ -482,6 +485,14 @@ function normalizeSingleAgentConfig(
482
485
  `Subagent "${config.type}" cannot combine self with resolveAgentInputs.`
483
486
  );
484
487
  }
488
+ if (
489
+ getSubagentHostArgSpecs(config).length > 0 &&
490
+ (config.resolveAgentInputs == null || config.agentInputs != null)
491
+ ) {
492
+ throw new Error(
493
+ `Subagent "${config.type}" declares hostArgs, which only a lazy resolveAgentInputs config without eager agentInputs can receive.`
494
+ );
495
+ }
485
496
  if (config.agentInputs != null) {
486
497
  return config as ResolvedSingleAgentSubagentConfig;
487
498
  }
@@ -1,3 +1,4 @@
1
+ import type { SubagentHostArgs } from '@/types/graph';
1
2
  import {
2
3
  SubagentSettlementBindingError,
3
4
  SubagentDefinitionBindingError,
@@ -5,11 +6,39 @@ import {
5
6
  SubagentExecutionInvalidatedError,
6
7
  } from './SubagentExecutionRegistry';
7
8
  import { describeCodeApiError } from '@/tools/diagnostics';
9
+ import { isSubagentHostArgName } from './hostArgs';
8
10
 
9
11
  /** Which step of child start-up failed: execution identity, then host config resolution. */
10
12
  export type SubagentResolutionPhase = 'identity' | 'config';
11
13
 
14
+ /** Why a host resolver refused a declared host argument value. */
15
+ export type SubagentHostArgumentRejection = 'unavailable' | 'not_allowed';
16
+
17
+ /**
18
+ * Thrown by a lazy resolver to refuse one declared host argument value. The
19
+ * parent model receives a fixed message naming the argument; the value and
20
+ * any error text stay private.
21
+ */
22
+ export class SubagentHostArgumentError extends Error {
23
+ readonly argument: string;
24
+ readonly rejection: SubagentHostArgumentRejection;
25
+
26
+ constructor(argument: string, rejection: SubagentHostArgumentRejection) {
27
+ super('Subagent host argument was rejected.');
28
+ this.name = 'SubagentHostArgumentError';
29
+ this.argument = argument;
30
+ this.rejection = rejection;
31
+ }
32
+ }
33
+
34
+ /** Safe host-argument refusal retained across the detached delivery boundary. */
35
+ export type SubagentHostArgumentFailure = {
36
+ argument: string;
37
+ rejection: SubagentHostArgumentRejection;
38
+ };
39
+
12
40
  const SUBAGENT_ERROR_TYPES = [
41
+ ['SubagentHostArgumentError', SubagentHostArgumentError],
13
42
  ['SubagentExecutionInvalidatedError', SubagentExecutionInvalidatedError],
14
43
  ['SubagentDefinitionBindingError', SubagentDefinitionBindingError],
15
44
  ['SubagentInvocationBindingError', SubagentInvocationBindingError],
@@ -27,6 +56,8 @@ const SUBAGENT_RESOLUTION_MESSAGES = {
27
56
  model_unavailable: 'Subagent error: Model or provider unavailable.',
28
57
  configuration_changed:
29
58
  'Subagent error: Subagent configuration changed. Start a new execution.',
59
+ host_argument_rejected:
60
+ 'Subagent error: A requested subagent argument is not available. Omit it to let the host choose, or pass a different value.',
30
61
  unknown: 'Subagent error: Unable to initialize the selected subagent.',
31
62
  } as const;
32
63
 
@@ -67,6 +98,7 @@ function normalizeResolutionCause(
67
98
  case 'agent_unavailable':
68
99
  case 'model_unavailable':
69
100
  case 'configuration_changed':
101
+ case 'host_argument_rejected':
70
102
  return cause;
71
103
  default:
72
104
  return 'unknown';
@@ -79,16 +111,68 @@ export function getSubagentResolutionFailureMessage(
79
111
  return SUBAGENT_RESOLUTION_MESSAGES[normalizeResolutionCause(cause)];
80
112
  }
81
113
 
114
+ /** Returns a resolver's typed host-argument refusal when its name is safe to show. */
115
+ export function getSubagentHostArgumentFailure(
116
+ error: unknown,
117
+ suppliedHostArgs: SubagentHostArgs | undefined
118
+ ): SubagentHostArgumentFailure | undefined {
119
+ try {
120
+ if (
121
+ !(error instanceof SubagentHostArgumentError) ||
122
+ suppliedHostArgs == null
123
+ ) {
124
+ return undefined;
125
+ }
126
+ const { argument, rejection } = error;
127
+ if (
128
+ typeof argument !== 'string' ||
129
+ !isSubagentHostArgName(argument) ||
130
+ !Object.prototype.hasOwnProperty.call(suppliedHostArgs, argument)
131
+ ) {
132
+ return undefined;
133
+ }
134
+ return {
135
+ argument,
136
+ rejection: rejection === 'unavailable' ? 'unavailable' : 'not_allowed',
137
+ };
138
+ } catch {
139
+ return undefined;
140
+ }
141
+ }
142
+
143
+ /** Model-facing message for a refused host argument. */
144
+ export function getSubagentHostArgumentFailureMessage(
145
+ failure: SubagentHostArgumentFailure
146
+ ): string {
147
+ const { argument } = failure;
148
+ const omit = `Omit "${argument}" to let the host choose, or pass a different value.`;
149
+ return failure.rejection === 'unavailable'
150
+ ? `Subagent error: The requested "${argument}" is unavailable right now. ${omit}`
151
+ : `Subagent error: The requested "${argument}" is not allowed for this subagent. ${omit}`;
152
+ }
153
+
82
154
  /** Safe typed failure retained when detached execution crosses the host boundary. */
83
155
  export class SubagentResolutionError extends Error {
84
156
  readonly phase: SubagentResolutionPhase;
85
157
  readonly resolutionCause: SubagentResolutionCause;
158
+ readonly hostArgument?: SubagentHostArgumentFailure;
86
159
 
87
- constructor(phase: SubagentResolutionPhase, cause: SubagentResolutionCause) {
88
- super(getSubagentResolutionFailureMessage(cause));
160
+ constructor(
161
+ phase: SubagentResolutionPhase,
162
+ cause: SubagentResolutionCause,
163
+ hostArgument?: SubagentHostArgumentFailure
164
+ ) {
165
+ super(
166
+ hostArgument == null
167
+ ? getSubagentResolutionFailureMessage(cause)
168
+ : getSubagentHostArgumentFailureMessage(hostArgument)
169
+ );
89
170
  this.name = 'SubagentResolutionError';
90
171
  this.phase = phase;
91
172
  this.resolutionCause = normalizeResolutionCause(cause);
173
+ if (hostArgument != null) {
174
+ this.hostArgument = hostArgument;
175
+ }
92
176
  }
93
177
  }
94
178
 
@@ -111,7 +195,8 @@ export function logSubagentResolutionFailure(
111
195
  signal: AbortSignal,
112
196
  error: unknown,
113
197
  context?: SubagentResolutionContext,
114
- onResolutionFailure?: SubagentResolutionFailureHandler
198
+ onResolutionFailure?: SubagentResolutionFailureHandler,
199
+ suppliedHostArgs?: SubagentHostArgs
115
200
  ): SubagentResolutionDiagnostic {
116
201
  const detail: SubagentResolutionDiagnostic = {
117
202
  ...context,
@@ -122,11 +207,18 @@ export function logSubagentResolutionFailure(
122
207
  cause: 'unknown',
123
208
  message: SUBAGENT_RESOLUTION_MESSAGES.unknown,
124
209
  };
210
+ const hostArgumentRejected =
211
+ getSubagentHostArgumentFailure(error, suppliedHostArgs) != null;
212
+ if (hostArgumentRejected) {
213
+ detail.cause = 'host_argument_rejected';
214
+ detail.message = SUBAGENT_RESOLUTION_MESSAGES.host_argument_rejected;
215
+ }
125
216
  if (onResolutionFailure != null) {
126
217
  try {
127
- const cause = normalizeResolutionCause(
128
- onResolutionFailure(Object.freeze(detail), error)
129
- );
218
+ const reported = onResolutionFailure(Object.freeze({ ...detail }), error);
219
+ const cause = hostArgumentRejected
220
+ ? detail.cause
221
+ : normalizeResolutionCause(reported);
130
222
  return {
131
223
  ...detail,
132
224
  cause,