@librechat/agents 3.7.20 → 3.7.22
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/graphs/Graph.cjs +21 -4
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/llm/invoke.cjs +21 -6
- package/dist/cjs/llm/invoke.cjs.map +1 -1
- package/dist/cjs/main.cjs +2 -0
- package/dist/cjs/stream.cjs +38 -1
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/tools/BashExecutor.cjs +3 -2
- package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
- package/dist/cjs/tools/BashProgrammaticToolCalling.cjs +2 -1
- package/dist/cjs/tools/BashProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/CodeExecutor.cjs +27 -10
- package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs +6 -5
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +95 -18
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/ToolSearch.cjs +130 -24
- package/dist/cjs/tools/ToolSearch.cjs.map +1 -1
- package/dist/cjs/tools/diagnostics.cjs +26 -0
- package/dist/cjs/tools/diagnostics.cjs.map +1 -0
- package/dist/cjs/tools/local/resolveLocalExecutionTools.cjs +1 -1
- package/dist/cjs/tools/preparedSubagents.cjs +129 -0
- package/dist/cjs/tools/preparedSubagents.cjs.map +1 -0
- package/dist/cjs/tools/search/rerankers.cjs +6 -4
- package/dist/cjs/tools/search/rerankers.cjs.map +1 -1
- package/dist/cjs/tools/search/search.cjs +14 -2
- package/dist/cjs/tools/search/search.cjs.map +1 -1
- package/dist/cjs/tools/search/tool.cjs +1 -1
- package/dist/esm/graphs/Graph.mjs +21 -4
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/llm/invoke.mjs +21 -6
- package/dist/esm/llm/invoke.mjs.map +1 -1
- package/dist/esm/main.mjs +2 -2
- package/dist/esm/stream.mjs +39 -2
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/tools/BashExecutor.mjs +3 -2
- package/dist/esm/tools/BashExecutor.mjs.map +1 -1
- package/dist/esm/tools/BashProgrammaticToolCalling.mjs +2 -1
- package/dist/esm/tools/BashProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/CodeExecutor.mjs +27 -10
- package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
- package/dist/esm/tools/ProgrammaticToolCalling.mjs +6 -5
- package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +95 -18
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/ToolSearch.mjs +129 -25
- package/dist/esm/tools/ToolSearch.mjs.map +1 -1
- package/dist/esm/tools/diagnostics.mjs +25 -0
- package/dist/esm/tools/diagnostics.mjs.map +1 -0
- package/dist/esm/tools/local/resolveLocalExecutionTools.mjs +1 -1
- package/dist/esm/tools/preparedSubagents.mjs +128 -0
- package/dist/esm/tools/preparedSubagents.mjs.map +1 -0
- package/dist/esm/tools/search/rerankers.mjs +6 -5
- package/dist/esm/tools/search/rerankers.mjs.map +1 -1
- package/dist/esm/tools/search/search.mjs +14 -2
- package/dist/esm/tools/search/search.mjs.map +1 -1
- package/dist/esm/tools/search/tool.mjs +1 -1
- package/dist/types/graphs/Graph.d.ts +6 -1
- package/dist/types/tools/CodeExecutor.d.ts +13 -2
- package/dist/types/tools/ToolNode.d.ts +13 -1
- package/dist/types/tools/ToolSearch.d.ts +46 -2
- package/dist/types/tools/diagnostics.d.ts +52 -0
- package/dist/types/tools/preparedSubagents.d.ts +26 -0
- package/dist/types/tools/search/rerankers.d.ts +10 -0
- package/dist/types/types/tools.d.ts +17 -0
- package/package.json +3 -2
- package/src/graphs/Graph.ts +63 -5
- package/src/llm/invoke.ts +35 -8
- package/src/stream.ts +70 -1
- package/src/tools/BashExecutor.ts +9 -4
- package/src/tools/BashProgrammaticToolCalling.ts +6 -3
- package/src/tools/CodeExecutor.ts +88 -15
- package/src/tools/ProgrammaticToolCalling.ts +24 -12
- package/src/tools/ToolNode.ts +189 -43
- package/src/tools/ToolSearch.ts +278 -28
- package/src/tools/diagnostics.ts +107 -0
- package/src/tools/preparedSubagents.ts +228 -0
- package/src/tools/search/rerankers.ts +18 -3
- package/src/tools/search/search.ts +30 -5
- package/src/types/tools.ts +17 -0
package/src/llm/invoke.ts
CHANGED
|
@@ -51,6 +51,7 @@ import {
|
|
|
51
51
|
} from '@/llm/providers';
|
|
52
52
|
import { ChatModelStreamHandler, dispatchesChatModelStream } from '@/stream';
|
|
53
53
|
import { Constants, ContentTypes, GraphEvents, Providers } from '@/common';
|
|
54
|
+
import { PreparedSubagentError } from '@/tools/preparedSubagents';
|
|
54
55
|
import { assertNotTruncatedToolCall } from '@/llm/truncation';
|
|
55
56
|
import { resolveClientOptionsModel } from '@/llm/request';
|
|
56
57
|
import { safeDispatchCustomEvent } from '@/utils/events';
|
|
@@ -702,9 +703,8 @@ export async function attemptInvoke(
|
|
|
702
703
|
},
|
|
703
704
|
};
|
|
704
705
|
const request = resolveAttemptRequest(params, providerStampedConfig);
|
|
705
|
-
const configuredModel =
|
|
706
|
-
Constants.INVOKED_MODEL
|
|
707
|
-
];
|
|
706
|
+
const configuredModel =
|
|
707
|
+
providerStampedConfig.metadata?.[Constants.INVOKED_MODEL];
|
|
708
708
|
const modelId =
|
|
709
709
|
request.modelId ??
|
|
710
710
|
(typeof configuredModel === 'string' ? configuredModel : undefined);
|
|
@@ -745,8 +745,14 @@ export async function attemptInvoke(
|
|
|
745
745
|
if (leaseTarget != null && generationKey != null) {
|
|
746
746
|
registerActiveStreamLimitGeneration(leaseTarget, generationKey);
|
|
747
747
|
}
|
|
748
|
+
const prepared =
|
|
749
|
+
params.context?.eagerEventToolExecution?.enabled === true
|
|
750
|
+
? params.context.preparedSubagents
|
|
751
|
+
: undefined;
|
|
752
|
+
const preparedAttempt = resolveGenerationKey(stampedConfig.metadata);
|
|
753
|
+
prepared?.begin(preparedAttempt, stampedConfig);
|
|
748
754
|
try {
|
|
749
|
-
|
|
755
|
+
const result = await attemptInvokeBody(
|
|
750
756
|
{
|
|
751
757
|
request,
|
|
752
758
|
context: params.context,
|
|
@@ -755,7 +761,27 @@ export async function attemptInvoke(
|
|
|
755
761
|
},
|
|
756
762
|
stampedConfig
|
|
757
763
|
);
|
|
764
|
+
const calls =
|
|
765
|
+
result.messages?.flatMap((message) =>
|
|
766
|
+
'tool_calls' in message
|
|
767
|
+
? ((message.tool_calls as ToolCall[] | undefined) ?? [])
|
|
768
|
+
: []
|
|
769
|
+
) ?? [];
|
|
770
|
+
prepared?.finish(preparedAttempt, calls);
|
|
771
|
+
return result;
|
|
772
|
+
} catch (error) {
|
|
773
|
+
prepared?.finish(preparedAttempt, undefined, error);
|
|
774
|
+
throw error;
|
|
758
775
|
} finally {
|
|
776
|
+
const chunks = params.context?.eagerEventToolCallChunks;
|
|
777
|
+
if (prepared != null && chunks != null) {
|
|
778
|
+
const prefix = `prepared:${preparedAttempt}\u0000`;
|
|
779
|
+
for (const key of chunks.keys()) {
|
|
780
|
+
if (key.startsWith(prefix)) {
|
|
781
|
+
chunks.delete(key);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
}
|
|
759
785
|
if (leaseTarget != null && generationKey != null) {
|
|
760
786
|
releaseStreamLimitGeneration(leaseTarget, generationKey);
|
|
761
787
|
}
|
|
@@ -1053,7 +1079,7 @@ async function attemptInvokeBody(
|
|
|
1053
1079
|
const signal = config.signal;
|
|
1054
1080
|
if (
|
|
1055
1081
|
signal?.aborted === true &&
|
|
1056
|
-
signal.reason instanceof StreamLimitExceededError
|
|
1082
|
+
(signal.reason instanceof StreamLimitExceededError || signal.reason instanceof PreparedSubagentError)
|
|
1057
1083
|
) {
|
|
1058
1084
|
throw signal.reason;
|
|
1059
1085
|
}
|
|
@@ -1290,6 +1316,7 @@ async function attemptInvokeBody(
|
|
|
1290
1316
|
const ownAbort =
|
|
1291
1317
|
restartRoute === 'aborted' &&
|
|
1292
1318
|
!(error instanceof StreamLimitExceededError) &&
|
|
1319
|
+
!(error instanceof PreparedSubagentError) &&
|
|
1293
1320
|
config.signal?.aborted !== true;
|
|
1294
1321
|
if (!ownAbort) {
|
|
1295
1322
|
throw error;
|
|
@@ -1639,7 +1666,7 @@ export async function tryFallbackProviders({
|
|
|
1639
1666
|
* a run that must reject. Check before every fallback invocation. */
|
|
1640
1667
|
if (
|
|
1641
1668
|
config?.signal?.aborted === true &&
|
|
1642
|
-
config.signal.reason instanceof StreamLimitExceededError
|
|
1669
|
+
(config.signal.reason instanceof StreamLimitExceededError || config.signal.reason instanceof PreparedSubagentError)
|
|
1643
1670
|
) {
|
|
1644
1671
|
throw config.signal.reason;
|
|
1645
1672
|
}
|
|
@@ -1673,7 +1700,7 @@ export async function tryFallbackProviders({
|
|
|
1673
1700
|
* provider failure. Continuing would try the remaining fallbacks and a
|
|
1674
1701
|
* succeeding one would resolve a run that must reject.
|
|
1675
1702
|
*/
|
|
1676
|
-
if (e instanceof StreamLimitExceededError) {
|
|
1703
|
+
if (e instanceof StreamLimitExceededError || e instanceof PreparedSubagentError) {
|
|
1677
1704
|
throw e;
|
|
1678
1705
|
}
|
|
1679
1706
|
/** A parallel sibling's trip aborts this branch's composed signal, and
|
|
@@ -1682,7 +1709,7 @@ export async function tryFallbackProviders({
|
|
|
1682
1709
|
* abort. Rethrow the breaker's own reason instead. */
|
|
1683
1710
|
if (
|
|
1684
1711
|
config?.signal?.aborted === true &&
|
|
1685
|
-
config.signal.reason instanceof StreamLimitExceededError
|
|
1712
|
+
(config.signal.reason instanceof StreamLimitExceededError || config.signal.reason instanceof PreparedSubagentError)
|
|
1686
1713
|
) {
|
|
1687
1714
|
throw config.signal.reason;
|
|
1688
1715
|
}
|
package/src/stream.ts
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
requiresStreamLimitAccounting,
|
|
16
16
|
StreamLimitExceededError,
|
|
17
17
|
STREAM_LIMIT_EPOCH_KEY,
|
|
18
|
+
resolveGenerationKey,
|
|
18
19
|
} from '@/llm/streamLimits';
|
|
19
20
|
import {
|
|
20
21
|
getStreamedToolCallSeal,
|
|
@@ -56,6 +57,7 @@ import {
|
|
|
56
57
|
} from '@/utils/truncation';
|
|
57
58
|
import { resolveToolOutcome, outcomeFieldsFromResult } from '@/tools/intentArg';
|
|
58
59
|
import { TOOL_OUTPUT_REF_PATTERN } from '@/tools/toolOutputReferences';
|
|
60
|
+
import { PreparedSubagentError } from '@/tools/preparedSubagents';
|
|
59
61
|
import { isReasoningContentBlock } from '@/messages/core';
|
|
60
62
|
import { safeDispatchCustomEvent } from '@/utils/events';
|
|
61
63
|
import { composeAbortSignals } from '@/utils/misc';
|
|
@@ -1319,6 +1321,65 @@ function startReadyStreamedEagerToolExecutions(args: {
|
|
|
1319
1321
|
});
|
|
1320
1322
|
}
|
|
1321
1323
|
|
|
1324
|
+
/** Shares provider sealing/parsing with event tools, but never relaxes their batch guard. */
|
|
1325
|
+
function startPreparedSubagents(
|
|
1326
|
+
graph: StandardGraph,
|
|
1327
|
+
agentContext: AgentContext | undefined,
|
|
1328
|
+
chunk: Partial<AIMessageChunk>,
|
|
1329
|
+
metadata?: Record<string, unknown>
|
|
1330
|
+
): void {
|
|
1331
|
+
const attempt = resolveGenerationKey(metadata);
|
|
1332
|
+
if (
|
|
1333
|
+
(graph as Partial<StandardGraph>).canPrestartSubagents?.(agentContext) !==
|
|
1334
|
+
true ||
|
|
1335
|
+
graph.preparedSubagents.isOpen(attempt) !== true ||
|
|
1336
|
+
graph.hookRegistry?.hasResultAlteringHooks(
|
|
1337
|
+
graph.preparedSubagents.getConfig(attempt)?.configurable?.run_id
|
|
1338
|
+
) === true
|
|
1339
|
+
) {
|
|
1340
|
+
return;
|
|
1341
|
+
}
|
|
1342
|
+
let calls: ToolCall[];
|
|
1343
|
+
if (hasOnArrivalToolCallSeal(chunk) && (chunk.tool_calls?.length ?? 0) > 0) {
|
|
1344
|
+
calls = chunk.tool_calls!;
|
|
1345
|
+
} else {
|
|
1346
|
+
const seal = getStreamedToolCallSeal(chunk.response_metadata);
|
|
1347
|
+
const allowSequentialSeal =
|
|
1348
|
+
canPrestartSequentialStreamedToolChunks(agentContext) ||
|
|
1349
|
+
streamedToolCallAdapterAllowsSequentialSeal(chunk.response_metadata);
|
|
1350
|
+
if (!hasExplicitStreamedToolCallSeals(chunk) && !allowSequentialSeal) {
|
|
1351
|
+
return;
|
|
1352
|
+
}
|
|
1353
|
+
const stepKey = `prepared:${attempt}`;
|
|
1354
|
+
recordEagerToolCallChunks({
|
|
1355
|
+
graph,
|
|
1356
|
+
stepKey,
|
|
1357
|
+
toolCallChunks: chunk.tool_call_chunks ?? [],
|
|
1358
|
+
seal,
|
|
1359
|
+
});
|
|
1360
|
+
calls = getStreamedReadyToolCalls({
|
|
1361
|
+
graph,
|
|
1362
|
+
stepKey,
|
|
1363
|
+
toolCallChunks: chunk.tool_call_chunks,
|
|
1364
|
+
seal,
|
|
1365
|
+
allowSequentialSeal,
|
|
1366
|
+
});
|
|
1367
|
+
pruneEagerToolCallChunkStates({
|
|
1368
|
+
graph,
|
|
1369
|
+
stepKey,
|
|
1370
|
+
toolCallIds: new Set(calls.map((call) => call.id!)),
|
|
1371
|
+
});
|
|
1372
|
+
}
|
|
1373
|
+
for (const call of calls) {
|
|
1374
|
+
if (
|
|
1375
|
+
call.name === Constants.SUBAGENT &&
|
|
1376
|
+
!hasToolOutputReference(call.args)
|
|
1377
|
+
) {
|
|
1378
|
+
graph.prestartSubagent(call, attempt, agentContext);
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1322
1383
|
export function getChunkContent({
|
|
1323
1384
|
chunk,
|
|
1324
1385
|
provider,
|
|
@@ -1597,7 +1658,7 @@ export class ChatModelStreamHandler implements t.EventHandler {
|
|
|
1597
1658
|
if (
|
|
1598
1659
|
eventBreaker != null &&
|
|
1599
1660
|
eventBreaker.signal.aborted &&
|
|
1600
|
-
eventBreaker.signal.reason instanceof StreamLimitExceededError
|
|
1661
|
+
(eventBreaker.signal.reason instanceof StreamLimitExceededError || eventBreaker.signal.reason instanceof PreparedSubagentError)
|
|
1601
1662
|
) {
|
|
1602
1663
|
throw eventBreaker.signal.reason;
|
|
1603
1664
|
}
|
|
@@ -1743,6 +1804,9 @@ export class ChatModelStreamHandler implements t.EventHandler {
|
|
|
1743
1804
|
return;
|
|
1744
1805
|
}
|
|
1745
1806
|
throwIfRunBreakerTripped();
|
|
1807
|
+
if (hasOnArrivalToolCallSeal(chunk)) {
|
|
1808
|
+
startPreparedSubagents(graph, agentContext, chunk, metadata);
|
|
1809
|
+
}
|
|
1746
1810
|
if (hasFinalToolCallSignal(chunk)) {
|
|
1747
1811
|
startEagerToolExecutions({
|
|
1748
1812
|
graph,
|
|
@@ -1839,6 +1903,11 @@ export class ChatModelStreamHandler implements t.EventHandler {
|
|
|
1839
1903
|
}
|
|
1840
1904
|
}
|
|
1841
1905
|
|
|
1906
|
+
if (!hasOnArrivalToolCallSeal(chunk) && !runScopeInvalidated()) {
|
|
1907
|
+
throwIfRunBreakerTripped();
|
|
1908
|
+
startPreparedSubagents(graph, agentContext, chunk, metadata);
|
|
1909
|
+
}
|
|
1910
|
+
|
|
1842
1911
|
if (isEmptyContent) {
|
|
1843
1912
|
return;
|
|
1844
1913
|
}
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
resolveCodeApiAuthHeaders,
|
|
19
19
|
selectRuntimeSessionHint,
|
|
20
20
|
} from './CodeExecutor';
|
|
21
|
+
import { logCodeApiDiagnostic } from '@/tools/diagnostics';
|
|
21
22
|
import { resolveFetchProxyAgent } from '@/utils/proxy';
|
|
22
23
|
import { INTENT_PROPERTY } from '@/tools/intentArg';
|
|
23
24
|
import { Constants } from '@/common';
|
|
@@ -249,9 +250,11 @@ function createBashExecutionTool(
|
|
|
249
250
|
session_id.length > 0 &&
|
|
250
251
|
!Array.isArray(postData.files)
|
|
251
252
|
) {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
253
|
+
logCodeApiDiagnostic(
|
|
254
|
+
'BashExecutor',
|
|
255
|
+
'debug',
|
|
256
|
+
'session carried no injected files; exec will run without input files',
|
|
257
|
+
{ files: 'none' }
|
|
255
258
|
);
|
|
256
259
|
}
|
|
257
260
|
|
|
@@ -278,7 +281,9 @@ function createBashExecutionTool(
|
|
|
278
281
|
const response = await fetch(execEndpoint, fetchOptions);
|
|
279
282
|
if (!response.ok) {
|
|
280
283
|
throw new CodeApiRequestError(
|
|
281
|
-
await buildCodeApiHttpErrorMessage('POST', execEndpoint, response
|
|
284
|
+
await buildCodeApiHttpErrorMessage('POST', execEndpoint, response, {
|
|
285
|
+
profile: executionProfile,
|
|
286
|
+
})
|
|
282
287
|
);
|
|
283
288
|
}
|
|
284
289
|
|
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
runPlainExecution,
|
|
32
32
|
formatCompletedResponse,
|
|
33
33
|
} from './ProgrammaticToolCalling';
|
|
34
|
+
import { logCodeApiDiagnostic } from '@/tools/diagnostics';
|
|
34
35
|
import { INTENT_PROPERTY } from '@/tools/intentArg';
|
|
35
36
|
import { Constants } from '@/common';
|
|
36
37
|
|
|
@@ -423,9 +424,11 @@ export function createBashProgrammaticToolCallingTool(
|
|
|
423
424
|
if (_injected_files && _injected_files.length > 0) {
|
|
424
425
|
files = _injected_files;
|
|
425
426
|
} else if (session_id != null && session_id.length > 0) {
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
427
|
+
logCodeApiDiagnostic(
|
|
428
|
+
'BashProgrammaticToolCalling',
|
|
429
|
+
'debug',
|
|
430
|
+
'session carried no injected files; exec will run without input files',
|
|
431
|
+
{ files: 'none' }
|
|
429
432
|
);
|
|
430
433
|
}
|
|
431
434
|
|
|
@@ -2,7 +2,13 @@ import { config } from 'dotenv';
|
|
|
2
2
|
import fetch, { RequestInit } from 'node-fetch';
|
|
3
3
|
import { getEnvironmentVariable } from '@langchain/core/utils/env';
|
|
4
4
|
import { tool, DynamicStructuredTool } from '@langchain/core/tools';
|
|
5
|
+
import type { Readable } from 'node:stream';
|
|
6
|
+
import type { CodeApiMethod } from '@/tools/diagnostics';
|
|
5
7
|
import type * as t from '@/types';
|
|
8
|
+
import {
|
|
9
|
+
describeCodeApiError,
|
|
10
|
+
logCodeApiDiagnostic,
|
|
11
|
+
} from '@/tools/diagnostics';
|
|
6
12
|
import { appendCodeSessionFileSummary } from '@/tools/CodeSessionFileSummary';
|
|
7
13
|
import { resolveFetchProxyAgent } from '@/utils/proxy';
|
|
8
14
|
import { INTENT_PROPERTY } from '@/tools/intentArg';
|
|
@@ -119,8 +125,7 @@ export const CodeExecutionToolSchema = {
|
|
|
119
125
|
required: ['lang', 'code'],
|
|
120
126
|
} as const;
|
|
121
127
|
|
|
122
|
-
export const CODE_API_EXPECTED_PROFILE_HEADER =
|
|
123
|
-
'X-CodeAPI-Expected-Profile';
|
|
128
|
+
export const CODE_API_EXPECTED_PROFILE_HEADER = 'X-CodeAPI-Expected-Profile';
|
|
124
129
|
|
|
125
130
|
type SupportedLanguage = (typeof SUPPORTED_LANGUAGES)[number];
|
|
126
131
|
|
|
@@ -221,7 +226,8 @@ export function buildCodeApiExecutionErrorMessage(response: {
|
|
|
221
226
|
}
|
|
222
227
|
|
|
223
228
|
export async function resolveCodeApiAuthHeaders(
|
|
224
|
-
authHeaders?: t.CodeApiAuthHeaders
|
|
229
|
+
authHeaders?: t.CodeApiAuthHeaders,
|
|
230
|
+
options?: { recoverable?: boolean }
|
|
225
231
|
): Promise<t.CodeApiAuthHeaderMap> {
|
|
226
232
|
if (authHeaders == null) {
|
|
227
233
|
return {};
|
|
@@ -230,7 +236,15 @@ export async function resolveCodeApiAuthHeaders(
|
|
|
230
236
|
try {
|
|
231
237
|
const resolvedHeaders = await authHeaders();
|
|
232
238
|
return resolvedHeaders;
|
|
233
|
-
} catch {
|
|
239
|
+
} catch (error) {
|
|
240
|
+
if (options?.recoverable !== true) {
|
|
241
|
+
logCodeApiDiagnostic(
|
|
242
|
+
'CodeExecutor',
|
|
243
|
+
'error',
|
|
244
|
+
'auth header resolution failed; the Code API request was never sent',
|
|
245
|
+
describeCodeApiError(error)
|
|
246
|
+
);
|
|
247
|
+
}
|
|
234
248
|
throw new CodeApiRequestError(CODE_API_AUTHORIZATION_ERROR_MESSAGE);
|
|
235
249
|
}
|
|
236
250
|
}
|
|
@@ -250,18 +264,73 @@ export function addCodeApiExecutionProfileHeader(
|
|
|
250
264
|
};
|
|
251
265
|
}
|
|
252
266
|
|
|
267
|
+
type CodeApiErrorResponse = {
|
|
268
|
+
status: number;
|
|
269
|
+
text: () => Promise<string>;
|
|
270
|
+
/** node-fetch types this as `NodeJS.ReadableStream`, which does not declare
|
|
271
|
+
* `destroy`; every runtime instance is a `Readable`, and a test double may
|
|
272
|
+
* supply nothing at all. */
|
|
273
|
+
body?: NodeJS.ReadableStream | null;
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Only the 429 branch reads the body, and node-fetch keeps the stream and its
|
|
278
|
+
* socket alive until something does. Repeated backend failures would otherwise
|
|
279
|
+
* accumulate connections holding payloads this module deliberately discards.
|
|
280
|
+
*/
|
|
281
|
+
function discardResponseBody(response: CodeApiErrorResponse): void {
|
|
282
|
+
const body = response.body;
|
|
283
|
+
if (body == null || !('destroy' in body)) {
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
const destroy = (body as NodeJS.ReadableStream & Partial<Readable>).destroy;
|
|
287
|
+
if (typeof destroy !== 'function') {
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
try {
|
|
291
|
+
destroy.call(body);
|
|
292
|
+
} catch {
|
|
293
|
+
/* Already finished or not destroyable; nothing is being held open. */
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
253
297
|
export async function buildCodeApiHttpErrorMessage(
|
|
254
|
-
|
|
298
|
+
method: CodeApiMethod,
|
|
255
299
|
_endpoint: string,
|
|
256
|
-
response:
|
|
300
|
+
response: CodeApiErrorResponse,
|
|
301
|
+
options?: { recoverable?: boolean; profile?: t.CodeApiExecutionProfile }
|
|
257
302
|
): Promise<string> {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
303
|
+
/* Logged before the body is touched. A non-OK response can leave a chunked
|
|
304
|
+
body open, and there is no read timeout here, so draining first would
|
|
305
|
+
withhold the diagnostic indefinitely for exactly the backend it identifies.
|
|
306
|
+
The body itself is never logged — it is upstream free text that can echo
|
|
307
|
+
the header that was sent — and the endpoint is host-configured, so the
|
|
308
|
+
backend is named by the profile this module chose rather than by its
|
|
309
|
+
address. A recoverable caller reports its own outcome; logging here too
|
|
310
|
+
would raise an error-level alert for a lookup that succeeds by returning
|
|
311
|
+
nothing. */
|
|
312
|
+
if (options?.recoverable !== true) {
|
|
313
|
+
logCodeApiDiagnostic(
|
|
314
|
+
'CodeExecutor',
|
|
315
|
+
response.status === 429 ? 'warn' : 'error',
|
|
316
|
+
'Code API rejected the request',
|
|
317
|
+
{
|
|
318
|
+
method,
|
|
319
|
+
profile: options?.profile ?? 'unset',
|
|
320
|
+
status: response.status,
|
|
321
|
+
}
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
if (response.status !== 429) {
|
|
325
|
+
discardResponseBody(response);
|
|
263
326
|
}
|
|
264
327
|
if (response.status === 429) {
|
|
328
|
+
let responseBody = '';
|
|
329
|
+
try {
|
|
330
|
+
responseBody = await response.text();
|
|
331
|
+
} catch {
|
|
332
|
+
responseBody = '';
|
|
333
|
+
}
|
|
265
334
|
const retryAfterSeconds = getRetryAfterSeconds(responseBody);
|
|
266
335
|
return retryAfterSeconds != null
|
|
267
336
|
? `Code execution is temporarily rate-limited. Retry after ${retryAfterSeconds} seconds.`
|
|
@@ -445,9 +514,11 @@ function createCodeExecutionTool(
|
|
|
445
514
|
session_id.length > 0 &&
|
|
446
515
|
!Array.isArray(postData.files)
|
|
447
516
|
) {
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
517
|
+
logCodeApiDiagnostic(
|
|
518
|
+
'CodeExecutor',
|
|
519
|
+
'debug',
|
|
520
|
+
'session carried no injected files; exec will run without input files',
|
|
521
|
+
{ files: 'none' }
|
|
451
522
|
);
|
|
452
523
|
}
|
|
453
524
|
|
|
@@ -474,7 +545,9 @@ function createCodeExecutionTool(
|
|
|
474
545
|
const response = await fetch(execEndpoint, fetchOptions);
|
|
475
546
|
if (!response.ok) {
|
|
476
547
|
throw new CodeApiRequestError(
|
|
477
|
-
await buildCodeApiHttpErrorMessage('POST', execEndpoint, response
|
|
548
|
+
await buildCodeApiHttpErrorMessage('POST', execEndpoint, response, {
|
|
549
|
+
profile: executionProfile,
|
|
550
|
+
})
|
|
478
551
|
);
|
|
479
552
|
}
|
|
480
553
|
|
|
@@ -33,6 +33,10 @@ import {
|
|
|
33
33
|
createCodeApiRunTimeoutSchema,
|
|
34
34
|
resolveCodeApiRunTimeoutMs,
|
|
35
35
|
} from './ptcTimeout';
|
|
36
|
+
import {
|
|
37
|
+
describeCodeApiError,
|
|
38
|
+
logCodeApiDiagnostic,
|
|
39
|
+
} from '@/tools/diagnostics';
|
|
36
40
|
import { resolveFetchProxyAgent } from '@/utils/proxy';
|
|
37
41
|
import { INTENT_PROPERTY } from '@/tools/intentArg';
|
|
38
42
|
import { Constants } from '@/common';
|
|
@@ -449,7 +453,9 @@ export async function fetchSessionFiles(
|
|
|
449
453
|
}
|
|
450
454
|
}
|
|
451
455
|
const filesEndpoint = `${baseUrl}/files/${encodeURIComponent(sessionId)}?${query.toString()}`;
|
|
452
|
-
const resolvedAuthHeaders = await resolveCodeApiAuthHeaders(authHeaders
|
|
456
|
+
const resolvedAuthHeaders = await resolveCodeApiAuthHeaders(authHeaders, {
|
|
457
|
+
recoverable: true,
|
|
458
|
+
});
|
|
453
459
|
const fetchOptions: RequestInit = {
|
|
454
460
|
method: 'GET',
|
|
455
461
|
headers: {
|
|
@@ -466,7 +472,9 @@ export async function fetchSessionFiles(
|
|
|
466
472
|
const response = await fetch(filesEndpoint, fetchOptions);
|
|
467
473
|
if (!response.ok) {
|
|
468
474
|
throw new Error(
|
|
469
|
-
await buildCodeApiHttpErrorMessage('GET', filesEndpoint, response
|
|
475
|
+
await buildCodeApiHttpErrorMessage('GET', filesEndpoint, response, {
|
|
476
|
+
recoverable: true,
|
|
477
|
+
})
|
|
470
478
|
);
|
|
471
479
|
}
|
|
472
480
|
|
|
@@ -479,9 +487,11 @@ export async function fetchSessionFiles(
|
|
|
479
487
|
.filter(isCodeApiSessionFileWire)
|
|
480
488
|
.map((file) => normalizeSessionFile(file, sessionId, scope));
|
|
481
489
|
} catch (error) {
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
490
|
+
logCodeApiDiagnostic(
|
|
491
|
+
'ProgrammaticToolCalling',
|
|
492
|
+
'warn',
|
|
493
|
+
'session file lookup failed; continuing without input files',
|
|
494
|
+
describeCodeApiError(error)
|
|
485
495
|
);
|
|
486
496
|
return [];
|
|
487
497
|
}
|
|
@@ -525,7 +535,9 @@ export async function makeRequest(
|
|
|
525
535
|
|
|
526
536
|
if (!response.ok) {
|
|
527
537
|
throw new CodeApiRequestError(
|
|
528
|
-
await buildCodeApiHttpErrorMessage('POST', endpoint, response
|
|
538
|
+
await buildCodeApiHttpErrorMessage('POST', endpoint, response, {
|
|
539
|
+
profile: executionProfile,
|
|
540
|
+
})
|
|
529
541
|
);
|
|
530
542
|
}
|
|
531
543
|
|
|
@@ -931,9 +943,7 @@ export async function runPlainExecution(args: {
|
|
|
931
943
|
{
|
|
932
944
|
lang: args.lang,
|
|
933
945
|
code:
|
|
934
|
-
args.lang === 'py'
|
|
935
|
-
? wrapPythonForPlainExecution(args.code)
|
|
936
|
-
: args.code,
|
|
946
|
+
args.lang === 'py' ? wrapPythonForPlainExecution(args.code) : args.code,
|
|
937
947
|
...(args.timeout != null ? { timeout: args.timeout } : {}),
|
|
938
948
|
...(args.sessionId != null && args.sessionId !== ''
|
|
939
949
|
? { session_id: args.sessionId }
|
|
@@ -1085,9 +1095,11 @@ export function createProgrammaticToolCallingTool(
|
|
|
1085
1095
|
if (_injected_files && _injected_files.length > 0) {
|
|
1086
1096
|
files = _injected_files;
|
|
1087
1097
|
} else if (session_id != null && session_id.length > 0) {
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1098
|
+
logCodeApiDiagnostic(
|
|
1099
|
+
'ProgrammaticToolCalling',
|
|
1100
|
+
'debug',
|
|
1101
|
+
'session carried no injected files; exec will run without input files',
|
|
1102
|
+
{ files: 'none' }
|
|
1091
1103
|
);
|
|
1092
1104
|
}
|
|
1093
1105
|
|