@librechat/agents 3.7.21 → 3.8.0
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/langfuse.cjs +6 -2
- package/dist/cjs/langfuse.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/ArtifactDelivery.cjs +27 -0
- package/dist/cjs/tools/ArtifactDelivery.cjs.map +1 -0
- package/dist/cjs/tools/BashExecutor.cjs +6 -1
- package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
- package/dist/cjs/tools/CodeExecutor.cjs +6 -1
- package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs +5 -1
- 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/local/resolveLocalExecutionTools.cjs +1 -1
- package/dist/cjs/tools/preparedSubagents.cjs +129 -0
- package/dist/cjs/tools/preparedSubagents.cjs.map +1 -0
- package/dist/esm/graphs/Graph.mjs +21 -4
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/langfuse.mjs +6 -2
- package/dist/esm/langfuse.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/ArtifactDelivery.mjs +26 -0
- package/dist/esm/tools/ArtifactDelivery.mjs.map +1 -0
- package/dist/esm/tools/BashExecutor.mjs +6 -1
- package/dist/esm/tools/BashExecutor.mjs.map +1 -1
- package/dist/esm/tools/CodeExecutor.mjs +6 -1
- package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
- package/dist/esm/tools/ProgrammaticToolCalling.mjs +5 -1
- 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/local/resolveLocalExecutionTools.mjs +1 -1
- package/dist/esm/tools/preparedSubagents.mjs +128 -0
- package/dist/esm/tools/preparedSubagents.mjs.map +1 -0
- package/dist/types/graphs/Graph.d.ts +6 -1
- package/dist/types/langfuse.d.ts +5 -0
- package/dist/types/tools/ArtifactDelivery.d.ts +4 -0
- package/dist/types/tools/ToolNode.d.ts +13 -1
- package/dist/types/tools/ToolSearch.d.ts +46 -2
- package/dist/types/tools/preparedSubagents.d.ts +26 -0
- package/dist/types/types/graph.d.ts +8 -0
- package/dist/types/types/tools.d.ts +28 -0
- package/package.json +1 -1
- package/src/graphs/Graph.ts +63 -5
- package/src/langfuse.ts +17 -2
- package/src/llm/invoke.ts +35 -8
- package/src/stream.ts +70 -1
- package/src/tools/ArtifactDelivery.ts +50 -0
- package/src/tools/BashExecutor.ts +18 -1
- package/src/tools/CodeExecutor.ts +18 -1
- package/src/tools/ProgrammaticToolCalling.ts +15 -1
- package/src/tools/ToolNode.ts +189 -43
- package/src/tools/ToolSearch.ts +278 -28
- package/src/tools/preparedSubagents.ts +228 -0
- package/src/types/graph.ts +8 -0
- package/src/types/tools.ts +29 -0
|
@@ -652,6 +652,14 @@ export interface LangfuseConfig {
|
|
|
652
652
|
*/
|
|
653
653
|
additionalHeaders?: Record<string, string>;
|
|
654
654
|
metadata?: Record<string, string | number | boolean | null | undefined>;
|
|
655
|
+
/**
|
|
656
|
+
* User identity stamped on every trace this run emits — the agent stream,
|
|
657
|
+
* titles, activity and reasoning labels, and phases. Hosts set it when the
|
|
658
|
+
* observability identity differs from `configurable.user_id` (an email or
|
|
659
|
+
* IdP subject instead of an internal database id); that id remains the
|
|
660
|
+
* fallback when unset or blank.
|
|
661
|
+
*/
|
|
662
|
+
userId?: string;
|
|
655
663
|
/**
|
|
656
664
|
* Internal OTLP span attributes to attach to Langfuse observations before
|
|
657
665
|
* export. Intended for collector-side routing/filtering; strip these in the
|
|
@@ -3,6 +3,7 @@ import type { StructuredToolInterface } from '@langchain/core/tools';
|
|
|
3
3
|
import type { ToolCall } from '@langchain/core/messages/tool';
|
|
4
4
|
import type { MessageContentComplex, RunStepResumeState, ToolErrorData } from './stream';
|
|
5
5
|
import type { ToolOutputReferenceRegistry } from '@/tools/toolOutputReferences';
|
|
6
|
+
import type { PreparedSubagents } from '@/tools/preparedSubagents';
|
|
6
7
|
import type { RunBreakerScope } from '@/llm/streamLimits';
|
|
7
8
|
import type { HumanInTheLoopConfig } from './hitl';
|
|
8
9
|
import type { LangfuseConfig } from './graph';
|
|
@@ -42,6 +43,13 @@ export type EagerEventToolExecutionConfig = {
|
|
|
42
43
|
* execution with final args.
|
|
43
44
|
*/
|
|
44
45
|
excludeToolNames?: string[];
|
|
46
|
+
/**
|
|
47
|
+
* Maximum retained early foreground subagent invocations per graph. Defaults
|
|
48
|
+
* to 4; 0 disables them. Checkpoints, parent tool hooks, background calls and
|
|
49
|
+
* control-flow graph tools retain normal batch execution. Once a child has
|
|
50
|
+
* started, a failed/revised model attempt fails instead of automatically retrying.
|
|
51
|
+
*/
|
|
52
|
+
maxPendingSubagents?: number;
|
|
45
53
|
};
|
|
46
54
|
export type EagerEventToolExecutionOutcome = {
|
|
47
55
|
results: ToolExecuteResult[];
|
|
@@ -273,6 +281,8 @@ export type ToolNodeOptions = {
|
|
|
273
281
|
* controller.
|
|
274
282
|
*/
|
|
275
283
|
getRunScope?: () => RunBreakerScope;
|
|
284
|
+
/** Internal graph-owned prepared invocation registry. */
|
|
285
|
+
preparedSubagents?: PreparedSubagents;
|
|
276
286
|
/** SDK-owned checkpoint bridge for open run-step lifecycle state. */
|
|
277
287
|
restoreRunStepResumeState?: (state?: RunStepResumeState, config?: RunnableConfig) => void;
|
|
278
288
|
/** SDK-owned checkpoint snapshot for open run-step lifecycle state. */
|
|
@@ -439,6 +449,13 @@ export type FileRef = {
|
|
|
439
449
|
inherited?: true;
|
|
440
450
|
};
|
|
441
451
|
export type FileRefs = FileRef[];
|
|
452
|
+
export type ArtifactDeliveryFailure = {
|
|
453
|
+
code: 'artifact_delivery_failed';
|
|
454
|
+
status: 'partial' | 'failed';
|
|
455
|
+
attempted: number;
|
|
456
|
+
delivered: number;
|
|
457
|
+
failed: number;
|
|
458
|
+
};
|
|
442
459
|
export type ExecuteResult = {
|
|
443
460
|
/**
|
|
444
461
|
* Execution session id — the (transient) sandbox run that produced
|
|
@@ -449,6 +466,7 @@ export type ExecuteResult = {
|
|
|
449
466
|
stdout: string;
|
|
450
467
|
stderr: string;
|
|
451
468
|
files?: FileRefs;
|
|
469
|
+
artifact_delivery?: ArtifactDeliveryFailure;
|
|
452
470
|
/**
|
|
453
471
|
* Durable runtime session id echoed by a stateful Code API backend
|
|
454
472
|
* (hash of tenant+user+hint). Additive; absent on stateless servers.
|
|
@@ -1160,6 +1178,13 @@ export type ToolSearchArtifact = {
|
|
|
1160
1178
|
total_searched: number;
|
|
1161
1179
|
pattern: string;
|
|
1162
1180
|
error?: string;
|
|
1181
|
+
/** Servers that had tools to search, emitted when a server filter matched
|
|
1182
|
+
* nothing so the caller can see what it could have asked for. */
|
|
1183
|
+
available_mcp_servers?: string[];
|
|
1184
|
+
/** Requested servers that matched no registered server. */
|
|
1185
|
+
unmatched_mcp_servers?: string[];
|
|
1186
|
+
/** Requested servers that exist but had no tools left to search. */
|
|
1187
|
+
idle_mcp_servers?: string[];
|
|
1163
1188
|
};
|
|
1164
1189
|
};
|
|
1165
1190
|
/**
|
|
@@ -1200,6 +1225,7 @@ export type ProgrammaticExecutionResponse = {
|
|
|
1200
1225
|
stdout?: string;
|
|
1201
1226
|
stderr?: string;
|
|
1202
1227
|
files?: FileRefs;
|
|
1228
|
+
artifact_delivery?: ArtifactDeliveryFailure;
|
|
1203
1229
|
/** Durable runtime session echo from a stateful backend (additive). */
|
|
1204
1230
|
runtime_session_id?: string;
|
|
1205
1231
|
runtime_status?: 'new' | 'reused';
|
|
@@ -1213,6 +1239,7 @@ export type ProgrammaticExecutionArtifact = {
|
|
|
1213
1239
|
/** Execution session — see `CodeSessionContext.session_id`. */
|
|
1214
1240
|
session_id?: string;
|
|
1215
1241
|
files?: FileRefs;
|
|
1242
|
+
artifact_delivery?: ArtifactDeliveryFailure;
|
|
1216
1243
|
/** Durable runtime session echo from a stateful backend (additive). */
|
|
1217
1244
|
runtime_session_id?: string;
|
|
1218
1245
|
runtime_status?: 'new' | 'reused';
|
|
@@ -1267,6 +1294,7 @@ export type CodeExecutionArtifact = {
|
|
|
1267
1294
|
/** Execution session — see `CodeSessionContext.session_id`. */
|
|
1268
1295
|
session_id?: string;
|
|
1269
1296
|
files?: FileRefs;
|
|
1297
|
+
artifact_delivery?: ArtifactDeliveryFailure;
|
|
1270
1298
|
/** Durable runtime session echo from a stateful backend (additive). */
|
|
1271
1299
|
runtime_session_id?: string;
|
|
1272
1300
|
runtime_status?: 'new' | 'reused';
|
package/package.json
CHANGED
package/src/graphs/Graph.ts
CHANGED
|
@@ -146,6 +146,7 @@ import {
|
|
|
146
146
|
findCallback,
|
|
147
147
|
type CallbackEntry,
|
|
148
148
|
} from '@/utils/callbacks';
|
|
149
|
+
import { PreparedSubagents, PreparedSubagentError } from '@/tools/preparedSubagents';
|
|
149
150
|
import { ToolNode as CustomToolNode, toolsCondition } from '@/tools/ToolNode';
|
|
150
151
|
import { shouldTraceToolNodeForLangfuse } from '@/langfuseToolOutputTracing';
|
|
151
152
|
import { createLocalCodingToolBundle } from '@/tools/local/LocalCodingTools';
|
|
@@ -898,6 +899,7 @@ export abstract class Graph<
|
|
|
898
899
|
* Call after a run completes and content has been extracted.
|
|
899
900
|
*/
|
|
900
901
|
clearHeavyState(): void {
|
|
902
|
+
this.preparedSubagents.clear();
|
|
901
903
|
this.config = undefined;
|
|
902
904
|
this.signal = undefined;
|
|
903
905
|
this.callerSignal = undefined;
|
|
@@ -1239,6 +1241,8 @@ export abstract class Graph<
|
|
|
1239
1241
|
restoreSubagentResumeState(state: SubagentToolNodeResumeState): void;
|
|
1240
1242
|
}> = new Set();
|
|
1241
1243
|
private _subagentExecutors = new Set<SubagentExecutor>();
|
|
1244
|
+
readonly preparedSubagents = new PreparedSubagents();
|
|
1245
|
+
protected readonly subagentToolNodes = new Map<string, CustomToolNode<t.BaseGraphState>>();
|
|
1242
1246
|
public getOrCreateFileCheckpointer(): t.LocalFileCheckpointer | undefined {
|
|
1243
1247
|
// Return the cached instance unconditionally if one exists. The
|
|
1244
1248
|
// toolExecution check below decides whether to *create* a new
|
|
@@ -1397,16 +1401,16 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
1397
1401
|
* paths must not run in that state. */
|
|
1398
1402
|
protected resolveTrippedBreakerReason(
|
|
1399
1403
|
breakerSignal: AbortSignal = this.breakerAbort.signal
|
|
1400
|
-
): StreamLimitExceededError | undefined {
|
|
1404
|
+
): StreamLimitExceededError | PreparedSubagentError | undefined {
|
|
1401
1405
|
if (
|
|
1402
1406
|
breakerSignal.aborted &&
|
|
1403
|
-
breakerSignal.reason instanceof StreamLimitExceededError
|
|
1407
|
+
(breakerSignal.reason instanceof StreamLimitExceededError || breakerSignal.reason instanceof PreparedSubagentError)
|
|
1404
1408
|
) {
|
|
1405
1409
|
return breakerSignal.reason;
|
|
1406
1410
|
}
|
|
1407
1411
|
if (
|
|
1408
1412
|
this.signal?.aborted === true &&
|
|
1409
|
-
this.signal.reason instanceof StreamLimitExceededError
|
|
1413
|
+
(this.signal.reason instanceof StreamLimitExceededError || this.signal.reason instanceof PreparedSubagentError)
|
|
1410
1414
|
) {
|
|
1411
1415
|
return this.signal.reason;
|
|
1412
1416
|
}
|
|
@@ -1581,6 +1585,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
1581
1585
|
/* Init */
|
|
1582
1586
|
|
|
1583
1587
|
resetValues(keepContent?: boolean, checkpointScope?: string): void {
|
|
1588
|
+
this.preparedSubagents.clear();
|
|
1584
1589
|
this.resetSubagentCheckpointThreadIds();
|
|
1585
1590
|
this.messages = [];
|
|
1586
1591
|
this.hasRestoredCheckpointMessages = false;
|
|
@@ -2717,6 +2722,49 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
2717
2722
|
|
|
2718
2723
|
/* Graph */
|
|
2719
2724
|
|
|
2725
|
+
canPrestartSubagents(agentContext?: AgentContext): boolean {
|
|
2726
|
+
if (
|
|
2727
|
+
this.eagerEventToolExecution?.enabled !== true ||
|
|
2728
|
+
(this.eagerEventToolExecution.maxPendingSubagents ?? 4) <= 0 ||
|
|
2729
|
+
this.compileOptions?.checkpointer != null ||
|
|
2730
|
+
this.humanInTheLoop?.enabled === true ||
|
|
2731
|
+
(this.interruptingToolNames?.length ?? 0) > 0 ||
|
|
2732
|
+
agentContext == null ||
|
|
2733
|
+
!this.subagentToolNodes.has(agentContext.agentId)
|
|
2734
|
+
) {
|
|
2735
|
+
return false;
|
|
2736
|
+
}
|
|
2737
|
+
const graphTools = agentContext.graphTools as t.GenericTool[] | undefined;
|
|
2738
|
+
return (
|
|
2739
|
+
graphTools?.length === 1 &&
|
|
2740
|
+
graphTools[0].name === Constants.SUBAGENT &&
|
|
2741
|
+
SUBAGENT_REPLAY_CONTROLLER in graphTools[0]
|
|
2742
|
+
);
|
|
2743
|
+
}
|
|
2744
|
+
|
|
2745
|
+
prestartSubagent(
|
|
2746
|
+
call: ToolCall,
|
|
2747
|
+
attempt: string,
|
|
2748
|
+
agentContext?: AgentContext
|
|
2749
|
+
): void {
|
|
2750
|
+
const config = this.preparedSubagents.getConfig(attempt);
|
|
2751
|
+
if (
|
|
2752
|
+
!this.canPrestartSubagents(agentContext) ||
|
|
2753
|
+
config == null ||
|
|
2754
|
+
agentContext == null
|
|
2755
|
+
) {
|
|
2756
|
+
return;
|
|
2757
|
+
}
|
|
2758
|
+
this.subagentToolNodes
|
|
2759
|
+
.get(agentContext.agentId)
|
|
2760
|
+
?.prestartSubagent(
|
|
2761
|
+
call,
|
|
2762
|
+
attempt,
|
|
2763
|
+
config,
|
|
2764
|
+
this.eagerEventToolExecution?.maxPendingSubagents ?? 4
|
|
2765
|
+
);
|
|
2766
|
+
}
|
|
2767
|
+
|
|
2720
2768
|
initializeTools({
|
|
2721
2769
|
currentTools,
|
|
2722
2770
|
currentToolMap,
|
|
@@ -2791,6 +2839,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
2791
2839
|
hookRegistry: this.hookRegistry,
|
|
2792
2840
|
humanInTheLoop: this.humanInTheLoop,
|
|
2793
2841
|
eagerEventToolExecution: this.eagerEventToolExecution,
|
|
2842
|
+
preparedSubagents: this.preparedSubagents,
|
|
2794
2843
|
codeSessionToolNames: this.codeSessionToolNames,
|
|
2795
2844
|
eagerEventToolExecutions: this.eagerEventToolExecutions,
|
|
2796
2845
|
eagerEventToolUsageCount: this.getEagerEventToolUsageCount(
|
|
@@ -2816,6 +2865,9 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
2816
2865
|
StandardGraph.handleToolCallErrorStatic(this, data, metadata),
|
|
2817
2866
|
});
|
|
2818
2867
|
this.registerCompiledToolNode(node);
|
|
2868
|
+
if (agentContext != null) {
|
|
2869
|
+
this.subagentToolNodes.set(agentContext.agentId, node);
|
|
2870
|
+
}
|
|
2819
2871
|
return node;
|
|
2820
2872
|
}
|
|
2821
2873
|
|
|
@@ -4092,7 +4144,10 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
4092
4144
|
* succeeding fallback would resolve a run the public contract says
|
|
4093
4145
|
* must reject. Rethrow before any recovery path.
|
|
4094
4146
|
*/
|
|
4095
|
-
if (
|
|
4147
|
+
if (
|
|
4148
|
+
primaryError instanceof StreamLimitExceededError ||
|
|
4149
|
+
primaryError instanceof PreparedSubagentError
|
|
4150
|
+
) {
|
|
4096
4151
|
/** Tripped before rethrowing so parallel agent nodes' in-flight
|
|
4097
4152
|
* model calls and subagents stop while the rejection propagates. */
|
|
4098
4153
|
attemptBreaker.abort(primaryError);
|
|
@@ -4342,7 +4397,10 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
4342
4397
|
})
|
|
4343
4398
|
);
|
|
4344
4399
|
} catch (fallbackError) {
|
|
4345
|
-
if (
|
|
4400
|
+
if (
|
|
4401
|
+
fallbackError instanceof StreamLimitExceededError ||
|
|
4402
|
+
fallbackError instanceof PreparedSubagentError
|
|
4403
|
+
) {
|
|
4346
4404
|
/** Same treatment as the primary path: a fallback stream that
|
|
4347
4405
|
* trips the breaker must stop parallel agent nodes' model calls
|
|
4348
4406
|
* and subagents before the rejection propagates. */
|
package/src/langfuse.ts
CHANGED
|
@@ -895,6 +895,18 @@ function mergeLangfuseTags(
|
|
|
895
895
|
return merged.length > 0 ? [...new Set(merged)] : undefined;
|
|
896
896
|
}
|
|
897
897
|
|
|
898
|
+
/**
|
|
899
|
+
* The trace's user identity: the host-configured `langfuse.userId` when
|
|
900
|
+
* present, else the caller's (normally `configurable.user_id`).
|
|
901
|
+
*/
|
|
902
|
+
export function resolveLangfuseTraceUserId(
|
|
903
|
+
langfuse: t.LangfuseConfig | undefined,
|
|
904
|
+
userId: string | undefined
|
|
905
|
+
): string | undefined {
|
|
906
|
+
const configured = langfuse?.userId?.trim();
|
|
907
|
+
return isPresent(configured) ? configured : userId;
|
|
908
|
+
}
|
|
909
|
+
|
|
898
910
|
export function getLangfuseTraceName(
|
|
899
911
|
traceMetadata?: LangfuseTraceMetadata,
|
|
900
912
|
fallback: string = 'LibreChat Agent'
|
|
@@ -942,7 +954,7 @@ export function createLangfuseHandler({
|
|
|
942
954
|
return undefined;
|
|
943
955
|
}
|
|
944
956
|
return new ScopedLangfuseCallbackHandler({
|
|
945
|
-
userId,
|
|
957
|
+
userId: resolveLangfuseTraceUserId(langfuse, userId),
|
|
946
958
|
sessionId,
|
|
947
959
|
traceMetadata:
|
|
948
960
|
inheritTraceIdentity === true
|
|
@@ -972,7 +984,10 @@ function createPropagateAttributeParams({
|
|
|
972
984
|
inheritTraceIdentity,
|
|
973
985
|
}: LangfuseAttributeParams): PropagateAttributesParams {
|
|
974
986
|
return {
|
|
975
|
-
userId:
|
|
987
|
+
userId:
|
|
988
|
+
inheritTraceIdentity === true
|
|
989
|
+
? undefined
|
|
990
|
+
: resolveLangfuseTraceUserId(langfuse, userId),
|
|
976
991
|
sessionId: inheritTraceIdentity === true ? undefined : sessionId,
|
|
977
992
|
traceName: inheritTraceIdentity === true ? undefined : traceName,
|
|
978
993
|
tags: mergeLangfuseTags(tags, langfuse?.tags),
|
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
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { ArtifactDeliveryFailure } from '@/types';
|
|
2
|
+
|
|
3
|
+
export const ARTIFACT_DELIVERY_WARNING_PREFIX = 'Artifact delivery warning:';
|
|
4
|
+
|
|
5
|
+
function isNonNegativeInteger(value: unknown): value is number {
|
|
6
|
+
return typeof value === 'number' && Number.isInteger(value) && value >= 0;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function normalizeArtifactDeliveryFailure(
|
|
10
|
+
value: unknown
|
|
11
|
+
): ArtifactDeliveryFailure | undefined {
|
|
12
|
+
if (value == null || typeof value !== 'object' || Array.isArray(value)) {
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const candidate = value as Partial<ArtifactDeliveryFailure>;
|
|
17
|
+
if (
|
|
18
|
+
candidate.code !== 'artifact_delivery_failed' ||
|
|
19
|
+
(candidate.status !== 'partial' && candidate.status !== 'failed') ||
|
|
20
|
+
!isNonNegativeInteger(candidate.attempted) ||
|
|
21
|
+
!isNonNegativeInteger(candidate.delivered) ||
|
|
22
|
+
!isNonNegativeInteger(candidate.failed) ||
|
|
23
|
+
candidate.failed === 0 ||
|
|
24
|
+
candidate.attempted !== candidate.delivered + candidate.failed ||
|
|
25
|
+
(candidate.status === 'failed' && candidate.delivered !== 0) ||
|
|
26
|
+
(candidate.status === 'partial' && candidate.delivered === 0)
|
|
27
|
+
) {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
code: candidate.code,
|
|
33
|
+
status: candidate.status,
|
|
34
|
+
attempted: candidate.attempted,
|
|
35
|
+
delivered: candidate.delivered,
|
|
36
|
+
failed: candidate.failed,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function appendArtifactDeliveryWarning(
|
|
41
|
+
output: string,
|
|
42
|
+
delivery: ArtifactDeliveryFailure | undefined
|
|
43
|
+
): string {
|
|
44
|
+
if (delivery == null) {
|
|
45
|
+
return output;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const warning = `${ARTIFACT_DELIVERY_WARNING_PREFIX} ${delivery.failed} of ${delivery.attempted} generated files could not be persisted. Do not assume missing files are available to later calls or downloadable. The code itself ran; do not rerun automatically because it may have had side effects.`;
|
|
49
|
+
return `${output.trimEnd()}\n${warning}\n`;
|
|
50
|
+
}
|
|
@@ -18,6 +18,10 @@ import {
|
|
|
18
18
|
resolveCodeApiAuthHeaders,
|
|
19
19
|
selectRuntimeSessionHint,
|
|
20
20
|
} from './CodeExecutor';
|
|
21
|
+
import {
|
|
22
|
+
appendArtifactDeliveryWarning,
|
|
23
|
+
normalizeArtifactDeliveryFailure,
|
|
24
|
+
} from '@/tools/ArtifactDelivery';
|
|
21
25
|
import { logCodeApiDiagnostic } from '@/tools/diagnostics';
|
|
22
26
|
import { resolveFetchProxyAgent } from '@/utils/proxy';
|
|
23
27
|
import { INTENT_PROPERTY } from '@/tools/intentArg';
|
|
@@ -300,6 +304,13 @@ function createBashExecutionTool(
|
|
|
300
304
|
formattedOutput,
|
|
301
305
|
command
|
|
302
306
|
);
|
|
307
|
+
const artifactDelivery = normalizeArtifactDeliveryFailure(
|
|
308
|
+
result.artifact_delivery
|
|
309
|
+
);
|
|
310
|
+
const outputWithDeliveryWarning = appendArtifactDeliveryWarning(
|
|
311
|
+
outputWithReminder,
|
|
312
|
+
artifactDelivery
|
|
313
|
+
);
|
|
303
314
|
const hasFiles = result.files != null && result.files.length > 0;
|
|
304
315
|
const runtimeEcho =
|
|
305
316
|
result.runtime_session_id != null
|
|
@@ -309,15 +320,21 @@ function createBashExecutionTool(
|
|
|
309
320
|
}
|
|
310
321
|
: {};
|
|
311
322
|
return [
|
|
312
|
-
appendCodeSessionFileSummary(
|
|
323
|
+
appendCodeSessionFileSummary(outputWithDeliveryWarning, result.files),
|
|
313
324
|
(hasFiles
|
|
314
325
|
? {
|
|
315
326
|
session_id: result.session_id,
|
|
316
327
|
files: result.files,
|
|
328
|
+
...(artifactDelivery != null
|
|
329
|
+
? { artifact_delivery: artifactDelivery }
|
|
330
|
+
: {}),
|
|
317
331
|
...runtimeEcho,
|
|
318
332
|
}
|
|
319
333
|
: {
|
|
320
334
|
session_id: result.session_id,
|
|
335
|
+
...(artifactDelivery != null
|
|
336
|
+
? { artifact_delivery: artifactDelivery }
|
|
337
|
+
: {}),
|
|
321
338
|
...runtimeEcho,
|
|
322
339
|
}) satisfies t.CodeExecutionArtifact,
|
|
323
340
|
];
|
|
@@ -5,6 +5,10 @@ import { tool, DynamicStructuredTool } from '@langchain/core/tools';
|
|
|
5
5
|
import type { Readable } from 'node:stream';
|
|
6
6
|
import type { CodeApiMethod } from '@/tools/diagnostics';
|
|
7
7
|
import type * as t from '@/types';
|
|
8
|
+
import {
|
|
9
|
+
appendArtifactDeliveryWarning,
|
|
10
|
+
normalizeArtifactDeliveryFailure,
|
|
11
|
+
} from '@/tools/ArtifactDelivery';
|
|
8
12
|
import {
|
|
9
13
|
describeCodeApiError,
|
|
10
14
|
logCodeApiDiagnostic,
|
|
@@ -564,6 +568,13 @@ function createCodeExecutionTool(
|
|
|
564
568
|
formattedOutput,
|
|
565
569
|
code
|
|
566
570
|
);
|
|
571
|
+
const artifactDelivery = normalizeArtifactDeliveryFailure(
|
|
572
|
+
result.artifact_delivery
|
|
573
|
+
);
|
|
574
|
+
const outputWithDeliveryWarning = appendArtifactDeliveryWarning(
|
|
575
|
+
outputWithReminder,
|
|
576
|
+
artifactDelivery
|
|
577
|
+
);
|
|
567
578
|
const hasFiles = result.files != null && result.files.length > 0;
|
|
568
579
|
/* Echo the durable runtime session (stateful backends only) so hosts
|
|
569
580
|
* can surface a "session active / was reset" signal later. Additive:
|
|
@@ -576,15 +587,21 @@ function createCodeExecutionTool(
|
|
|
576
587
|
}
|
|
577
588
|
: {};
|
|
578
589
|
return [
|
|
579
|
-
appendCodeSessionFileSummary(
|
|
590
|
+
appendCodeSessionFileSummary(outputWithDeliveryWarning, result.files),
|
|
580
591
|
(hasFiles
|
|
581
592
|
? {
|
|
582
593
|
session_id: result.session_id,
|
|
583
594
|
files: result.files,
|
|
595
|
+
...(artifactDelivery != null
|
|
596
|
+
? { artifact_delivery: artifactDelivery }
|
|
597
|
+
: {}),
|
|
584
598
|
...runtimeEcho,
|
|
585
599
|
}
|
|
586
600
|
: {
|
|
587
601
|
session_id: result.session_id,
|
|
602
|
+
...(artifactDelivery != null
|
|
603
|
+
? { artifact_delivery: artifactDelivery }
|
|
604
|
+
: {}),
|
|
588
605
|
...runtimeEcho,
|
|
589
606
|
}) satisfies t.CodeExecutionArtifact,
|
|
590
607
|
];
|