@librechat/agents 3.9.8 → 4.0.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 (31) hide show
  1. package/dist/cjs/llm/bedrock/index.cjs +1 -1
  2. package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
  3. package/dist/cjs/llm/bedrock/utils/message_inputs.cjs +20 -9
  4. package/dist/cjs/llm/bedrock/utils/message_inputs.cjs.map +1 -1
  5. package/dist/cjs/tools/BashExecutor.cjs +1 -1
  6. package/dist/cjs/tools/CodeExecutor.cjs +1 -1
  7. package/dist/cjs/tools/ProgrammaticToolCalling.cjs +1 -1
  8. package/dist/cjs/tools/subagent/SubagentExecutionRegistry.cjs +1 -0
  9. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +3 -0
  10. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
  11. package/dist/cjs/tools/subagent/diagnostics.cjs +30 -0
  12. package/dist/cjs/tools/subagent/diagnostics.cjs.map +1 -0
  13. package/dist/esm/llm/bedrock/index.mjs +1 -1
  14. package/dist/esm/llm/bedrock/index.mjs.map +1 -1
  15. package/dist/esm/llm/bedrock/utils/message_inputs.mjs +20 -9
  16. package/dist/esm/llm/bedrock/utils/message_inputs.mjs.map +1 -1
  17. package/dist/esm/tools/BashExecutor.mjs +1 -1
  18. package/dist/esm/tools/CodeExecutor.mjs +1 -1
  19. package/dist/esm/tools/ProgrammaticToolCalling.mjs +1 -1
  20. package/dist/esm/tools/subagent/SubagentExecutionRegistry.mjs +1 -1
  21. package/dist/esm/tools/subagent/SubagentExecutor.mjs +3 -0
  22. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
  23. package/dist/esm/tools/subagent/diagnostics.mjs +30 -0
  24. package/dist/esm/tools/subagent/diagnostics.mjs.map +1 -0
  25. package/dist/types/llm/bedrock/utils/message_inputs.d.ts +13 -1
  26. package/dist/types/tools/subagent/diagnostics.d.ts +20 -0
  27. package/package.json +3 -3
  28. package/src/llm/bedrock/index.ts +4 -2
  29. package/src/llm/bedrock/utils/message_inputs.ts +62 -15
  30. package/src/tools/subagent/SubagentExecutor.ts +39 -19
  31. package/src/tools/subagent/diagnostics.ts +63 -0
@@ -29,8 +29,8 @@ import type {
29
29
  MessageContentReasoningBlock,
30
30
  } from '../types';
31
31
  import { serializeStructuredValueBounded } from '@/utils/toolContent';
32
- import { HARD_MAX_TOOL_RESULT_CHARS } from '@/utils/truncation';
33
32
  import { isReasoningContentBlock } from '@/messages/reasoningTypes';
33
+ import { HARD_MAX_TOOL_RESULT_CHARS } from '@/utils/truncation';
34
34
 
35
35
  /**
36
36
  * Reasoning blocks from other providers, relative to Bedrock. Bedrock's native
@@ -714,13 +714,16 @@ function coerceBedrockToolUseInput(input: unknown): Record<string, unknown> {
714
714
  : {};
715
715
  }
716
716
 
717
- function convertAIMessageToConverseMessage(msg: BaseMessage): BedrockMessage {
717
+ function convertAIMessageToConverseMessage(
718
+ msg: BaseMessage,
719
+ replayReasoning = true
720
+ ): BedrockMessage {
718
721
  // Check for v1 format from other providers (PR #9766 fix)
719
722
  const responseMetadata = msg.response_metadata as
720
723
  | { output_version?: string }
721
724
  | undefined;
722
725
  if (responseMetadata?.output_version === 'v1') {
723
- return convertFromV1ToChatBedrockConverseMessage(msg);
726
+ return convertFromV1ToChatBedrockConverseMessage(msg, replayReasoning);
724
727
  }
725
728
 
726
729
  const assistantMsg: BedrockMessage = {
@@ -733,9 +736,7 @@ function convertAIMessageToConverseMessage(msg: BaseMessage): BedrockMessage {
733
736
  } else if (Array.isArray(msg.content)) {
734
737
  const parsedToolCallIds = new Set(
735
738
  isAIMessage(msg)
736
- ? (msg.tool_calls ?? []).flatMap((toolCall) =>
737
- toolCall.id != null ? [toolCall.id] : []
738
- )
739
+ ? (msg.tool_calls ?? []).flatMap(({ id }) => id ?? [])
739
740
  : []
740
741
  );
741
742
  const concatenatedBlocks = concatenateLangchainReasoningBlocks(
@@ -775,10 +776,14 @@ function convertAIMessageToConverseMessage(msg: BaseMessage): BedrockMessage {
775
776
  } else if (block.type === 'reasoning_content') {
776
777
  const reasoningBlock = block as MessageContentReasoningBlock;
777
778
  // Bedrock Converse rejects reasoningContent whose reasoningText.text is
778
- // null/empty (a signature-only block that never merged with its text).
779
- // Drop it rather than emit an invalid request; the empty-turn
780
- // placeholder below covers a turn left with no content.
781
- if (!isSerializableBedrockReasoningBlock(reasoningBlock)) {
779
+ // null/empty (a signature-only block that never merged with its text),
780
+ // and OpenAI models reject reasoningContent outright. Drop
781
+ // it rather than emit an invalid request; the empty-turn placeholder
782
+ // below covers a turn left with no content.
783
+ if (
784
+ !replayReasoning ||
785
+ !isSerializableBedrockReasoningBlock(reasoningBlock)
786
+ ) {
782
787
  return;
783
788
  }
784
789
  contentBlocks.push({
@@ -856,7 +861,8 @@ function convertAIMessageToConverseMessage(msg: BaseMessage): BedrockMessage {
856
861
  * (Implements PR #9766 fix for output_version v1 detection)
857
862
  */
