@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.
- package/dist/cjs/llm/bedrock/index.cjs +1 -1
- package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/utils/message_inputs.cjs +20 -9
- package/dist/cjs/llm/bedrock/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/tools/BashExecutor.cjs +1 -1
- package/dist/cjs/tools/CodeExecutor.cjs +1 -1
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs +1 -1
- package/dist/cjs/tools/subagent/SubagentExecutionRegistry.cjs +1 -0
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs +3 -0
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
- package/dist/cjs/tools/subagent/diagnostics.cjs +30 -0
- package/dist/cjs/tools/subagent/diagnostics.cjs.map +1 -0
- package/dist/esm/llm/bedrock/index.mjs +1 -1
- package/dist/esm/llm/bedrock/index.mjs.map +1 -1
- package/dist/esm/llm/bedrock/utils/message_inputs.mjs +20 -9
- package/dist/esm/llm/bedrock/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/tools/BashExecutor.mjs +1 -1
- package/dist/esm/tools/CodeExecutor.mjs +1 -1
- package/dist/esm/tools/ProgrammaticToolCalling.mjs +1 -1
- package/dist/esm/tools/subagent/SubagentExecutionRegistry.mjs +1 -1
- package/dist/esm/tools/subagent/SubagentExecutor.mjs +3 -0
- package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
- package/dist/esm/tools/subagent/diagnostics.mjs +30 -0
- package/dist/esm/tools/subagent/diagnostics.mjs.map +1 -0
- package/dist/types/llm/bedrock/utils/message_inputs.d.ts +13 -1
- package/dist/types/tools/subagent/diagnostics.d.ts +20 -0
- package/package.json +3 -3
- package/src/llm/bedrock/index.ts +4 -2
- package/src/llm/bedrock/utils/message_inputs.ts +62 -15
- package/src/tools/subagent/SubagentExecutor.ts +39 -19
- 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(
|
|
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((
|
|
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
|
-
//
|
|
780
|
-
//
|
|
781
|
-
|
|
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 (
|
|
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 (
|
|
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(
|
|
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 (
|
|
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(
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
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 (
|
|
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')
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
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
|
+
}
|