@librechat/agents 3.3.0 → 3.3.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/agents/AgentContext.cjs +116 -2
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/common/enum.cjs +2 -0
- package/dist/cjs/common/enum.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +229 -48
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/graphs/MultiAgentGraph.cjs +102 -27
- package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/index.cjs +11 -2
- package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
- package/dist/cjs/llm/contextOverflowRecovery.cjs +132 -0
- package/dist/cjs/llm/contextOverflowRecovery.cjs.map +1 -0
- package/dist/cjs/llm/google/index.cjs +1 -1
- package/dist/cjs/llm/invoke.cjs +60 -3
- package/dist/cjs/llm/invoke.cjs.map +1 -1
- package/dist/cjs/main.cjs +20 -7
- package/dist/cjs/messages/prune.cjs +16 -5
- package/dist/cjs/messages/prune.cjs.map +1 -1
- package/dist/cjs/messages/recency.cjs +2 -0
- package/dist/cjs/messages/recency.cjs.map +1 -1
- package/dist/cjs/run.cjs +15 -2
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/stream.cjs +205 -49
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/summarization/node.cjs +55 -7
- package/dist/cjs/summarization/node.cjs.map +1 -1
- package/dist/cjs/tools/BashExecutor.cjs +2 -2
- package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
- package/dist/cjs/tools/BashProgrammaticToolCalling.cjs +2 -2
- package/dist/cjs/tools/BashProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/CodeExecutor.cjs +69 -8
- package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs +19 -15
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +32 -3
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/utils/errors.cjs +317 -52
- package/dist/cjs/utils/errors.cjs.map +1 -1
- package/dist/esm/agents/AgentContext.mjs +117 -3
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/common/enum.mjs +2 -0
- package/dist/esm/common/enum.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +227 -46
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/graphs/MultiAgentGraph.mjs +102 -27
- package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
- package/dist/esm/llm/bedrock/index.mjs +11 -2
- package/dist/esm/llm/bedrock/index.mjs.map +1 -1
- package/dist/esm/llm/contextOverflowRecovery.mjs +130 -0
- package/dist/esm/llm/contextOverflowRecovery.mjs.map +1 -0
- package/dist/esm/llm/google/index.mjs +1 -1
- package/dist/esm/llm/invoke.mjs +59 -4
- package/dist/esm/llm/invoke.mjs.map +1 -1
- package/dist/esm/main.mjs +9 -9
- package/dist/esm/messages/prune.mjs +14 -6
- package/dist/esm/messages/prune.mjs.map +1 -1
- package/dist/esm/messages/recency.mjs +2 -1
- package/dist/esm/messages/recency.mjs.map +1 -1
- package/dist/esm/run.mjs +15 -2
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/stream.mjs +205 -49
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/summarization/node.mjs +55 -7
- package/dist/esm/summarization/node.mjs.map +1 -1
- package/dist/esm/tools/BashExecutor.mjs +3 -3
- package/dist/esm/tools/BashExecutor.mjs.map +1 -1
- package/dist/esm/tools/BashProgrammaticToolCalling.mjs +3 -3
- package/dist/esm/tools/BashProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/CodeExecutor.mjs +62 -9
- package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
- package/dist/esm/tools/ProgrammaticToolCalling.mjs +20 -16
- package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +32 -3
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/utils/errors.mjs +317 -53
- package/dist/esm/utils/errors.mjs.map +1 -1
- package/dist/types/agents/AgentContext.d.ts +62 -3
- package/dist/types/common/enum.d.ts +2 -0
- package/dist/types/graphs/Graph.d.ts +16 -2
- package/dist/types/graphs/MultiAgentGraph.d.ts +1 -0
- package/dist/types/llm/contextOverflowRecovery.d.ts +85 -0
- package/dist/types/llm/invoke.d.ts +35 -6
- package/dist/types/messages/prune.d.ts +10 -2
- package/dist/types/messages/recency.d.ts +1 -0
- package/dist/types/run.d.ts +2 -0
- package/dist/types/tools/CodeExecutor.d.ts +14 -1
- package/dist/types/types/llm.d.ts +7 -4
- package/dist/types/types/stream.d.ts +5 -4
- package/dist/types/types/summarize.d.ts +22 -0
- package/dist/types/utils/__tests__/fixtures/contextOverflowSignatures.d.ts +40 -0
- package/dist/types/utils/errors.d.ts +65 -16
- package/dist/types/utils/redactSecrets.d.ts +3 -0
- package/package.json +7 -8
- package/src/agents/AgentContext.ts +188 -7
- package/src/agents/__tests__/AgentContext.overflow.test.ts +205 -0
- package/src/common/enum.ts +2 -0
- package/src/graphs/Graph.ts +389 -58
- package/src/graphs/MultiAgentGraph.ts +184 -46
- package/src/graphs/__tests__/Graph.contextOverflow.test.ts +631 -0
- package/src/llm/__tests__/contextOverflowRecovery.test.ts +401 -0
- package/src/llm/__tests__/fallbackOverflow.test.ts +287 -0
- package/src/llm/bedrock/index.ts +25 -12
- package/src/llm/contextOverflowRecovery.ts +292 -0
- package/src/llm/invoke.ts +119 -4
- package/src/messages/prune.ts +24 -11
- package/src/messages/recency.ts +3 -1
- package/src/run.ts +24 -1
- package/src/scripts/context-overflow-probe.ts +997 -0
- package/src/specs/agent-handoffs.test.ts +903 -1
- package/src/specs/context-overflow-recovery.live.test.ts +213 -0
- package/src/splitStream.test.ts +882 -0
- package/src/stream.ts +315 -51
- package/src/summarization/__tests__/aggregator.test.ts +83 -0
- package/src/summarization/__tests__/node.test.ts +139 -0
- package/src/summarization/node.ts +99 -14
- package/src/tools/BashExecutor.ts +4 -2
- package/src/tools/BashProgrammaticToolCalling.ts +4 -7
- package/src/tools/CodeExecutor.ts +119 -8
- package/src/tools/ProgrammaticToolCalling.ts +29 -27
- package/src/tools/ToolNode.ts +50 -8
- package/src/tools/__tests__/CodeApiAuthHeaders.test.ts +297 -3
- package/src/types/llm.ts +8 -1
- package/src/types/stream.ts +5 -4
- package/src/types/summarize.ts +22 -0
- package/src/utils/__tests__/errors.test.ts +270 -0
- package/src/utils/__tests__/fixtures/contextOverflowSignatures.ts +336 -0
- package/src/utils/__tests__/redactSecrets.test.ts +56 -0
- package/src/utils/errors.ts +484 -66
- package/src/utils/redactSecrets.ts +61 -0
|
@@ -15,6 +15,10 @@ import {
|
|
|
15
15
|
resolvePromptCacheTtl,
|
|
16
16
|
type PromptCacheTtl,
|
|
17
17
|
} from '@/messages/cache';
|
|
18
|
+
import {
|
|
19
|
+
DEFAULT_RETAIN_RECENT_TURNS,
|
|
20
|
+
splitAtRecencyBoundary,
|
|
21
|
+
} from '@/messages/recency';
|
|
18
22
|
import {
|
|
19
23
|
Constants,
|
|
20
24
|
ContentTypes,
|
|
@@ -25,7 +29,6 @@ import {
|
|
|
25
29
|
import { safeDispatchCustomEvent, emitAgentLog } from '@/utils/events';
|
|
26
30
|
import { attemptInvoke, tryFallbackProviders } from '@/llm/invoke';
|
|
27
31
|
import { createRemoveAllMessage } from '@/messages/reducer';
|
|
28
|
-
import { splitAtRecencyBoundary } from '@/messages/recency';
|
|
29
32
|
import { getMaxOutputTokensKey } from '@/llm/request';
|
|
30
33
|
import { initializeModel } from '@/llm/init';
|
|
31
34
|
import { getChunkContent } from '@/stream';
|
|
@@ -43,8 +46,6 @@ const SUMMARIZATION_PARAM_KEYS = new Set(['maxSummaryTokens']);
|
|
|
43
46
|
* `retainRecent.turns` to `0` reverts to the legacy "summarize every
|
|
44
47
|
* message" behavior.
|
|
45
48
|
*/
|
|
46
|
-
const DEFAULT_RETAIN_RECENT_TURNS = 2;
|
|
47
|
-
|
|
48
49
|
/**
|
|
49
50
|
* Token overhead of the XML wrapper + instruction text added around the
|
|
50
51
|
* summary at injection time in AgentContext.buildSystemRunnable:
|
|
@@ -517,7 +518,16 @@ async function executeSummarizationWithFallback(params: {
|
|
|
517
518
|
stepId: string;
|
|
518
519
|
usePromptCache: boolean;
|
|
519
520
|
log: LogFn;
|
|
520
|
-
}): Promise<{
|
|
521
|
+
}): Promise<{
|
|
522
|
+
text: string;
|
|
523
|
+
usage?: Partial<UsageMetadata>;
|
|
524
|
+
/**
|
|
525
|
+
* True when every model call failed and `text` is the generated metadata
|
|
526
|
+
* stub rather than a real summary. Callers that would replace history with
|
|
527
|
+
* this need to know it carries none of the original content.
|
|
528
|
+
*/
|
|
529
|
+
usedMetadataStub?: boolean;
|
|
530
|
+
}> {
|
|
521
531
|
const {
|
|
522
532
|
agentContext,
|
|
523
533
|
messages,
|
|
@@ -532,6 +542,7 @@ async function executeSummarizationWithFallback(params: {
|
|
|
532
542
|
|
|
533
543
|
let summaryText = '';
|
|
534
544
|
let summaryUsage: Partial<UsageMetadata> | undefined;
|
|
545
|
+
let usedMetadataStub = false;
|
|
535
546
|
|
|
536
547
|
try {
|
|
537
548
|
/**
|
|
@@ -634,10 +645,11 @@ async function executeSummarizationWithFallback(params: {
|
|
|
634
645
|
}
|
|
635
646
|
);
|
|
636
647
|
summaryText = generateMetadataStub(messages);
|
|
648
|
+
usedMetadataStub = true;
|
|
637
649
|
}
|
|
638
650
|
}
|
|
639
651
|
|
|
640
|
-
return { text: summaryText, usage: summaryUsage };
|
|
652
|
+
return { text: summaryText, usage: summaryUsage, usedMetadataStub };
|
|
641
653
|
}
|
|
642
654
|
|
|
643
655
|
/** Dispatches run step completion, ON_SUMMARIZE_COMPLETE, and rebuilds token map. */
|
|
@@ -774,6 +786,34 @@ export function createSummarizeNode({
|
|
|
774
786
|
return { summarizationRequest: undefined };
|
|
775
787
|
}
|
|
776
788
|
|
|
789
|
+
/**
|
|
790
|
+
* Overflow recovery routes through this node purely to get back to the
|
|
791
|
+
* agent node with a corrected budget, and deliberately spends no model
|
|
792
|
+
* call on its first attempt: re-pruning under the raised context pressure
|
|
793
|
+
* drives the pruner's tool-output compression and masking, which is
|
|
794
|
+
* cheaper than a summary and cannot lose message content. Summarization
|
|
795
|
+
* is also skipped outright when the caller never enabled it.
|
|
796
|
+
*/
|
|
797
|
+
if (
|
|
798
|
+
request.reason === 'overflow' &&
|
|
799
|
+
(request.allowSummarization !== true ||
|
|
800
|
+
agentContext.summarizationEnabled !== true)
|
|
801
|
+
) {
|
|
802
|
+
emitAgentLog(
|
|
803
|
+
config,
|
|
804
|
+
'debug',
|
|
805
|
+
'summarize',
|
|
806
|
+
'Overflow recovery re-prune — compressing tool output without a summarization call',
|
|
807
|
+
{
|
|
808
|
+
maxContextTokens: agentContext.maxContextTokens,
|
|
809
|
+
summarizationEnabled: agentContext.summarizationEnabled === true,
|
|
810
|
+
allowSummarization: request.allowSummarization === true,
|
|
811
|
+
},
|
|
812
|
+
{ runId: graph.runId, agentId: request.agentId }
|
|
813
|
+
);
|
|
814
|
+
return { summarizationRequest: undefined };
|
|
815
|
+
}
|
|
816
|
+
|
|
777
817
|
const maxCtx = agentContext.maxContextTokens ?? 0;
|
|
778
818
|
if (maxCtx > 0 && agentContext.instructionTokens >= maxCtx) {
|
|
779
819
|
emitAgentLog(
|
|
@@ -976,16 +1016,61 @@ export function createSummarizeNode({
|
|
|
976
1016
|
}
|
|
977
1017
|
: undefined;
|
|
978
1018
|
|
|
979
|
-
const {
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
1019
|
+
const {
|
|
1020
|
+
text: rawText,
|
|
1021
|
+
usage: summaryUsage,
|
|
1022
|
+
usedMetadataStub,
|
|
1023
|
+
} = await executeSummarizationWithFallback({
|
|
1024
|
+
agentContext,
|
|
1025
|
+
messages: messagesToRefine,
|
|
1026
|
+
clientConfig,
|
|
1027
|
+
summarizeConfig,
|
|
1028
|
+
stepId,
|
|
1029
|
+
usePromptCache: isSelfSummarizeModel && hasPromptCache,
|
|
1030
|
+
log,
|
|
1031
|
+
});
|
|
1032
|
+
|
|
1033
|
+
/**
|
|
1034
|
+
* The metadata stub describes the history rather than summarizing it, so
|
|
1035
|
+
* committing it means removing the head and keeping nothing of what it
|
|
1036
|
+
* said. That trade is never worth making to paper over an overflow: the
|
|
1037
|
+
* recovery would "succeed" only by destroying the conversation it was
|
|
1038
|
+
* supposed to preserve. Leave state untouched and let the provider error
|
|
1039
|
+
* surface instead.
|
|
1040
|
+
*/
|
|
1041
|
+
if (usedMetadataStub === true && request.reason === 'overflow') {
|
|
1042
|
+
log(
|
|
1043
|
+
'warn',
|
|
1044
|
+
'Overflow summarization failed; keeping history rather than replacing it with a metadata stub'
|
|
1045
|
+
);
|
|
1046
|
+
agentContext.markSummarizationTriggered(state.messages.length);
|
|
1047
|
+
/**
|
|
1048
|
+
* The run step was already dispatched, so it has to be resolved here or
|
|
1049
|
+
* consumers tracking step lifecycle keep an unfinished placeholder for
|
|
1050
|
+
* the rest of the run.
|
|
1051
|
+
*/
|
|
1052
|
+
await graph.dispatchRunStepCompleted(
|
|
985
1053
|
stepId,
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
1054
|
+
{
|
|
1055
|
+
type: 'summary',
|
|
1056
|
+
summary: placeholderSummary,
|
|
1057
|
+
} satisfies t.SummaryCompleted,
|
|
1058
|
+
runnableConfig
|
|
1059
|
+
);
|
|
1060
|
+
if (runnableConfig) {
|
|
1061
|
+
await safeDispatchCustomEvent(
|
|
1062
|
+
GraphEvents.ON_SUMMARIZE_COMPLETE,
|
|
1063
|
+
{
|
|
1064
|
+
id: stepId,
|
|
1065
|
+
agentId: request.agentId,
|
|
1066
|
+
error:
|
|
1067
|
+
'Summarization failed during overflow recovery; conversation history was preserved',
|
|
1068
|
+
} satisfies t.SummarizeCompleteEvent,
|
|
1069
|
+
runnableConfig
|
|
1070
|
+
);
|
|
1071
|
+
}
|
|
1072
|
+
return { summarizationRequest: undefined };
|
|
1073
|
+
}
|
|
989
1074
|
|
|
990
1075
|
if (!rawText) {
|
|
991
1076
|
agentContext.markSummarizationTriggered(0);
|
|
@@ -11,7 +11,9 @@ import {
|
|
|
11
11
|
appendCodeSessionFileSummary,
|
|
12
12
|
emptyOutputMessage,
|
|
13
13
|
buildCodeApiHttpErrorMessage,
|
|
14
|
+
CodeApiRequestError,
|
|
14
15
|
getCodeBaseURL,
|
|
16
|
+
normalizeCodeApiRequestError,
|
|
15
17
|
resolveCodeApiAuthHeaders,
|
|
16
18
|
} from './CodeExecutor';
|
|
17
19
|
import { Constants } from '@/common';
|
|
@@ -250,7 +252,7 @@ function createBashExecutionTool(
|
|
|
250
252
|
}
|
|
251
253
|
const response = await fetch(EXEC_ENDPOINT, fetchOptions);
|
|
252
254
|
if (!response.ok) {
|
|
253
|
-
throw new
|
|
255
|
+
throw new CodeApiRequestError(
|
|
254
256
|
await buildCodeApiHttpErrorMessage('POST', EXEC_ENDPOINT, response)
|
|
255
257
|
);
|
|
256
258
|
}
|
|
@@ -291,7 +293,7 @@ function createBashExecutionTool(
|
|
|
291
293
|
];
|
|
292
294
|
} catch (error) {
|
|
293
295
|
const messageWithReminder = appendFailedExecutionFileReminder(
|
|
294
|
-
(error
|
|
296
|
+
normalizeCodeApiRequestError(error).message,
|
|
295
297
|
command
|
|
296
298
|
);
|
|
297
299
|
throw new Error(`Execution error:\n\n${messageWithReminder}`);
|
|
@@ -7,6 +7,8 @@ import {
|
|
|
7
7
|
BASH_SHELL_GUIDANCE,
|
|
8
8
|
CODE_ARTIFACT_PATH_GUIDANCE,
|
|
9
9
|
appendFailedExecutionFileReminder,
|
|
10
|
+
buildCodeApiExecutionErrorMessage,
|
|
11
|
+
CodeApiRequestError,
|
|
10
12
|
getCodeBaseURL,
|
|
11
13
|
} from './CodeExecutor';
|
|
12
14
|
import {
|
|
@@ -434,15 +436,10 @@ export function createBashProgrammaticToolCallingTool(
|
|
|
434
436
|
}
|
|
435
437
|
|
|
436
438
|
if (response.status === 'error') {
|
|
437
|
-
throw new Error(
|
|
438
|
-
`Execution error: ${response.error}` +
|
|
439
|
-
(response.stderr != null && response.stderr !== ''
|
|
440
|
-
? `\n\nStderr:\n${response.stderr}`
|
|
441
|
-
: '')
|
|
442
|
-
);
|
|
439
|
+
throw new Error(buildCodeApiExecutionErrorMessage(response));
|
|
443
440
|
}
|
|
444
441
|
|
|
445
|
-
throw new
|
|
442
|
+
throw new CodeApiRequestError();
|
|
446
443
|
} catch (error) {
|
|
447
444
|
const messageWithReminder = appendFailedExecutionFileReminder(
|
|
448
445
|
(error as Error).message,
|
|
@@ -111,6 +111,102 @@ const EXEC_ENDPOINT = `${baseEndpoint}/exec`;
|
|
|
111
111
|
|
|
112
112
|
type SupportedLanguage = (typeof SUPPORTED_LANGUAGES)[number];
|
|
113
113
|
|
|
114
|
+
const MAX_RETRY_AFTER_SECONDS = 3600;
|
|
115
|
+
|
|
116
|
+
export const CODE_API_UNAVAILABLE_ERROR_MESSAGE =
|
|
117
|
+
'Code execution is temporarily unavailable. Please retry.';
|
|
118
|
+
export const CODE_API_AUTHORIZATION_ERROR_MESSAGE =
|
|
119
|
+
'Code execution is not authorized. Verify access before trying again.';
|
|
120
|
+
export const CODE_API_EXECUTION_FAILED_ERROR_MESSAGE = 'Code execution failed.';
|
|
121
|
+
export const CODE_API_INVALID_REQUEST_ERROR_MESSAGE =
|
|
122
|
+
'The code execution request was rejected. Please check the tool input and try again.';
|
|
123
|
+
export const CODE_API_RATE_LIMITED_ERROR_MESSAGE =
|
|
124
|
+
'Code execution is temporarily rate-limited. Please retry shortly.';
|
|
125
|
+
|
|
126
|
+
const SAFE_CODE_API_EXECUTION_ERROR_DETAILS: Readonly<
|
|
127
|
+
Partial<Record<string, string>>
|
|
128
|
+
> = {
|
|
129
|
+
'Execution failed or timed out': 'Execution failed or timed out.',
|
|
130
|
+
'Out of memory': 'Execution exceeded the memory limit.',
|
|
131
|
+
'Time limit exceeded': 'Execution exceeded the time limit.',
|
|
132
|
+
'sandbox emitted an empty pending tool call block; aborting to avoid a tight retry loop':
|
|
133
|
+
'Generated code emitted an invalid empty tool request.',
|
|
134
|
+
'stderr length exceeded': 'Execution error output exceeded the size limit.',
|
|
135
|
+
'stdout length exceeded': 'Execution output exceeded the size limit.',
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
export class CodeApiRequestError extends Error {
|
|
139
|
+
constructor(message = CODE_API_UNAVAILABLE_ERROR_MESSAGE) {
|
|
140
|
+
super(message);
|
|
141
|
+
this.name = 'CodeApiRequestError';
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function getRetryAfterSeconds(responseBody: string): number | undefined {
|
|
146
|
+
try {
|
|
147
|
+
const parsed = JSON.parse(responseBody) as {
|
|
148
|
+
error?: unknown;
|
|
149
|
+
retry_after_seconds?: unknown;
|
|
150
|
+
};
|
|
151
|
+
if (
|
|
152
|
+
parsed.error !== 'rate_limited' ||
|
|
153
|
+
typeof parsed.retry_after_seconds !== 'number' ||
|
|
154
|
+
!Number.isFinite(parsed.retry_after_seconds) ||
|
|
155
|
+
parsed.retry_after_seconds <= 0
|
|
156
|
+
) {
|
|
157
|
+
return undefined;
|
|
158
|
+
}
|
|
159
|
+
return Math.min(
|
|
160
|
+
Math.ceil(parsed.retry_after_seconds),
|
|
161
|
+
MAX_RETRY_AFTER_SECONDS
|
|
162
|
+
);
|
|
163
|
+
} catch {
|
|
164
|
+
return undefined;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function normalizeCodeApiRequestError(
|
|
169
|
+
error: unknown
|
|
170
|
+
): CodeApiRequestError {
|
|
171
|
+
return error instanceof CodeApiRequestError
|
|
172
|
+
? error
|
|
173
|
+
: new CodeApiRequestError();
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function getSafeCodeApiExecutionErrorDetail(
|
|
177
|
+
error: unknown
|
|
178
|
+
): string | undefined {
|
|
179
|
+
if (typeof error !== 'string') {
|
|
180
|
+
return undefined;
|
|
181
|
+
}
|
|
182
|
+
const exactMatch = SAFE_CODE_API_EXECUTION_ERROR_DETAILS[error];
|
|
183
|
+
if (exactMatch != null) {
|
|
184
|
+
return exactMatch;
|
|
185
|
+
}
|
|
186
|
+
if (/^Sandbox exited with code (?:-?\d{1,4}|unknown)$/.test(error)) {
|
|
187
|
+
return error.replace(/^Sandbox/, 'Execution');
|
|
188
|
+
}
|
|
189
|
+
if (/^Sandbox requested an unregistered tool: .+$/.test(error)) {
|
|
190
|
+
return 'Generated code requested a tool that is not available.';
|
|
191
|
+
}
|
|
192
|
+
return undefined;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function buildCodeApiExecutionErrorMessage(response: {
|
|
196
|
+
error?: unknown;
|
|
197
|
+
stderr?: unknown;
|
|
198
|
+
}): string {
|
|
199
|
+
const safeDetail = getSafeCodeApiExecutionErrorDetail(response.error);
|
|
200
|
+
const message =
|
|
201
|
+
safeDetail != null
|
|
202
|
+
? `${CODE_API_EXECUTION_FAILED_ERROR_MESSAGE} ${safeDetail}`
|
|
203
|
+
: CODE_API_EXECUTION_FAILED_ERROR_MESSAGE;
|
|
204
|
+
if (typeof response.stderr === 'string' && response.stderr !== '') {
|
|
205
|
+
return `${message}\n\nStderr:\n${response.stderr}`;
|
|
206
|
+
}
|
|
207
|
+
return message;
|
|
208
|
+
}
|
|
209
|
+
|
|
114
210
|
export async function resolveCodeApiAuthHeaders(
|
|
115
211
|
authHeaders?: t.CodeApiAuthHeaders
|
|
116
212
|
): Promise<t.CodeApiAuthHeaderMap> {
|
|
@@ -118,14 +214,19 @@ export async function resolveCodeApiAuthHeaders(
|
|
|
118
214
|
return {};
|
|
119
215
|
}
|
|
120
216
|
if (typeof authHeaders === 'function') {
|
|
121
|
-
|
|
217
|
+
try {
|
|
218
|
+
const resolvedHeaders = await authHeaders();
|
|
219
|
+
return resolvedHeaders;
|
|
220
|
+
} catch {
|
|
221
|
+
throw new CodeApiRequestError(CODE_API_AUTHORIZATION_ERROR_MESSAGE);
|
|
222
|
+
}
|
|
122
223
|
}
|
|
123
224
|
return authHeaders;
|
|
124
225
|
}
|
|
125
226
|
|
|
126
227
|
export async function buildCodeApiHttpErrorMessage(
|
|
127
|
-
|
|
128
|
-
|
|
228
|
+
_method: string,
|
|
229
|
+
_endpoint: string,
|
|
129
230
|
response: { status: number; text: () => Promise<string> }
|
|
130
231
|
): Promise<string> {
|
|
131
232
|
let responseBody = '';
|
|
@@ -134,9 +235,19 @@ export async function buildCodeApiHttpErrorMessage(
|
|
|
134
235
|
} catch {
|
|
135
236
|
responseBody = '';
|
|
136
237
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
238
|
+
if (response.status === 429) {
|
|
239
|
+
const retryAfterSeconds = getRetryAfterSeconds(responseBody);
|
|
240
|
+
return retryAfterSeconds != null
|
|
241
|
+
? `Code execution is temporarily rate-limited. Retry after ${retryAfterSeconds} seconds.`
|
|
242
|
+
: CODE_API_RATE_LIMITED_ERROR_MESSAGE;
|
|
243
|
+
}
|
|
244
|
+
if (response.status === 401 || response.status === 403) {
|
|
245
|
+
return CODE_API_AUTHORIZATION_ERROR_MESSAGE;
|
|
246
|
+
}
|
|
247
|
+
if (response.status === 400 || response.status === 422) {
|
|
248
|
+
return CODE_API_INVALID_REQUEST_ERROR_MESSAGE;
|
|
249
|
+
}
|
|
250
|
+
return CODE_API_UNAVAILABLE_ERROR_MESSAGE;
|
|
140
251
|
}
|
|
141
252
|
|
|
142
253
|
export const CodeExecutionToolDescription = `
|
|
@@ -314,7 +425,7 @@ function createCodeExecutionTool(
|
|
|
314
425
|
}
|
|
315
426
|
const response = await fetch(EXEC_ENDPOINT, fetchOptions);
|
|
316
427
|
if (!response.ok) {
|
|
317
|
-
throw new
|
|
428
|
+
throw new CodeApiRequestError(
|
|
318
429
|
await buildCodeApiHttpErrorMessage('POST', EXEC_ENDPOINT, response)
|
|
319
430
|
);
|
|
320
431
|
}
|
|
@@ -358,7 +469,7 @@ function createCodeExecutionTool(
|
|
|
358
469
|
];
|
|
359
470
|
} catch (error) {
|
|
360
471
|
const messageWithReminder = appendFailedExecutionFileReminder(
|
|
361
|
-
(error
|
|
472
|
+
normalizeCodeApiRequestError(error).message,
|
|
362
473
|
code
|
|
363
474
|
);
|
|
364
475
|
throw new Error(`Execution error:\n\n${messageWithReminder}`);
|
|
@@ -10,10 +10,13 @@ import {
|
|
|
10
10
|
CODE_ARTIFACT_PATH_GUIDANCE,
|
|
11
11
|
appendCodeSessionFileSummary,
|
|
12
12
|
appendFailedExecutionFileReminder,
|
|
13
|
+
buildCodeApiExecutionErrorMessage,
|
|
13
14
|
buildCodeApiHttpErrorMessage,
|
|
15
|
+
CodeApiRequestError,
|
|
14
16
|
emptyOutputMessage,
|
|
15
17
|
getCodeBaseURL,
|
|
16
18
|
appendTmpScratchReminder,
|
|
19
|
+
normalizeCodeApiRequestError,
|
|
17
20
|
resolveCodeApiAuthHeaders,
|
|
18
21
|
} from './CodeExecutor';
|
|
19
22
|
import {
|
|
@@ -474,30 +477,34 @@ export async function makeRequest(
|
|
|
474
477
|
proxy?: string,
|
|
475
478
|
authHeaders?: t.CodeApiAuthHeaders
|
|
476
479
|
): Promise<t.ProgrammaticExecutionResponse> {
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
480
|
+
try {
|
|
481
|
+
const resolvedAuthHeaders = await resolveCodeApiAuthHeaders(authHeaders);
|
|
482
|
+
const fetchOptions: RequestInit = {
|
|
483
|
+
method: 'POST',
|
|
484
|
+
headers: {
|
|
485
|
+
'Content-Type': 'application/json',
|
|
486
|
+
'User-Agent': 'LibreChat/1.0',
|
|
487
|
+
...resolvedAuthHeaders,
|
|
488
|
+
},
|
|
489
|
+
body: JSON.stringify(body),
|
|
490
|
+
};
|
|
487
491
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
492
|
+
if (proxy != null && proxy !== '') {
|
|
493
|
+
fetchOptions.agent = new HttpsProxyAgent(proxy);
|
|
494
|
+
}
|
|
491
495
|
|
|
492
|
-
|
|
496
|
+
const response = await fetch(endpoint, fetchOptions);
|
|
493
497
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
498
|
+
if (!response.ok) {
|
|
499
|
+
throw new CodeApiRequestError(
|
|
500
|
+
await buildCodeApiHttpErrorMessage('POST', endpoint, response)
|
|
501
|
+
);
|
|
502
|
+
}
|
|
499
503
|
|
|
500
|
-
|
|
504
|
+
return (await response.json()) as t.ProgrammaticExecutionResponse;
|
|
505
|
+
} catch (error) {
|
|
506
|
+
throw normalizeCodeApiRequestError(error);
|
|
507
|
+
}
|
|
501
508
|
}
|
|
502
509
|
|
|
503
510
|
/**
|
|
@@ -1014,15 +1021,10 @@ export function createProgrammaticToolCallingTool(
|
|
|
1014
1021
|
}
|
|
1015
1022
|
|
|
1016
1023
|
if (response.status === 'error') {
|
|
1017
|
-
throw new Error(
|
|
1018
|
-
`Execution error: ${response.error}` +
|
|
1019
|
-
(response.stderr != null && response.stderr !== ''
|
|
1020
|
-
? `\n\nStderr:\n${response.stderr}`
|
|
1021
|
-
: '')
|
|
1022
|
-
);
|
|
1024
|
+
throw new Error(buildCodeApiExecutionErrorMessage(response));
|
|
1023
1025
|
}
|
|
1024
1026
|
|
|
1025
|
-
throw new
|
|
1027
|
+
throw new CodeApiRequestError();
|
|
1026
1028
|
} catch (error) {
|
|
1027
1029
|
const messageWithReminder = appendFailedExecutionFileReminder(
|
|
1028
1030
|
(error as Error).message,
|
package/src/tools/ToolNode.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { nanoid } from 'nanoid';
|
|
1
2
|
import { ToolCall } from '@langchain/core/messages/tool';
|
|
2
3
|
import { AsyncLocalStorageProviderSingleton } from '@langchain/core/singletons';
|
|
3
4
|
import {
|
|
@@ -119,6 +120,9 @@ type RunToolBatchContext<T = unknown> = {
|
|
|
119
120
|
};
|
|
120
121
|
|
|
121
122
|
const TOOL_NODE_RUN_NAME = 'tool_batch';
|
|
123
|
+
const NANOID_URL_ALPHABET =
|
|
124
|
+
'_-0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
|
125
|
+
const RUNTIME_HANDOFF_GROUP_OFFSET = 2 ** 48;
|
|
122
126
|
|
|
123
127
|
/**
|
|
124
128
|
* Per-batch context for `dispatchToolEvents` / `executeViaEvent`.
|
|
@@ -158,6 +162,41 @@ function isHandoffToolName(name: string): boolean {
|
|
|
158
162
|
return name.startsWith(Constants.LC_TRANSFER_TO_);
|
|
159
163
|
}
|
|
160
164
|
|
|
165
|
+
/**
|
|
166
|
+
* Encodes 48 random bits from the persisted batch key into a safe integer.
|
|
167
|
+
* The high offset keeps runtime groups disjoint from low, structural group IDs.
|
|
168
|
+
*/
|
|
169
|
+
function getRuntimeHandoffGroupId(batch: string): number {
|
|
170
|
+
let value = 0;
|
|
171
|
+
for (const char of batch.slice(0, 8)) {
|
|
172
|
+
const digit = NANOID_URL_ALPHABET.indexOf(char);
|
|
173
|
+
value = value * 64 + Math.max(digit, 0);
|
|
174
|
+
}
|
|
175
|
+
return RUNTIME_HANDOFF_GROUP_OFFSET + value;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function findHandoffMessage(
|
|
179
|
+
messages: BaseMessage[],
|
|
180
|
+
destination: string
|
|
181
|
+
): ToolMessage | undefined {
|
|
182
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
183
|
+
const message = messages[i];
|
|
184
|
+
if (message.getType() !== 'tool') {
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
const toolMessage = message as ToolMessage;
|
|
188
|
+
const isStandardHandoff =
|
|
189
|
+
toolMessage.name === `${Constants.LC_TRANSFER_TO_}${destination}`;
|
|
190
|
+
const isConditionalHandoff =
|
|
191
|
+
toolMessage.name === 'conditional_transfer' &&
|
|
192
|
+
toolMessage.additional_kwargs.handoff_destination === destination;
|
|
193
|
+
if (isStandardHandoff || isConditionalHandoff) {
|
|
194
|
+
return toolMessage;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return undefined;
|
|
198
|
+
}
|
|
199
|
+
|
|
161
200
|
/**
|
|
162
201
|
* Format a fail-closed diagnostic for malformed approval-decision
|
|
163
202
|
* fields. Hosts deserialize resume payloads from untyped JSON, so
|
|
@@ -3854,21 +3893,24 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
3854
3893
|
const goto = cmd.goto;
|
|
3855
3894
|
return typeof goto === 'string' ? goto : (goto as string[])[0];
|
|
3856
3895
|
});
|
|
3896
|
+
const parallelBatch = nanoid();
|
|
3897
|
+
const parallelGroupId = getRuntimeHandoffGroupId(parallelBatch);
|
|
3857
3898
|
|
|
3858
3899
|
const sends = handoffCommands.map((cmd, idx) => {
|
|
3859
3900
|
const destination = allDestinations[idx];
|
|
3860
3901
|
/** Get siblings (other destinations, not this one) */
|
|
3861
3902
|
const siblings = allDestinations.filter((d) => d !== destination);
|
|
3862
3903
|
|
|
3863
|
-
/** Add siblings to ToolMessage additional_kwargs */
|
|
3864
3904
|
const update = cmd.update as { messages?: BaseMessage[] } | undefined;
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3905
|
+
const handoffMessage = update?.messages
|
|
3906
|
+
? findHandoffMessage(update.messages, destination)
|
|
3907
|
+
: undefined;
|
|
3908
|
+
if (handoffMessage) {
|
|
3909
|
+
handoffMessage.additional_kwargs.handoff_parallel_siblings = siblings;
|
|
3910
|
+
handoffMessage.additional_kwargs[Constants.HANDOFF_PARALLEL_BATCH] =
|
|
3911
|
+
parallelBatch;
|
|
3912
|
+
handoffMessage.additional_kwargs[Constants.HANDOFF_GROUP_ID] =
|
|
3913
|
+
parallelGroupId;
|
|
3872
3914
|
}
|
|
3873
3915
|
|
|
3874
3916
|
return new Send(destination, cmd.update);
|