858
863
  function convertFromV1ToChatBedrockConverseMessage(
859
- msg: BaseMessage
864
+ msg: BaseMessage,
865
+ replayReasoning = true
860
866
  ): BedrockMessage {
861
867
  const contentBlocks: BedrockContentBlock[] = [];
862
868
  const assistantMsg: BedrockMessage = {
@@ -898,7 +904,11 @@ function convertFromV1ToChatBedrockConverseMessage(
898
904
  /** Bedrock Converse rejects `reasoningText` with a null/empty `text`
899
905
  * (`Member must not be null`), e.g. when the producing model omitted
900
906
  * reasoning text (`thinking.display: "omitted"`) — drop rather than send. */
901
- if (reasoning.reasoning == null || reasoning.reasoning === '') {
907
+ if (
908
+ !replayReasoning ||
909
+ reasoning.reasoning == null ||
910
+ reasoning.reasoning === ''
911
+ ) {
902
912
  droppedUnserializableContent = true;
903
913
  continue;
904
914
  }
@@ -909,7 +919,10 @@ function convertFromV1ToChatBedrockConverseMessage(
909
919
  } as BedrockContentBlock);
910
920
  } else if (block.type === 'reasoning_content') {
911
921
  const reasoningBlock = block as MessageContentReasoningBlock;
912
- if (!isSerializableBedrockReasoningBlock(reasoningBlock)) {
922
+ if (
923
+ !replayReasoning ||
924
+ !isSerializableBedrockReasoningBlock(reasoningBlock)
925
+ ) {
913
926
  droppedUnserializableContent = true;
914
927
  continue;
915
928
  }
@@ -1118,13 +1131,47 @@ function convertToolMessageToConverseMessage(msg: BaseMessage): BedrockMessage {
1118
1131
  };
1119
1132
  }
1120
1133
 
1134
+ /** Options for {@link convertToConverseMessages}. */
1135
+ export interface ConvertToConverseMessagesOptions {
1136
+ /** The model the request is for, which decides whether prior reasoning is replayed. */
1137
+ model?: string;
1138
+ }
1139
+
1140
+ /**
1141
+ * OpenAI Bedrock models reject prior `reasoningContent`, including on Claude-to-GPT handoffs.
1142
+ * Only suppress replay for identified OpenAI targets; other model families retain the existing
1143
+ * behavior because their Bedrock reasoning-replay requirements have not been verified here.
1144
+ * Missing model IDs and opaque resource ARNs also retain the existing behavior.
1145
+ */
1146
+ export function replaysBedrockReasoning(model?: string): boolean {
1147
+ if (model == null || model === '') {
1148
+ return true;
1149
+ }
1150
+ let id = model.toLowerCase();
1151
+ if (id.startsWith('arn:')) {
1152
+ const modelId =
1153
+ /^arn:[^:]+:bedrock:[^:]*:[^:]*:(?:foundation-model|inference-profile)\/(.+)$/.exec(
1154
+ id
1155
+ )?.[1];
1156
+ if (modelId == null) {
1157
+ return true;
1158
+ }
1159
+ id = modelId;
1160
+ }
1161
+ return !/(?:^|\.)openai\./.test(id);
1162
+ }
1163
+
1121
1164
  /**
1122
1165
  * Convert LangChain messages to Bedrock Converse messages.
1123
1166
  */
1124
- export function convertToConverseMessages(messages: BaseMessage[]): {
1167
+ export function convertToConverseMessages(
1168
+ messages: BaseMessage[],
1169
+ options: ConvertToConverseMessagesOptions = {}
1170
+ ): {
1125
1171
  converseMessages: BedrockMessage[];
1126
1172
  converseSystem: BedrockSystemContentBlock[];
1127
1173
  } {
1174
+ const replayReasoning = replaysBedrockReasoning(options.model);
1128
1175
  const converseSystem = messages
1129
1176
  .filter((msg) => msg._getType() === 'system')
1130
1177
  .flatMap((msg) => convertSystemMessageToConverseMessage(msg));
@@ -1133,7 +1180,7 @@ export function convertToConverseMessages(messages: BaseMessage[]): {
1133
1180
  .filter((msg) => msg._getType() !== 'system')
1134
1181
  .map((msg) => {
1135
1182
  if (msg._getType() === 'ai') {
1136
- return convertAIMessageToConverseMessage(msg);
1183
+ return convertAIMessageToConverseMessage(msg, replayReasoning);
1137
1184
  } else if (msg._getType() === 'human' || msg._getType() === 'generic') {
1138
1185
  return convertHumanMessageToConverseMessage(msg);
1139
1186
  } else if (msg._getType() === 'tool') {
@@ -141,6 +141,7 @@ import { stripRunStepResumeState } from '@/tools/runStepResume';
141
141
  import { seedAgentInitialSessions } from '@/utils/toolSessions';
142
142
  import { stableStringify } from '@/tools/eagerEventExecution';
143
143
  import { convertInjectedMessages } from '@/messages/injected';
144
+ import { logSubagentResolutionFailure } from './diagnostics';
144
145
  import { resolveClientOptionsModel } from '@/llm/request';
145
146
  import { isBackgroundDenyMode } from '@/types/hitl';
146
147
  import { composeAbortSignals } from '@/utils/misc';
@@ -1358,10 +1359,7 @@ export class SubagentExecutor {
1358
1359
  };
1359
1360
  let deliveryAttempts = 1;
1360
1361
  let result = await executeAttempt();
1361
- while (
1362
- result.retryableDelivery === true &&
1363
- deliveryAttempts < 3
1364
- ) {
1362
+ while (result.retryableDelivery === true && deliveryAttempts < 3) {
1365
1363
  if (deliveryAttempts > 1) {
1366
1364
  await sleep(
1367
1365
  Math.min(100 * 2 ** Math.min(deliveryAttempts - 2, 6), 5_000)
@@ -2245,7 +2243,10 @@ export class SubagentExecutor {
2245
2243
  parentToolCallId,
2246
2244
  parentConfigurable,
2247
2245
  });
2248
- if (execution.completedResult != null && settled.output.status === 'error') {
2246
+ if (
2247
+ execution.completedResult != null &&
2248
+ settled.output.status === 'error'
2249
+ ) {
2249
2250
  return;
2250
2251
  }
2251
2252
  const { resumeExecution } = execution;
@@ -2492,6 +2493,12 @@ export class SubagentExecutor {
2492
2493
  if (error instanceof StreamLimitExceededError) {
2493
2494
  throw error;
2494
2495
  }
2496
+ logSubagentResolutionFailure(
2497
+ 'identity',
2498
+ executableConfig.type,
2499
+ childSignal,
2500
+ error
2501
+ );
2495
2502
  return createSubagentFailure(SUBAGENT_RESOLUTION_ERROR_MESSAGE);
2496
2503
  }
2497
2504
  const { childRunId, childThreadId, approvalExecutionScope } = identity;
@@ -2522,6 +2529,12 @@ export class SubagentExecutor {
2522
2529
  if (error instanceof StreamLimitExceededError) {
2523
2530
  throw error;
2524
2531
  }
2532
+ logSubagentResolutionFailure(
2533
+ 'config',
2534
+ executableConfig.type,
2535
+ childSignal,
2536
+ error
2537
+ );
2525
2538
  return createSubagentFailure(SUBAGENT_RESOLUTION_ERROR_MESSAGE);
2526
2539
  }
2527
2540
 
@@ -2609,9 +2622,7 @@ export class SubagentExecutor {
2609
2622
  }
2610
2623
  } catch (error) {
2611
2624
  if (childSignal.aborted) {
2612
- throw childSignal.reason instanceof Error
2613
- ? childSignal.reason
2614
- : error;
2625
+ throw childSignal.reason instanceof Error ? childSignal.reason : error;
2615
2626
  }
2616
2627
  if (execution.completedResult != null) {
2617
2628
  return {
@@ -2958,10 +2969,11 @@ export class SubagentExecutor {
2958
2969
  },
2959
2970
  sessionId: currentHookSessionId,
2960
2971
  matchQuery: subagentType,
2961
- }).catch((): AggregatedHookResult =>
2962
- isBackgroundDenyMode(this.humanInTheLoop)
2963
- ? { ...HOOK_FALLBACK, hasHookFailures: true }
2964
- : HOOK_FALLBACK
2972
+ }).catch(
2973
+ (): AggregatedHookResult =>
2974
+ isBackgroundDenyMode(this.humanInTheLoop)
2975
+ ? { ...HOOK_FALLBACK, hasHookFailures: true }
2976
+ : HOOK_FALLBACK
2965
2977
  );
2966
2978
 
2967
2979
  const policyFailed =
@@ -3823,7 +3835,10 @@ export function sanitizeForwardedSubagentUpdateData(
3823
3835
  if (eventName === GraphEvents.ON_TOOL_CALLS_DISPATCHED) {
3824
3836
  if (!isObjectLike(data)) return undefined;
3825
3837
  const event = data as { dispatched_at?: number; toolCalls?: unknown };
3826
- if (!Array.isArray(event.toolCalls) || typeof event.dispatched_at !== 'number') {
3838
+ if (
3839
+ !Array.isArray(event.toolCalls) ||
3840
+ typeof event.dispatched_at !== 'number'
3841
+ ) {
3827
3842
  return undefined;
3828
3843
  }
3829
3844
  return {
@@ -3831,12 +3846,17 @@ export function sanitizeForwardedSubagentUpdateData(
3831
3846
  toolCalls: (event.toolCalls as unknown[]).flatMap((call) => {
3832
3847
  if (!isObjectLike(call)) return [];
3833
3848
  const entry = call as Record<string, unknown>;
3834
- if (typeof entry.id !== 'string' || typeof entry.name !== 'string') return [];
3835
- return [{
3836
- id: entry.id,
3837
- name: entry.name,
3838
- ...(typeof entry.stepId === 'string' ? { stepId: entry.stepId } : {}),
3839
- }];
3849
+ if (typeof entry.id !== 'string' || typeof entry.name !== 'string')
3850
+ return [];
3851
+ return [
3852
+ {
3853
+ id: entry.id,
3854
+ name: entry.name,
3855
+ ...(typeof entry.stepId === 'string'
3856
+ ? { stepId: entry.stepId }
3857
+ : {}),
3858
+ },
3859
+ ];
3840
3860
  }),
3841
3861
  };
3842
3862
  }
@@ -0,0 +1,63 @@
1
+ import {
2
+ SubagentSettlementBindingError,
3
+ SubagentDefinitionBindingError,
4
+ SubagentInvocationBindingError,
5
+ SubagentExecutionInvalidatedError,
6
+ } from './SubagentExecutionRegistry';
7
+ import { describeCodeApiError } from '@/tools/diagnostics';
8
+
9
+ /** Which step of child start-up failed: execution identity, then host config resolution. */
10
+ export type SubagentResolutionPhase = 'identity' | 'config';
11
+
12
+ const SUBAGENT_ERROR_TYPES = [
13
+ ['SubagentExecutionInvalidatedError', SubagentExecutionInvalidatedError],
14
+ ['SubagentDefinitionBindingError', SubagentDefinitionBindingError],
15
+ ['SubagentInvocationBindingError', SubagentInvocationBindingError],
16
+ ['SubagentSettlementBindingError', SubagentSettlementBindingError],
17
+ ] as const;
18
+
19
+ type SubagentErrorLabel =
20
+ | (typeof SUBAGENT_ERROR_TYPES)[number][0]
21
+ | ReturnType<typeof describeCodeApiError>['type'];
22
+
23
+ /**
24
+ * Follows the rule in `@/tools/diagnostics`: every field is a value this
25
+ * module owns or a host-registered subagent type, never text from the error.
26
+ * The failure the model sees is a fixed sentence, so this line is the only
27
+ * operator account of it; the host logs its own error text where it throws.
28
+ */
29
+ export type SubagentResolutionDiagnostic = {
30
+ phase: SubagentResolutionPhase;
31
+ subagentType: string;
32
+ aborted: boolean;
33
+ type: SubagentErrorLabel;
34
+ };
35
+
36
+ function describeSubagentError(error: unknown): SubagentErrorLabel {
37
+ try {
38
+ for (const [label, constructor] of SUBAGENT_ERROR_TYPES) {
39
+ if (error instanceof constructor) {
40
+ return label;
41
+ }
42
+ }
43
+ } catch {
44
+ return 'UndescribableError';
45
+ }
46
+ return describeCodeApiError(error).type;
47
+ }
48
+
49
+ export function logSubagentResolutionFailure(
50
+ phase: SubagentResolutionPhase,
51
+ subagentType: string,
52
+ signal: AbortSignal,
53
+ error: unknown
54
+ ): void {
55
+ const detail: SubagentResolutionDiagnostic = {
56
+ phase,
57
+ subagentType,
58
+ aborted: signal.aborted,
59
+ type: describeSubagentError(error),
60
+ };
61
+ // eslint-disable-next-line no-console
62
+ console.warn('[SubagentExecutor] Subagent resolution failed', detail);
63
+ }