@librechat/agents 3.1.88 → 3.1.90
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 +25 -1
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/hooks/executeHooks.cjs +14 -7
- package/dist/cjs/hooks/executeHooks.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/index.cjs +8 -2
- package/dist/cjs/llm/anthropic/index.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +34 -0
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/main.cjs +9 -0
- package/dist/cjs/main.cjs.map +1 -1
- package/dist/cjs/stream.cjs +115 -8
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/tools/BashExecutor.cjs +10 -9
- package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
- package/dist/cjs/tools/BashProgrammaticToolCalling.cjs +12 -8
- package/dist/cjs/tools/BashProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/CodeExecutor.cjs +35 -11
- package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
- package/dist/cjs/tools/CodeSessionFileSummary.cjs +63 -0
- package/dist/cjs/tools/CodeSessionFileSummary.cjs.map +1 -0
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs +16 -12
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +32 -12
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs +319 -29
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
- package/dist/cjs/tools/toolOutputReferences.cjs +8 -0
- package/dist/cjs/tools/toolOutputReferences.cjs.map +1 -1
- package/dist/cjs/utils/events.cjs +3 -1
- package/dist/cjs/utils/events.cjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +25 -1
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/hooks/executeHooks.mjs +14 -7
- package/dist/esm/hooks/executeHooks.mjs.map +1 -1
- package/dist/esm/llm/anthropic/index.mjs +9 -3
- package/dist/esm/llm/anthropic/index.mjs.map +1 -1
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs +33 -1
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/main.mjs +2 -1
- package/dist/esm/main.mjs.map +1 -1
- package/dist/esm/stream.mjs +115 -8
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/tools/BashExecutor.mjs +11 -10
- package/dist/esm/tools/BashExecutor.mjs.map +1 -1
- package/dist/esm/tools/BashProgrammaticToolCalling.mjs +13 -9
- package/dist/esm/tools/BashProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/CodeExecutor.mjs +29 -12
- package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
- package/dist/esm/tools/CodeSessionFileSummary.mjs +60 -0
- package/dist/esm/tools/CodeSessionFileSummary.mjs.map +1 -0
- package/dist/esm/tools/ProgrammaticToolCalling.mjs +17 -13
- package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +32 -12
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/subagent/SubagentExecutor.mjs +320 -31
- package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
- package/dist/esm/tools/toolOutputReferences.mjs +8 -1
- package/dist/esm/tools/toolOutputReferences.mjs.map +1 -1
- package/dist/esm/utils/events.mjs +3 -1
- package/dist/esm/utils/events.mjs.map +1 -1
- package/dist/types/graphs/Graph.d.ts +8 -0
- package/dist/types/llm/anthropic/index.d.ts +3 -1
- package/dist/types/llm/anthropic/utils/message_inputs.d.ts +4 -0
- package/dist/types/tools/BashExecutor.d.ts +3 -3
- package/dist/types/tools/CodeExecutor.d.ts +10 -3
- package/dist/types/tools/CodeSessionFileSummary.d.ts +3 -0
- package/dist/types/tools/ProgrammaticToolCalling.d.ts +4 -4
- package/dist/types/tools/subagent/SubagentExecutor.d.ts +8 -5
- package/dist/types/types/tools.d.ts +11 -3
- package/dist/types/utils/events.d.ts +1 -1
- package/package.json +1 -1
- package/src/__tests__/stream.eagerEventExecution.test.ts +1073 -221
- package/src/graphs/Graph.ts +27 -5
- package/src/hooks/__tests__/executeHooks.test.ts +38 -0
- package/src/hooks/executeHooks.ts +27 -7
- package/src/llm/anthropic/index.ts +27 -3
- package/src/llm/anthropic/llm.spec.ts +60 -1
- package/src/llm/anthropic/utils/message_inputs.ts +46 -0
- package/src/specs/subagent.test.ts +87 -1
- package/src/stream.ts +163 -12
- package/src/tools/BashExecutor.ts +21 -10
- package/src/tools/BashProgrammaticToolCalling.ts +21 -9
- package/src/tools/CodeExecutor.ts +55 -12
- package/src/tools/CodeSessionFileSummary.ts +80 -0
- package/src/tools/ProgrammaticToolCalling.ts +25 -12
- package/src/tools/ToolNode.ts +142 -116
- package/src/tools/__tests__/BashExecutor.test.ts +9 -0
- package/src/tools/__tests__/CodeApiAuthHeaders.test.ts +43 -0
- package/src/tools/__tests__/ProgrammaticToolCalling.test.ts +100 -16
- package/src/tools/__tests__/SubagentExecutor.test.ts +540 -6
- package/src/tools/__tests__/ToolNode.eagerEventExecution.test.ts +278 -14
- package/src/tools/__tests__/ToolNode.outputReferences.test.ts +52 -0
- package/src/tools/__tests__/subagentHooks.test.ts +237 -0
- package/src/tools/subagent/SubagentExecutor.ts +514 -36
- package/src/types/tools.ts +11 -3
- package/src/utils/events.ts +4 -2
|
@@ -7,9 +7,13 @@ import type { ToolCall } from '@langchain/core/messages/tool';
|
|
|
7
7
|
import type { ProgrammaticToolCallingJsonSchema } from './ptcTimeout';
|
|
8
8
|
import type * as t from '@/types';
|
|
9
9
|
import {
|
|
10
|
+
CODE_ARTIFACT_PATH_GUIDANCE,
|
|
11
|
+
appendCodeSessionFileSummary,
|
|
12
|
+
appendFailedExecutionFileReminder,
|
|
10
13
|
buildCodeApiHttpErrorMessage,
|
|
11
14
|
emptyOutputMessage,
|
|
12
15
|
getCodeBaseURL,
|
|
16
|
+
appendTmpScratchReminder,
|
|
13
17
|
resolveCodeApiAuthHeaders,
|
|
14
18
|
} from './CodeExecutor';
|
|
15
19
|
import {
|
|
@@ -36,15 +40,17 @@ You MUST complete your entire workflow in ONE code block: query → process →
|
|
|
36
40
|
DO NOT split work across multiple calls expecting to reuse variables.`;
|
|
37
41
|
|
|
38
42
|
const CORE_RULES = `Rules:
|
|
39
|
-
-
|
|
40
|
-
-
|
|
41
|
-
- DO NOT define async def main() or call asyncio.run()
|
|
43
|
+
- One call: state does not persist
|
|
44
|
+
- Auto-wrapped async; use await, no main()/asyncio.run()
|
|
42
45
|
- Tools are pre-defined—DO NOT write function definitions
|
|
46
|
+
- Call tools with keyword args only (await tool(arg=value), never pass a dict)
|
|
47
|
+
- Tool results are decoded Python values (dict/list/str)
|
|
43
48
|
- Only print() output returns to the model
|
|
49
|
+
- ${CODE_ARTIFACT_PATH_GUIDANCE}
|
|
44
50
|
- timeout caps one sandbox run/replay iteration, not the total multi-round-trip workflow`;
|
|
45
51
|
|
|
46
|
-
const ADDITIONAL_RULES =
|
|
47
|
-
- Tool names normalized: hyphens→underscores, keywords get
|
|
52
|
+
const ADDITIONAL_RULES =
|
|
53
|
+
'- Tool names normalized: hyphens→underscores, keywords get `_tool` suffix';
|
|
48
54
|
|
|
49
55
|
const EXAMPLES = `Example (Complete workflow in one call):
|
|
50
56
|
# Query data
|
|
@@ -678,15 +684,16 @@ export async function executeTools(
|
|
|
678
684
|
/**
|
|
679
685
|
* Formats the completed response for the agent.
|
|
680
686
|
*
|
|
681
|
-
* Output
|
|
682
|
-
*
|
|
683
|
-
*
|
|
687
|
+
* Output includes stdout/stderr plus a compact session-file summary
|
|
688
|
+
* when artifacts were persisted. The artifact still carries every
|
|
689
|
+
* file so the host's session map stays in sync.
|
|
684
690
|
*
|
|
685
691
|
* @param response - The completed API response
|
|
686
692
|
* @returns Tuple of [formatted string, artifact]
|
|
687
693
|
*/
|
|
688
694
|
export function formatCompletedResponse(
|
|
689
|
-
response: t.ProgrammaticExecutionResponse
|
|
695
|
+
response: t.ProgrammaticExecutionResponse,
|
|
696
|
+
sourceCode = ''
|
|
690
697
|
): [string, t.ProgrammaticExecutionArtifact] {
|
|
691
698
|
let formatted = '';
|
|
692
699
|
|
|
@@ -700,8 +707,10 @@ export function formatCompletedResponse(
|
|
|
700
707
|
formatted += `stderr:\n${response.stderr}\n`;
|
|
701
708
|
}
|
|
702
709
|
|
|
710
|
+
const outputWithReminder = appendTmpScratchReminder(formatted, sourceCode);
|
|
711
|
+
|
|
703
712
|
return [
|
|
704
|
-
|
|
713
|
+
appendCodeSessionFileSummary(outputWithReminder, response.files),
|
|
705
714
|
{
|
|
706
715
|
session_id: response.session_id,
|
|
707
716
|
files: response.files,
|
|
@@ -859,7 +868,7 @@ export function createProgrammaticToolCallingTool(
|
|
|
859
868
|
// ====================================================================
|
|
860
869
|
|
|
861
870
|
if (response.status === 'completed') {
|
|
862
|
-
return formatCompletedResponse(response);
|
|
871
|
+
return formatCompletedResponse(response, code);
|
|
863
872
|
}
|
|
864
873
|
|
|
865
874
|
if (response.status === 'error') {
|
|
@@ -873,8 +882,12 @@ export function createProgrammaticToolCallingTool(
|
|
|
873
882
|
|
|
874
883
|
throw new Error(`Unexpected response status: ${response.status}`);
|
|
875
884
|
} catch (error) {
|
|
885
|
+
const messageWithReminder = appendFailedExecutionFileReminder(
|
|
886
|
+
(error as Error).message,
|
|
887
|
+
code
|
|
888
|
+
);
|
|
876
889
|
throw new Error(
|
|
877
|
-
`Programmatic execution failed: ${
|
|
890
|
+
`Programmatic execution failed: ${messageWithReminder}`
|
|
878
891
|
);
|
|
879
892
|
}
|
|
880
893
|
},
|
package/src/tools/ToolNode.ts
CHANGED
|
@@ -46,6 +46,7 @@ import {
|
|
|
46
46
|
buildReferenceKey,
|
|
47
47
|
ToolOutputReferenceRegistry,
|
|
48
48
|
} from '@/tools/toolOutputReferences';
|
|
49
|
+
import { stripCodeSessionFileSummary } from '@/tools/CodeSessionFileSummary';
|
|
49
50
|
import {
|
|
50
51
|
resolveLocalToolRegistry,
|
|
51
52
|
resolveLocalExecutionTools,
|
|
@@ -911,8 +912,9 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
911
912
|
* Both session_id and _injected_files are injected directly to invokeParams
|
|
912
913
|
* (not inside args) so they bypass Zod schema validation and reach config.toolCall.
|
|
913
914
|
*
|
|
914
|
-
* session_id is always injected when available
|
|
915
|
-
*
|
|
915
|
+
* session_id is always injected when available, but concrete file refs
|
|
916
|
+
* still need to travel through `_injected_files`; the legacy
|
|
917
|
+
* `/files/<session_id>` fallback was removed from the executors.
|
|
916
918
|
*/
|
|
917
919
|
if (CODE_EXECUTION_TOOLS.has(call.name)) {
|
|
918
920
|
const codeSession = this.sessions?.get(Constants.EXECUTE_CODE) as
|
|
@@ -959,6 +961,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
959
961
|
if (this.toolOutputRegistry != null || unresolvedRefs.length > 0) {
|
|
960
962
|
if (typeof toolMsg.content === 'string') {
|
|
961
963
|
const rawContent = toolMsg.content;
|
|
964
|
+
const registryContent = stripCodeSessionFileSummary(rawContent);
|
|
962
965
|
const llmContent = truncateToolResultContent(
|
|
963
966
|
rawContent,
|
|
964
967
|
this.maxToolResultChars
|
|
@@ -966,7 +969,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
966
969
|
toolMsg.content = llmContent;
|
|
967
970
|
const refMeta = this.recordOutputReference(
|
|
968
971
|
runId,
|
|
969
|
-
|
|
972
|
+
registryContent,
|
|
970
973
|
refKey,
|
|
971
974
|
unresolvedRefs
|
|
972
975
|
);
|
|
@@ -1015,7 +1018,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
1015
1018
|
);
|
|
1016
1019
|
const refMeta = this.recordOutputReference(
|
|
1017
1020
|
runId,
|
|
1018
|
-
rawContent,
|
|
1021
|
+
stripCodeSessionFileSummary(rawContent),
|
|
1019
1022
|
refKey,
|
|
1020
1023
|
unresolvedRefs
|
|
1021
1024
|
);
|
|
@@ -1062,13 +1065,13 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
1062
1065
|
handlerError:
|
|
1063
1066
|
handlerError instanceof Error
|
|
1064
1067
|
? {
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
+
message: handlerError.message,
|
|
1069
|
+
stack: handlerError.stack ?? undefined,
|
|
1070
|
+
}
|
|
1068
1071
|
: {
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
+
message: String(handlerError),
|
|
1073
|
+
stack: undefined,
|
|
1074
|
+
},
|
|
1072
1075
|
});
|
|
1073
1076
|
}
|
|
1074
1077
|
}
|
|
@@ -1076,11 +1079,11 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
1076
1079
|
const refMeta =
|
|
1077
1080
|
unresolvedRefs.length > 0
|
|
1078
1081
|
? this.recordOutputReference(
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1082
|
+
runId,
|
|
1083
|
+
errorContent,
|
|
1084
|
+
undefined,
|
|
1085
|
+
unresolvedRefs
|
|
1086
|
+
)
|
|
1084
1087
|
: undefined;
|
|
1085
1088
|
return new ToolMessage({
|
|
1086
1089
|
status: 'error',
|
|
@@ -2432,59 +2435,77 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
2432
2435
|
dispatchRequests.length === 0
|
|
2433
2436
|
? Promise.resolve([] as t.ToolExecuteResult[])
|
|
2434
2437
|
: new Promise<t.ToolExecuteResult[]>((resolve, reject) => {
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
+
let dispatchSettled = false;
|
|
2439
|
+
let resultSettled = false;
|
|
2440
|
+
let settledResults: t.ToolExecuteResult[] | undefined;
|
|
2438
2441
|
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2442
|
+
const maybeResolve = (): void => {
|
|
2443
|
+
if (dispatchSettled && resultSettled) {
|
|
2444
|
+
resolve(settledResults ?? []);
|
|
2445
|
+
}
|
|
2446
|
+
};
|
|
2444
2447
|
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2448
|
+
const batchRequest: t.ToolExecuteBatchRequest = {
|
|
2449
|
+
toolCalls: dispatchRequests,
|
|
2450
|
+
userId: config.configurable?.user_id as string | undefined,
|
|
2451
|
+
agentId: this.agentId,
|
|
2452
|
+
configurable: config.configurable as
|
|
2450
2453
|
| Record<string, unknown>
|
|
2451
2454
|
| undefined,
|
|
2452
|
-
|
|
2455
|
+
metadata: config.metadata as
|
|
2453
2456
|
| Record<string, unknown>
|
|
2454
2457
|
| undefined,
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2458
|
+
resolve: (results): void => {
|
|
2459
|
+
resultSettled = true;
|
|
2460
|
+
settledResults = results;
|
|
2461
|
+
maybeResolve();
|
|
2462
|
+
},
|
|
2463
|
+
reject,
|
|
2464
|
+
};
|
|
2462
2465
|
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2466
|
+
void safeDispatchCustomEvent(
|
|
2467
|
+
GraphEvents.ON_TOOL_EXECUTE,
|
|
2468
|
+
batchRequest,
|
|
2469
|
+
config
|
|
2470
|
+
)
|
|
2471
|
+
.then(() => {
|
|
2472
|
+
dispatchSettled = true;
|
|
2473
|
+
maybeResolve();
|
|
2474
|
+
})
|
|
2475
|
+
.catch(reject);
|
|
2476
|
+
});
|
|
2474
2477
|
|
|
2475
2478
|
const eagerResultsPromise = Promise.all(
|
|
2476
|
-
eagerExecutions.map(({ request, execution }) =>
|
|
2477
|
-
this.resolveEagerEventExecution(
|
|
2478
|
-
|
|
2479
|
-
|
|
2479
|
+
eagerExecutions.map(async ({ request, execution }) => {
|
|
2480
|
+
const results = await this.resolveEagerEventExecution(
|
|
2481
|
+
request,
|
|
2482
|
+
execution
|
|
2483
|
+
);
|
|
2484
|
+
return {
|
|
2485
|
+
results,
|
|
2486
|
+
completionDispatched:
|
|
2487
|
+
execution.completionDispatched === true &&
|
|
2488
|
+
execution.request.turn === request.turn,
|
|
2489
|
+
toolCallId: request.id,
|
|
2490
|
+
};
|
|
2491
|
+
})
|
|
2492
|
+
);
|
|
2480
2493
|
|
|
2481
2494
|
const [eagerResults, dispatchedResults] = await Promise.all([
|
|
2482
2495
|
eagerResultsPromise,
|
|
2483
2496
|
dispatchPromise,
|
|
2484
2497
|
]);
|
|
2498
|
+
const eagerCompletionDispatchedIds = new Set(
|
|
2499
|
+
eagerResults
|
|
2500
|
+
.filter((result) => result.completionDispatched)
|
|
2501
|
+
.map((result) => result.toolCallId)
|
|
2502
|
+
);
|
|
2503
|
+
const flattenedEagerResults = eagerResults.flatMap(
|
|
2504
|
+
(result) => result.results
|
|
2505
|
+
);
|
|
2485
2506
|
const results = [
|
|
2486
2507
|
...plan.rejectedResults,
|
|
2487
|
-
...
|
|
2508
|
+
...flattenedEagerResults,
|
|
2488
2509
|
...dispatchedResults,
|
|
2489
2510
|
];
|
|
2490
2511
|
|
|
@@ -2537,11 +2558,11 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
2537
2558
|
const errorRefMeta =
|
|
2538
2559
|
unresolved.length > 0
|
|
2539
2560
|
? this.recordOutputReference(
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2561
|
+
registryRunId,
|
|
2562
|
+
contentString,
|
|
2563
|
+
undefined,
|
|
2564
|
+
unresolved
|
|
2565
|
+
)
|
|
2545
2566
|
: undefined;
|
|
2546
2567
|
toolMessage = new ToolMessage({
|
|
2547
2568
|
status: 'error',
|
|
@@ -2643,7 +2664,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
2643
2664
|
: undefined;
|
|
2644
2665
|
const successRefMeta = this.recordOutputReference(
|
|
2645
2666
|
registryRunId,
|
|
2646
|
-
registryRaw,
|
|
2667
|
+
stripCodeSessionFileSummary(registryRaw),
|
|
2647
2668
|
refKey,
|
|
2648
2669
|
unresolved
|
|
2649
2670
|
);
|
|
@@ -2660,14 +2681,16 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
2660
2681
|
});
|
|
2661
2682
|
}
|
|
2662
2683
|
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2684
|
+
if (!eagerCompletionDispatchedIds.has(result.toolCallId)) {
|
|
2685
|
+
await this.dispatchStepCompleted(
|
|
2686
|
+
result.toolCallId,
|
|
2687
|
+
toolName,
|
|
2688
|
+
request?.args ?? {},
|
|
2689
|
+
contentString,
|
|
2690
|
+
config,
|
|
2691
|
+
request?.turn
|
|
2692
|
+
);
|
|
2693
|
+
}
|
|
2671
2694
|
|
|
2672
2695
|
postToolBatchEntryByCallId.set(result.toolCallId, {
|
|
2673
2696
|
toolName,
|
|
@@ -2706,8 +2729,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
2706
2729
|
this.eventDrivenMode &&
|
|
2707
2730
|
this.eagerEventToolExecution?.enabled === true &&
|
|
2708
2731
|
this.hookRegistry == null &&
|
|
2709
|
-
this.humanInTheLoop?.enabled !== true
|
|
2710
|
-
this.toolOutputRegistry == null
|
|
2732
|
+
this.humanInTheLoop?.enabled !== true
|
|
2711
2733
|
);
|
|
2712
2734
|
}
|
|
2713
2735
|
|
|
@@ -2725,10 +2747,14 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
2725
2747
|
|
|
2726
2748
|
this.eagerEventToolExecutions?.delete(request.id);
|
|
2727
2749
|
|
|
2750
|
+
// Only tool identity + canonical args define side-effect identity here.
|
|
2751
|
+
// `request.turn` is final-planning metadata; if it drifts between the
|
|
2752
|
+
// streamed eager reservation and model-end materialization, consume the
|
|
2753
|
+
// same-name/same-args eager result and let the final request drive refs,
|
|
2754
|
+
// completion metadata, and PostToolBatch state.
|
|
2728
2755
|
if (
|
|
2729
2756
|
execution.toolName !== request.name ||
|
|
2730
|
-
!recordArgsEqual(execution.args, request.args)
|
|
2731
|
-
execution.request.turn !== request.turn
|
|
2757
|
+
!recordArgsEqual(execution.args, request.args)
|
|
2732
2758
|
) {
|
|
2733
2759
|
return {
|
|
2734
2760
|
toolCallId: request.id,
|
|
@@ -3015,15 +3041,15 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
3015
3041
|
outputs =
|
|
3016
3042
|
directAdditionalContexts.length > 0
|
|
3017
3043
|
? [
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3044
|
+
sendOutput,
|
|
3045
|
+
new HumanMessage({
|
|
3046
|
+
content: directAdditionalContexts.join('\n\n'),
|
|
3047
|
+
// Match the event-driven path's marker so hosts /
|
|
3048
|
+
// model-side annotators treat this as system intent
|
|
3049
|
+
// rather than ordinary user text. Codex P2 [46].
|
|
3050
|
+
additional_kwargs: { role: 'system', source: 'hook' },
|
|
3051
|
+
}),
|
|
3052
|
+
]
|
|
3027
3053
|
: [sendOutput];
|
|
3028
3054
|
await this.handleRunToolCompletions(
|
|
3029
3055
|
[input.lg_tool_call],
|
|
@@ -3174,17 +3200,17 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
3174
3200
|
const directOutputs: (BaseMessage | Command)[] =
|
|
3175
3201
|
directCalls.length > 0
|
|
3176
3202
|
? await Promise.all(
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
)
|
|
3203
|
+
directCalls.map((call, i) =>
|
|
3204
|
+
this.runDirectToolWithLifecycleHooks(call, config, {
|
|
3205
|
+
batchIndex: directIndices[i],
|
|
3206
|
+
turn,
|
|
3207
|
+
batchScopeId,
|
|
3208
|
+
resolvedArgsByCallId,
|
|
3209
|
+
preBatchSnapshot,
|
|
3210
|
+
additionalContextsSink: directAdditionalContexts,
|
|
3211
|
+
})
|
|
3187
3212
|
)
|
|
3213
|
+
)
|
|
3188
3214
|
: [];
|
|
3189
3215
|
|
|
3190
3216
|
if (directCalls.length > 0 && directOutputs.length > 0) {
|
|
@@ -3199,29 +3225,29 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
3199
3225
|
const eventResult =
|
|
3200
3226
|
eventCalls.length > 0
|
|
3201
3227
|
? await this.dispatchToolEvents(eventCalls, config, {
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3228
|
+
batchIndices: eventIndices,
|
|
3229
|
+
turn,
|
|
3230
|
+
batchScopeId,
|
|
3231
|
+
preResolvedArgs: preResolvedEventArgs,
|
|
3232
|
+
preBatchSnapshot,
|
|
3233
|
+
})
|
|
3208
3234
|
: {
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3235
|
+
toolMessages: [] as ToolMessage[],
|
|
3236
|
+
injected: [] as BaseMessage[],
|
|
3237
|
+
};
|
|
3212
3238
|
|
|
3213
3239
|
const directInjected: BaseMessage[] =
|
|
3214
3240
|
directAdditionalContexts.length > 0
|
|
3215
3241
|
? [
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3242
|
+
new HumanMessage({
|
|
3243
|
+
content: directAdditionalContexts.join('\n\n'),
|
|
3244
|
+
// System-role metadata to match the event-driven
|
|
3245
|
+
// path so policy/recovery guidance is treated
|
|
3246
|
+
// consistently regardless of whether the tool ran
|
|
3247
|
+
// direct or dispatched. Codex P2 [46].
|
|
3248
|
+
additional_kwargs: { role: 'system', source: 'hook' },
|
|
3249
|
+
}),
|
|
3250
|
+
]
|
|
3225
3251
|
: [];
|
|
3226
3252
|
outputs = [
|
|
3227
3253
|
...directOutputs,
|
|
@@ -3260,15 +3286,15 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
3260
3286
|
outputs =
|
|
3261
3287
|
directAdditionalContexts.length > 0
|
|
3262
3288
|
? [
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3289
|
+
...toolOutputs,
|
|
3290
|
+
new HumanMessage({
|
|
3291
|
+
content: directAdditionalContexts.join('\n\n'),
|
|
3292
|
+
// Same system-role marker the event-driven path
|
|
3293
|
+
// uses so direct vs dispatched is invisible to
|
|
3294
|
+
// downstream consumers. Codex P2 [46].
|
|
3295
|
+
additional_kwargs: { role: 'system', source: 'hook' },
|
|
3296
|
+
}),
|
|
3297
|
+
]
|
|
3272
3298
|
: toolOutputs;
|
|
3273
3299
|
}
|
|
3274
3300
|
}
|
|
@@ -18,6 +18,15 @@ describe('buildBashExecutionToolDescription', () => {
|
|
|
18
18
|
).toBe(BashExecutionToolDescription);
|
|
19
19
|
});
|
|
20
20
|
|
|
21
|
+
it('warns about compact bash shell pitfalls', () => {
|
|
22
|
+
expect(BashExecutionToolDescription).toContain('heredoc/printf');
|
|
23
|
+
expect(BashExecutionToolDescription).toContain('not bare Python');
|
|
24
|
+
expect(BashExecutionToolDescription).toContain(
|
|
25
|
+
'failed executions do not register new files'
|
|
26
|
+
);
|
|
27
|
+
expect(BashExecutionToolDescription).toContain('not later-call storage');
|
|
28
|
+
});
|
|
29
|
+
|
|
21
30
|
it('appends the tool-output references guide when enabled', () => {
|
|
22
31
|
const composed = buildBashExecutionToolDescription({
|
|
23
32
|
enableToolOutputReferences: true,
|
|
@@ -165,6 +165,17 @@ describe('CodeAPI auth header injection', () => {
|
|
|
165
165
|
).not.toHaveProperty('authHeaders');
|
|
166
166
|
});
|
|
167
167
|
|
|
168
|
+
it('tolerates null params for direct code execution', async () => {
|
|
169
|
+
fetchMock.mockResolvedValueOnce(
|
|
170
|
+
jsonResponse({ session_id: 'session_123', stdout: '1\n' })
|
|
171
|
+
);
|
|
172
|
+
const tool = createCodeExecutionTool(null);
|
|
173
|
+
|
|
174
|
+
await expect(
|
|
175
|
+
tool.invoke({ lang: 'py', code: 'print(1)' })
|
|
176
|
+
).resolves.toBeDefined();
|
|
177
|
+
});
|
|
178
|
+
|
|
168
179
|
it('forwards Authorization for bash execution', async () => {
|
|
169
180
|
fetchMock.mockResolvedValueOnce(
|
|
170
181
|
jsonResponse({ session_id: 'session_123', stdout: '1\n' })
|
|
@@ -333,6 +344,38 @@ describe('CodeAPI auth header injection', () => {
|
|
|
333
344
|
);
|
|
334
345
|
});
|
|
335
346
|
|
|
347
|
+
it('reminds that failed bash programmatic executions do not register new files', async () => {
|
|
348
|
+
fetchMock.mockResolvedValueOnce(
|
|
349
|
+
jsonResponse({
|
|
350
|
+
status: 'error',
|
|
351
|
+
error: 'jq failed',
|
|
352
|
+
stderr: 'jq: Cannot index string with string "name"',
|
|
353
|
+
})
|
|
354
|
+
);
|
|
355
|
+
const tool = createBashProgrammaticToolCallingTool();
|
|
356
|
+
|
|
357
|
+
await expect(
|
|
358
|
+
tool.invoke(
|
|
359
|
+
{
|
|
360
|
+
code: [
|
|
361
|
+
'lookup_user "{}" > /mnt/data/user.json',
|
|
362
|
+
'jq -r \'.result.name\' /mnt/data/user.json',
|
|
363
|
+
].join('\n'),
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
toolCall: {
|
|
367
|
+
name: 'bash_programmatic_code_execution',
|
|
368
|
+
args: {},
|
|
369
|
+
toolMap: toolMap(),
|
|
370
|
+
toolDefs,
|
|
371
|
+
},
|
|
372
|
+
}
|
|
373
|
+
)
|
|
374
|
+
).rejects.toThrow(
|
|
375
|
+
'files written during this failed call were not registered for later calls'
|
|
376
|
+
);
|
|
377
|
+
});
|
|
378
|
+
|
|
336
379
|
it('fetches session files with the CodeAPI resource scope and auth headers', async () => {
|
|
337
380
|
fetchMock.mockResolvedValueOnce(
|
|
338
381
|
jsonResponse([
|