@librechat/agents 3.3.13 → 3.4.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/graphs/Graph.cjs +121 -27
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/hooks/HookRegistry.cjs +83 -0
- package/dist/cjs/hooks/HookRegistry.cjs.map +1 -1
- package/dist/cjs/hooks/executeHooks.cjs +60 -14
- package/dist/cjs/hooks/executeHooks.cjs.map +1 -1
- package/dist/cjs/hooks/index.cjs.map +1 -1
- package/dist/cjs/hooks/types.cjs +2 -0
- package/dist/cjs/hooks/types.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/index.cjs +35 -206
- package/dist/cjs/llm/anthropic/index.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/index.cjs +121 -241
- package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
- package/dist/cjs/llm/google/index.cjs +19 -12
- package/dist/cjs/llm/google/index.cjs.map +1 -1
- package/dist/cjs/llm/init.cjs +3 -3
- package/dist/cjs/llm/invoke.cjs +2 -2
- package/dist/cjs/llm/mistral/index.cjs +26 -0
- package/dist/cjs/llm/mistral/index.cjs.map +1 -0
- package/dist/cjs/llm/openai/index.cjs +82 -80
- package/dist/cjs/llm/openai/index.cjs.map +1 -1
- package/dist/cjs/llm/openrouter/index.cjs.map +1 -1
- package/dist/cjs/llm/providers.cjs +19 -19
- package/dist/cjs/llm/providers.cjs.map +1 -1
- package/dist/cjs/llm/stream/chunkAdapters.cjs +198 -0
- package/dist/cjs/llm/stream/chunkAdapters.cjs.map +1 -0
- package/dist/cjs/llm/stream/smoother.cjs +369 -0
- package/dist/cjs/llm/stream/smoother.cjs.map +1 -0
- package/dist/cjs/llm/vertexai/index.cjs +13 -1
- package/dist/cjs/llm/vertexai/index.cjs.map +1 -1
- package/dist/cjs/main.cjs +25 -16
- package/dist/cjs/messages/format.cjs +40 -23
- package/dist/cjs/messages/format.cjs.map +1 -1
- package/dist/cjs/run.cjs +82 -15
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/session/AgentSession.cjs +37 -2
- package/dist/cjs/session/AgentSession.cjs.map +1 -1
- package/dist/cjs/stream.cjs +1 -1
- package/dist/cjs/summarization/node.cjs +6 -3
- package/dist/cjs/summarization/node.cjs.map +1 -1
- package/dist/cjs/tools/BashExecutor.cjs +1 -1
- package/dist/cjs/tools/CodeExecutor.cjs +1 -1
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs +1 -1
- package/dist/cjs/tools/ToolNode.cjs +316 -94
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/ToolSearch.cjs +1 -1
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs +664 -80
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
- package/dist/cjs/tools/subagent/SubagentReplay.cjs +175 -0
- package/dist/cjs/tools/subagent/SubagentReplay.cjs.map +1 -0
- package/dist/cjs/tools/toolOutputReferences.cjs +20 -0
- package/dist/cjs/tools/toolOutputReferences.cjs.map +1 -1
- package/dist/cjs/utils/index.cjs +2 -2
- package/dist/cjs/utils/tokens.cjs +76 -29
- package/dist/cjs/utils/tokens.cjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +121 -27
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/hooks/HookRegistry.mjs +83 -0
- package/dist/esm/hooks/HookRegistry.mjs.map +1 -1
- package/dist/esm/hooks/executeHooks.mjs +60 -14
- package/dist/esm/hooks/executeHooks.mjs.map +1 -1
- package/dist/esm/hooks/index.mjs.map +1 -1
- package/dist/esm/hooks/types.mjs +2 -1
- package/dist/esm/hooks/types.mjs.map +1 -1
- package/dist/esm/llm/anthropic/index.mjs +34 -205
- package/dist/esm/llm/anthropic/index.mjs.map +1 -1
- package/dist/esm/llm/bedrock/index.mjs +120 -240
- package/dist/esm/llm/bedrock/index.mjs.map +1 -1
- package/dist/esm/llm/google/index.mjs +19 -12
- package/dist/esm/llm/google/index.mjs.map +1 -1
- package/dist/esm/llm/init.mjs +1 -1
- package/dist/esm/llm/invoke.mjs +2 -2
- package/dist/esm/llm/mistral/index.mjs +26 -0
- package/dist/esm/llm/mistral/index.mjs.map +1 -0
- package/dist/esm/llm/openai/index.mjs +82 -80
- package/dist/esm/llm/openai/index.mjs.map +1 -1
- package/dist/esm/llm/openrouter/index.mjs.map +1 -1
- package/dist/esm/llm/providers.mjs +3 -3
- package/dist/esm/llm/providers.mjs.map +1 -1
- package/dist/esm/llm/stream/chunkAdapters.mjs +195 -0
- package/dist/esm/llm/stream/chunkAdapters.mjs.map +1 -0
- package/dist/esm/llm/stream/smoother.mjs +365 -0
- package/dist/esm/llm/stream/smoother.mjs.map +1 -0
- package/dist/esm/llm/vertexai/index.mjs +13 -1
- package/dist/esm/llm/vertexai/index.mjs.map +1 -1
- package/dist/esm/main.mjs +13 -11
- package/dist/esm/messages/format.mjs +40 -23
- package/dist/esm/messages/format.mjs.map +1 -1
- package/dist/esm/run.mjs +83 -16
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/session/AgentSession.mjs +37 -2
- package/dist/esm/session/AgentSession.mjs.map +1 -1
- package/dist/esm/stream.mjs +1 -1
- package/dist/esm/summarization/node.mjs +6 -3
- package/dist/esm/summarization/node.mjs.map +1 -1
- package/dist/esm/tools/BashExecutor.mjs +1 -1
- package/dist/esm/tools/CodeExecutor.mjs +1 -1
- package/dist/esm/tools/ProgrammaticToolCalling.mjs +1 -1
- package/dist/esm/tools/ToolNode.mjs +317 -95
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/ToolSearch.mjs +1 -1
- package/dist/esm/tools/subagent/SubagentExecutor.mjs +665 -81
- package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
- package/dist/esm/tools/subagent/SubagentReplay.mjs +168 -0
- package/dist/esm/tools/subagent/SubagentReplay.mjs.map +1 -0
- package/dist/esm/tools/toolOutputReferences.mjs +20 -0
- package/dist/esm/tools/toolOutputReferences.mjs.map +1 -1
- package/dist/esm/utils/index.mjs +2 -2
- package/dist/esm/utils/tokens.mjs +76 -30
- package/dist/esm/utils/tokens.mjs.map +1 -1
- package/dist/types/graphs/Graph.d.ts +19 -1
- package/dist/types/hooks/HookRegistry.d.ts +10 -1
- package/dist/types/hooks/executeHooks.d.ts +5 -1
- package/dist/types/hooks/index.d.ts +2 -2
- package/dist/types/hooks/types.d.ts +12 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/llm/google/index.d.ts +2 -0
- package/dist/types/llm/mistral/index.d.ts +11 -0
- package/dist/types/llm/openai/index.d.ts +20 -4
- package/dist/types/llm/openrouter/index.d.ts +4 -1
- package/dist/types/llm/stream/chunkAdapters.d.ts +48 -0
- package/dist/types/llm/stream/smoother.d.ts +95 -0
- package/dist/types/llm/vertexai/index.d.ts +2 -0
- package/dist/types/run.d.ts +4 -1
- package/dist/types/session/AgentSession.d.ts +1 -0
- package/dist/types/tools/ToolNode.d.ts +23 -15
- package/dist/types/tools/subagent/SubagentExecutor.d.ts +49 -7
- package/dist/types/tools/subagent/SubagentReplay.d.ts +81 -0
- package/dist/types/tools/toolOutputReferences.d.ts +12 -0
- package/dist/types/types/graph.d.ts +5 -5
- package/dist/types/types/hitl.d.ts +15 -0
- package/dist/types/types/llm.d.ts +21 -14
- package/dist/types/utils/tokens.d.ts +10 -0
- package/package.json +6 -2
- package/src/graphs/Graph.ts +257 -52
- package/src/graphs/__tests__/Graph.breakerLifecycle.test.ts +48 -9
- package/src/graphs/__tests__/Graph.contextOverflow.test.ts +1 -1
- package/src/graphs/__tests__/Graph.subagentResumeState.test.ts +80 -0
- package/src/hooks/HookRegistry.ts +151 -1
- package/src/hooks/__tests__/HookRegistry.test.ts +96 -0
- package/src/hooks/__tests__/executeHooks.test.ts +160 -0
- package/src/hooks/executeHooks.ts +133 -15
- package/src/hooks/index.ts +3 -1
- package/src/hooks/types.ts +16 -0
- package/src/index.ts +8 -0
- package/src/llm/anthropic/index.ts +85 -354
- package/src/llm/bedrock/index.ts +174 -390
- package/src/llm/bedrock/llm.spec.ts +2 -0
- package/src/llm/bedrock/streamSealDispatch.test.ts +76 -0
- package/src/llm/custom-chat-models.smoke.test.ts +16 -1
- package/src/llm/google/index.ts +17 -18
- package/src/llm/google/streamSmoothing.test.ts +121 -0
- package/src/llm/mistral/index.ts +33 -0
- package/src/llm/mistral/streamSmoothing.test.ts +97 -0
- package/src/llm/openai/deepseek.test.ts +56 -0
- package/src/llm/openai/index.ts +119 -126
- package/src/llm/openrouter/index.ts +4 -1
- package/src/llm/providers.ts +3 -3
- package/src/llm/stream/chunkAdapters.test.ts +202 -0
- package/src/llm/stream/chunkAdapters.ts +317 -0
- package/src/llm/stream/reassembly.test.ts +241 -0
- package/src/llm/stream/smoother.bench.test.ts +155 -0
- package/src/llm/stream/smoother.test.ts +519 -0
- package/src/llm/stream/smoother.ts +574 -0
- package/src/llm/vertexai/index.ts +17 -1
- package/src/llm/vertexai/streamSmoothing.test.ts +109 -0
- package/src/messages/format.ts +54 -26
- package/src/messages/formatAgentMessages.reducer.test.ts +162 -0
- package/src/messages/formatAgentMessages.steer.test.ts +16 -11
- package/src/messages/formatAgentMessages.test.ts +7 -2
- package/src/run.ts +164 -18
- package/src/session/AgentSession.ts +52 -2
- package/src/session/__tests__/JsonlSessionStore.test.ts +53 -0
- package/src/specs/subagent.test.ts +44 -0
- package/src/specs/tokens.test.ts +159 -14
- package/src/summarization/__tests__/node.test.ts +32 -14
- package/src/summarization/node.ts +15 -11
- package/src/tools/ToolNode.ts +631 -157
- package/src/tools/__tests__/SubagentExecutor.test.ts +590 -7
- package/src/tools/__tests__/SubagentReplay.test.ts +300 -0
- package/src/tools/__tests__/ToolNode.breakerSignal.test.ts +208 -2
- package/src/tools/__tests__/directToolHITLResumeScope.test.ts +486 -2
- package/src/tools/__tests__/hitl.test.ts +234 -5
- package/src/tools/__tests__/subagentHooks.test.ts +1040 -62
- package/src/tools/__tests__/toolOutputReferences.test.ts +19 -2
- package/src/tools/subagent/SubagentExecutor.ts +1438 -136
- package/src/tools/subagent/SubagentReplay.ts +575 -0
- package/src/tools/toolOutputReferences.ts +40 -1
- package/src/types/graph.ts +5 -5
- package/src/types/hitl.ts +16 -0
- package/src/types/llm.ts +53 -36
- package/src/utils/tokens.ts +115 -30
|
@@ -1,27 +1,206 @@
|
|
|
1
|
+
const require_SubagentReplay = require("./SubagentReplay.cjs");
|
|
1
2
|
require("../../common/enum.cjs");
|
|
2
3
|
require("../../common/index.cjs");
|
|
3
4
|
const require_streamLimits = require("../../llm/streamLimits.cjs");
|
|
4
5
|
const require_executeHooks = require("../../hooks/executeHooks.cjs");
|
|
6
|
+
const require_types = require("../../hooks/types.cjs");
|
|
5
7
|
require("../../hooks/index.cjs");
|
|
6
8
|
let nanoid = require("nanoid");
|
|
7
9
|
let _langchain_core_callbacks_base = require("@langchain/core/callbacks/base");
|
|
8
10
|
let _langchain_core_messages = require("@langchain/core/messages");
|
|
11
|
+
let _langchain_langgraph = require("@langchain/langgraph");
|
|
12
|
+
let _langchain_core_singletons = require("@langchain/core/singletons");
|
|
9
13
|
//#region src/tools/subagent/SubagentExecutor.ts
|
|
10
14
|
const DEFAULT_MAX_TURNS = 25;
|
|
11
15
|
const RECURSION_MULTIPLIER = 3;
|
|
12
16
|
const ERROR_MESSAGE_MAX_CHARS = 200;
|
|
13
17
|
const MAX_PENDING_SUBAGENT_UPDATES = 64;
|
|
18
|
+
const TEXT_DELTA_CONTENT_TYPE = `text_delta`;
|
|
14
19
|
const HOOK_FALLBACK = Object.freeze({
|
|
15
20
|
additionalContexts: [],
|
|
16
21
|
injectedMessages: [],
|
|
17
22
|
errors: []
|
|
18
23
|
});
|
|
19
24
|
const LANGGRAPH_RUNTIME_CONFIG_PREFIX = "__pregel_";
|
|
25
|
+
const LANGGRAPH_RESUME_MAP_CONFIG_KEY = "__pregel_resume_map";
|
|
20
26
|
const LANGGRAPH_CHECKPOINT_CONFIG_KEYS = new Set([
|
|
21
27
|
"checkpoint_id",
|
|
22
28
|
"checkpoint_map",
|
|
23
29
|
"checkpoint_ns"
|
|
24
30
|
]);
|
|
31
|
+
const SUBAGENT_CHECKPOINT_MARKER_KEY = "__librechat_subagent_checkpoint";
|
|
32
|
+
const SUBAGENT_HOOK_SESSION_KEY = "__librechat_subagent_hook_session";
|
|
33
|
+
const SUBAGENT_RUN_ID_KEY = "__librechat_subagent_run_id";
|
|
34
|
+
const SUBAGENT_REPLAY_NODE = "subagent-replay";
|
|
35
|
+
function isCheckpointSaver(value) {
|
|
36
|
+
if (value == null || typeof value !== "object") return false;
|
|
37
|
+
const candidate = value;
|
|
38
|
+
return typeof candidate.getTuple === "function" && typeof candidate.list === "function" && typeof candidate.put === "function" && typeof candidate.putWrites === "function" && typeof candidate.deleteThread === "function";
|
|
39
|
+
}
|
|
40
|
+
function isSubagentCheckpointMarker(value) {
|
|
41
|
+
if (value == null || typeof value !== "object") return false;
|
|
42
|
+
const marker = value;
|
|
43
|
+
return marker.version === 1 && marker.lifecycleComplete === true && typeof marker.parentToolCallId === "string" && (marker.hookSessionId == null || typeof marker.hookSessionId === "string") && (marker.childRunId == null || typeof marker.childRunId === "string") && (marker.settledOutput == null || isPersistedToolOutput(marker.settledOutput));
|
|
44
|
+
}
|
|
45
|
+
function isPersistedToolOutput(value) {
|
|
46
|
+
if (value == null || typeof value !== "object") return false;
|
|
47
|
+
const output = value;
|
|
48
|
+
const contentIsValid = typeof output.content === "string" || Array.isArray(output.content);
|
|
49
|
+
const statusIsValid = output.status == null || output.status === "success" || output.status === "error";
|
|
50
|
+
const contextsAreValid = Array.isArray(output.additionalContexts) && output.additionalContexts.every((context) => typeof context === "string");
|
|
51
|
+
const resolvedArgsAreValid = output.resolvedArgs == null || typeof output.resolvedArgs === "object" && !Array.isArray(output.resolvedArgs);
|
|
52
|
+
return contentIsValid && typeof output.toolCallId === "string" && output.additionalKwargs != null && typeof output.additionalKwargs === "object" && output.responseMetadata != null && typeof output.responseMetadata === "object" && statusIsValid && contextsAreValid && resolvedArgsAreValid && (output.referenceContent == null || typeof output.referenceContent === "string");
|
|
53
|
+
}
|
|
54
|
+
function getSubagentCheckpointMarker(messages, parentToolCallId) {
|
|
55
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
56
|
+
const marker = messages[i].additional_kwargs[SUBAGENT_CHECKPOINT_MARKER_KEY];
|
|
57
|
+
if (isSubagentCheckpointMarker(marker) && marker.parentToolCallId === parentToolCallId) return marker;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function getSubagentHookSessionId(messages) {
|
|
61
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
62
|
+
const sessionId = messages[i].additional_kwargs[SUBAGENT_HOOK_SESSION_KEY];
|
|
63
|
+
if (typeof sessionId === "string" && sessionId.length > 0) return sessionId;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function getSubagentRunId(messages) {
|
|
67
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
68
|
+
const runId = messages[i].additional_kwargs[SUBAGENT_RUN_ID_KEY];
|
|
69
|
+
if (typeof runId === "string" && runId.length > 0) return runId;
|
|
70
|
+
const marker = messages[i].additional_kwargs[SUBAGENT_CHECKPOINT_MARKER_KEY];
|
|
71
|
+
if (isSubagentCheckpointMarker(marker) && marker.childRunId != null && marker.childRunId.length > 0) return marker.childRunId;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function isSubagentCheckpointMarkerMessage(message) {
|
|
75
|
+
return isSubagentCheckpointMarker(message.additional_kwargs[SUBAGENT_CHECKPOINT_MARKER_KEY]);
|
|
76
|
+
}
|
|
77
|
+
function createSubagentCheckpointMarkerMessage(marker) {
|
|
78
|
+
return new _langchain_core_messages.AIMessage({
|
|
79
|
+
content: "",
|
|
80
|
+
additional_kwargs: { [SUBAGENT_CHECKPOINT_MARKER_KEY]: marker }
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
function getCheckpointMessages(value) {
|
|
84
|
+
return Array.isArray(value) ? value.filter(_langchain_core_messages.BaseMessage.isInstance) : [];
|
|
85
|
+
}
|
|
86
|
+
function getConfigurableString(config, key) {
|
|
87
|
+
const value = config?.configurable?.[key];
|
|
88
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
89
|
+
}
|
|
90
|
+
function getTupleMessages(tuple) {
|
|
91
|
+
return getCheckpointMessages(tuple?.checkpoint.channel_values.messages);
|
|
92
|
+
}
|
|
93
|
+
function getCheckpointReference(tuple) {
|
|
94
|
+
const configurable = tuple?.config.configurable;
|
|
95
|
+
const threadId = configurable?.thread_id;
|
|
96
|
+
const checkpointId = configurable?.checkpoint_id;
|
|
97
|
+
const checkpointNs = configurable?.checkpoint_ns ?? "";
|
|
98
|
+
if (typeof threadId !== "string" || threadId.length === 0 || typeof checkpointId !== "string" || checkpointId.length === 0 || typeof checkpointNs !== "string") return;
|
|
99
|
+
return {
|
|
100
|
+
threadId,
|
|
101
|
+
checkpointId,
|
|
102
|
+
checkpointNs
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function serializeToolOutput(settled) {
|
|
106
|
+
const { output } = settled;
|
|
107
|
+
return {
|
|
108
|
+
content: output.content,
|
|
109
|
+
toolCallId: output.tool_call_id,
|
|
110
|
+
...output.id == null ? {} : { id: output.id },
|
|
111
|
+
...output.name == null ? {} : { name: output.name },
|
|
112
|
+
...output.status == null ? {} : { status: output.status },
|
|
113
|
+
additionalKwargs: output.additional_kwargs,
|
|
114
|
+
responseMetadata: output.response_metadata,
|
|
115
|
+
...output.metadata == null ? {} : { metadata: output.metadata },
|
|
116
|
+
additionalContexts: settled.additionalContexts,
|
|
117
|
+
...settled.resolvedArgs == null ? {} : { resolvedArgs: settled.resolvedArgs },
|
|
118
|
+
...settled.referenceContent == null ? {} : { referenceContent: settled.referenceContent }
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
function deserializeToolOutput(output) {
|
|
122
|
+
return {
|
|
123
|
+
output: new _langchain_core_messages.ToolMessage({
|
|
124
|
+
content: output.content,
|
|
125
|
+
tool_call_id: output.toolCallId,
|
|
126
|
+
...output.id == null ? {} : { id: output.id },
|
|
127
|
+
...output.name == null ? {} : { name: output.name },
|
|
128
|
+
...output.status == null ? {} : { status: output.status },
|
|
129
|
+
additional_kwargs: output.additionalKwargs,
|
|
130
|
+
response_metadata: output.responseMetadata,
|
|
131
|
+
...output.metadata == null ? {} : { metadata: output.metadata }
|
|
132
|
+
}),
|
|
133
|
+
additionalContexts: output.additionalContexts,
|
|
134
|
+
...output.resolvedArgs == null ? {} : { resolvedArgs: output.resolvedArgs },
|
|
135
|
+
...output.referenceContent == null ? {} : { referenceContent: output.referenceContent }
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function getParentCheckpointFork(configurable) {
|
|
139
|
+
const checkpointId = configurable?.checkpoint_id;
|
|
140
|
+
return typeof checkpointId === "string" && checkpointId.length > 0 ? checkpointId : "root";
|
|
141
|
+
}
|
|
142
|
+
function getResumeAttemptId(configurable, fallback) {
|
|
143
|
+
const attemptId = configurable?.[require_SubagentReplay.SUBAGENT_RESUME_ATTEMPT_CONFIG_KEY];
|
|
144
|
+
return typeof attemptId === "string" && attemptId.length > 0 ? attemptId : fallback;
|
|
145
|
+
}
|
|
146
|
+
function getApprovalExecutionScope(childRunId, resumeAttemptId) {
|
|
147
|
+
return `subagent-approval:${Buffer.from(JSON.stringify([childRunId, resumeAttemptId])).toString("base64url")}`;
|
|
148
|
+
}
|
|
149
|
+
function getChildThreadId(args) {
|
|
150
|
+
const durableParentId = args.threadId ?? args.parentRunId;
|
|
151
|
+
const parentFork = getParentCheckpointFork(args.parentConfigurable);
|
|
152
|
+
const parentBatch = args.parentConfigurable?.[require_SubagentReplay.SUBAGENT_PARENT_BATCH_CONFIG_KEY];
|
|
153
|
+
const identity = [
|
|
154
|
+
durableParentId,
|
|
155
|
+
parentFork,
|
|
156
|
+
args.parentAgentId ?? "agent",
|
|
157
|
+
args.parentToolCallId,
|
|
158
|
+
typeof parentBatch === "string" && parentBatch.length > 0 ? parentBatch : "batch"
|
|
159
|
+
];
|
|
160
|
+
if (args.branchId != null) identity.push(args.branchId);
|
|
161
|
+
return `subagent:${Buffer.from(JSON.stringify(identity)).toString("base64url")}`;
|
|
162
|
+
}
|
|
163
|
+
function isToolApprovalPayload(value) {
|
|
164
|
+
return value != null && typeof value === "object" && value.type === "tool_approval";
|
|
165
|
+
}
|
|
166
|
+
function addSubagentScope(interrupts, scope, resumeManifest) {
|
|
167
|
+
return interrupts.map((childInterrupt) => {
|
|
168
|
+
const payload = isToolApprovalPayload(childInterrupt.value) ? {
|
|
169
|
+
...childInterrupt.value,
|
|
170
|
+
subagent: childInterrupt.value.subagent ?? scope
|
|
171
|
+
} : childInterrupt.value;
|
|
172
|
+
return {
|
|
173
|
+
...childInterrupt,
|
|
174
|
+
value: resumeManifest == null ? payload : require_SubagentReplay.attachSubagentResumeManifest(payload, resumeManifest)
|
|
175
|
+
};
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
function getChildResumeMap(pendingInterrupts, parentConfigurable) {
|
|
179
|
+
const resumeMap = parentConfigurable?.[LANGGRAPH_RESUME_MAP_CONFIG_KEY];
|
|
180
|
+
if (resumeMap == null || typeof resumeMap !== "object") return;
|
|
181
|
+
const parentResumeMap = resumeMap;
|
|
182
|
+
const childResumeMap = {};
|
|
183
|
+
for (const childInterrupt of pendingInterrupts) {
|
|
184
|
+
const interruptId = childInterrupt.id;
|
|
185
|
+
if (typeof interruptId === "string" && Object.prototype.hasOwnProperty.call(parentResumeMap, interruptId)) childResumeMap[interruptId] = parentResumeMap[interruptId];
|
|
186
|
+
}
|
|
187
|
+
return Object.keys(childResumeMap).length > 0 ? childResumeMap : void 0;
|
|
188
|
+
}
|
|
189
|
+
function getPersistedInterrupts(snapshot) {
|
|
190
|
+
const interrupts = [];
|
|
191
|
+
for (const task of snapshot.tasks) for (const pendingInterrupt of task.interrupts) interrupts.push(pendingInterrupt);
|
|
192
|
+
return interrupts;
|
|
193
|
+
}
|
|
194
|
+
function getPersistedMessages(snapshot) {
|
|
195
|
+
if (snapshot.values == null || typeof snapshot.values !== "object") return;
|
|
196
|
+
const values = snapshot.values;
|
|
197
|
+
if (!Array.isArray(values.messages) || values.messages.length === 0) return;
|
|
198
|
+
const messages = values.messages.filter((message) => !isSubagentCheckpointMarkerMessage(message));
|
|
199
|
+
return messages.length > 0 ? messages : void 0;
|
|
200
|
+
}
|
|
201
|
+
function createReplayCheckpointWorkflow(checkpointer) {
|
|
202
|
+
return new _langchain_langgraph.StateGraph(_langchain_langgraph.MessagesAnnotation).addNode(SUBAGENT_REPLAY_NODE, (state) => state).addEdge(_langchain_langgraph.START, SUBAGENT_REPLAY_NODE).addEdge(SUBAGENT_REPLAY_NODE, _langchain_langgraph.END).compile({ checkpointer });
|
|
203
|
+
}
|
|
25
204
|
var SubagentExecutor = class {
|
|
26
205
|
configs;
|
|
27
206
|
parentSignal;
|
|
@@ -39,9 +218,18 @@ var SubagentExecutor = class {
|
|
|
39
218
|
langfuse;
|
|
40
219
|
tokenCounter;
|
|
41
220
|
streamLimits;
|
|
221
|
+
humanInTheLoop;
|
|
222
|
+
checkpointer;
|
|
42
223
|
maxDepth;
|
|
43
224
|
createChildGraph;
|
|
44
225
|
usageSink;
|
|
226
|
+
checkpointThreadIds = /* @__PURE__ */ new Set();
|
|
227
|
+
startedChildRuns = /* @__PURE__ */ new Set();
|
|
228
|
+
completedChildRuns = /* @__PURE__ */ new Set();
|
|
229
|
+
completedChildResults = /* @__PURE__ */ new Map();
|
|
230
|
+
childExecutionIdentities = /* @__PURE__ */ new Map();
|
|
231
|
+
activeChildRuns = /* @__PURE__ */ new Map();
|
|
232
|
+
replayCheckpointWorkflow;
|
|
45
233
|
resolveParentHandlerRegistry;
|
|
46
234
|
constructor(options) {
|
|
47
235
|
this.configs = options.configs;
|
|
@@ -53,6 +241,8 @@ var SubagentExecutor = class {
|
|
|
53
241
|
this.langfuse = options.langfuse;
|
|
54
242
|
this.tokenCounter = options.tokenCounter;
|
|
55
243
|
this.streamLimits = options.streamLimits;
|
|
244
|
+
this.humanInTheLoop = options.humanInTheLoop;
|
|
245
|
+
this.checkpointer = isCheckpointSaver(options.checkpointer) ? options.checkpointer : void 0;
|
|
56
246
|
this.maxDepth = options.maxDepth ?? 1;
|
|
57
247
|
this.createChildGraph = options.createChildGraph;
|
|
58
248
|
this.usageSink = options.usageSink;
|
|
@@ -77,6 +267,274 @@ var SubagentExecutor = class {
|
|
|
77
267
|
getParentHandlerRegistry() {
|
|
78
268
|
return this.resolveParentHandlerRegistry?.();
|
|
79
269
|
}
|
|
270
|
+
/**
|
|
271
|
+
* Keeps the original child thread as an immutable resume source once a
|
|
272
|
+
* different parent Run reconstructs it. Each rebuilt parent gets a private
|
|
273
|
+
* checkpoint fork, while the persisted child run ID remains stable for
|
|
274
|
+
* activity and usage correlation across the interrupt boundary.
|
|
275
|
+
*/
|
|
276
|
+
async resolveChildExecutionIdentity(params) {
|
|
277
|
+
const currentChildRunId = `${this.parentRunId}_sub_${params.parentToolCallId}`;
|
|
278
|
+
const resumeAttemptId = getResumeAttemptId(params.parentConfigurable, this.parentRunId);
|
|
279
|
+
const baseChildThreadId = getChildThreadId({
|
|
280
|
+
parentRunId: this.parentRunId,
|
|
281
|
+
parentAgentId: this.parentAgentId,
|
|
282
|
+
threadId: params.threadId,
|
|
283
|
+
parentToolCallId: params.parentToolCallId,
|
|
284
|
+
parentConfigurable: params.parentConfigurable
|
|
285
|
+
});
|
|
286
|
+
if (this.humanInTheLoop?.enabled !== true || this.checkpointer == null) return {
|
|
287
|
+
childRunId: currentChildRunId,
|
|
288
|
+
childThreadId: baseChildThreadId,
|
|
289
|
+
approvalExecutionScope: currentChildRunId
|
|
290
|
+
};
|
|
291
|
+
const resumeExecution = require_SubagentReplay.getSubagentResumeManifest(params.parentConfigurable)?.executions.find((execution) => execution.parentToolCallId === params.parentToolCallId);
|
|
292
|
+
const branchChildThreadId = getChildThreadId({
|
|
293
|
+
parentRunId: this.parentRunId,
|
|
294
|
+
parentAgentId: this.parentAgentId,
|
|
295
|
+
threadId: params.threadId,
|
|
296
|
+
parentToolCallId: params.parentToolCallId,
|
|
297
|
+
parentConfigurable: params.parentConfigurable,
|
|
298
|
+
branchId: resumeAttemptId
|
|
299
|
+
});
|
|
300
|
+
if (resumeExecution != null) {
|
|
301
|
+
const approvalExecutionScope = getApprovalExecutionScope(resumeExecution.childRunId, resumeAttemptId);
|
|
302
|
+
await this.forkCheckpointSnapshot(resumeExecution.checkpoints, branchChildThreadId);
|
|
303
|
+
this.hookRegistry?.restorePendingToolApprovals(approvalExecutionScope, approvalExecutionScope, resumeExecution.approvalReplays);
|
|
304
|
+
this.checkpointThreadIds.add(branchChildThreadId);
|
|
305
|
+
return {
|
|
306
|
+
childRunId: resumeExecution.childRunId,
|
|
307
|
+
childThreadId: branchChildThreadId,
|
|
308
|
+
approvalExecutionScope,
|
|
309
|
+
resumeExecution
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
const branchTuple = await this.checkpointer.getTuple({ configurable: { thread_id: branchChildThreadId } });
|
|
313
|
+
if (branchTuple != null) {
|
|
314
|
+
const childRunId = getSubagentRunId(getTupleMessages(branchTuple)) ?? currentChildRunId;
|
|
315
|
+
this.checkpointThreadIds.add(branchChildThreadId);
|
|
316
|
+
return {
|
|
317
|
+
childRunId,
|
|
318
|
+
childThreadId: branchChildThreadId,
|
|
319
|
+
approvalExecutionScope: getApprovalExecutionScope(childRunId, resumeAttemptId)
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
const baseTuple = await this.checkpointer.getTuple({ configurable: { thread_id: baseChildThreadId } });
|
|
323
|
+
const persistedChildRunId = getSubagentRunId(getTupleMessages(baseTuple));
|
|
324
|
+
if (baseTuple == null || persistedChildRunId == null || persistedChildRunId === currentChildRunId) {
|
|
325
|
+
this.checkpointThreadIds.add(baseChildThreadId);
|
|
326
|
+
return {
|
|
327
|
+
childRunId: persistedChildRunId ?? currentChildRunId,
|
|
328
|
+
childThreadId: baseChildThreadId,
|
|
329
|
+
approvalExecutionScope: getApprovalExecutionScope(persistedChildRunId ?? currentChildRunId, resumeAttemptId)
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
const sourceCheckpoints = await this.getLatestCheckpointSnapshot(baseChildThreadId);
|
|
333
|
+
if (sourceCheckpoints.length === 0) throw new Error(`Cannot fork subagent checkpoint thread "${baseChildThreadId}" without a checkpoint ID.`);
|
|
334
|
+
await this.forkCheckpointSnapshot(sourceCheckpoints, branchChildThreadId);
|
|
335
|
+
this.checkpointThreadIds.add(branchChildThreadId);
|
|
336
|
+
return {
|
|
337
|
+
childRunId: persistedChildRunId,
|
|
338
|
+
childThreadId: branchChildThreadId,
|
|
339
|
+
approvalExecutionScope: getApprovalExecutionScope(persistedChildRunId, resumeAttemptId)
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
/** Captures one exact checkpoint head per namespace for a child thread. */
|
|
343
|
+
async getLatestCheckpointSnapshot(threadId) {
|
|
344
|
+
if (this.checkpointer == null) return [];
|
|
345
|
+
const checkpointsByNamespace = /* @__PURE__ */ new Map();
|
|
346
|
+
for await (const tuple of this.checkpointer.list({ configurable: { thread_id: threadId } })) {
|
|
347
|
+
const checkpoint = getCheckpointReference(tuple);
|
|
348
|
+
const current = checkpoint == null ? void 0 : checkpointsByNamespace.get(checkpoint.checkpointNs);
|
|
349
|
+
if (checkpoint != null && (current == null || checkpoint.checkpointId.localeCompare(current.checkpointId) > 0)) checkpointsByNamespace.set(checkpoint.checkpointNs, checkpoint);
|
|
350
|
+
}
|
|
351
|
+
return [...checkpointsByNamespace.values()].sort((left, right) => left.checkpointNs.localeCompare(right.checkpointNs));
|
|
352
|
+
}
|
|
353
|
+
/** Copies exact checkpoint lineages, including pending task writes. */
|
|
354
|
+
async forkCheckpointSnapshot(sources, targetThreadId) {
|
|
355
|
+
if (this.checkpointer == null || sources.length === 0 || sources.every((source) => source.threadId === targetThreadId)) return;
|
|
356
|
+
for (const source of sources) {
|
|
357
|
+
const tuples = [];
|
|
358
|
+
const visited = /* @__PURE__ */ new Set();
|
|
359
|
+
let tuple = await this.checkpointer.getTuple({ configurable: {
|
|
360
|
+
thread_id: source.threadId,
|
|
361
|
+
checkpoint_ns: source.checkpointNs,
|
|
362
|
+
checkpoint_id: source.checkpointId
|
|
363
|
+
} });
|
|
364
|
+
if (tuple == null) throw new Error(`Subagent checkpoint "${source.checkpointId}" is unavailable.`);
|
|
365
|
+
for (;;) {
|
|
366
|
+
const reference = getCheckpointReference(tuple);
|
|
367
|
+
if (reference == null) throw new Error("Subagent checkpoint lineage contains an invalid tuple.");
|
|
368
|
+
if (reference.threadId !== source.threadId || reference.checkpointNs !== source.checkpointNs) throw new Error("Subagent checkpoint lineage escapes its source namespace.");
|
|
369
|
+
const lineageKey = JSON.stringify([
|
|
370
|
+
reference.threadId,
|
|
371
|
+
reference.checkpointNs,
|
|
372
|
+
reference.checkpointId
|
|
373
|
+
]);
|
|
374
|
+
if (visited.has(lineageKey)) throw new Error("Subagent checkpoint lineage contains a cycle.");
|
|
375
|
+
visited.add(lineageKey);
|
|
376
|
+
tuples.push(tuple);
|
|
377
|
+
if (tuple.parentConfig == null) break;
|
|
378
|
+
tuple = await this.checkpointer.getTuple(tuple.parentConfig);
|
|
379
|
+
if (tuple == null) throw new Error("Subagent checkpoint lineage is incomplete.");
|
|
380
|
+
}
|
|
381
|
+
tuples.reverse();
|
|
382
|
+
for (const lineageTuple of tuples) {
|
|
383
|
+
const checkpointNs = getConfigurableString(lineageTuple.config, "checkpoint_ns") ?? "";
|
|
384
|
+
const parentCheckpointId = getConfigurableString(lineageTuple.parentConfig, "checkpoint_id");
|
|
385
|
+
const storedConfig = await this.checkpointer.put({ configurable: {
|
|
386
|
+
thread_id: targetThreadId,
|
|
387
|
+
checkpoint_ns: checkpointNs,
|
|
388
|
+
...parentCheckpointId == null ? {} : { checkpoint_id: parentCheckpointId }
|
|
389
|
+
} }, (0, _langchain_langgraph.copyCheckpoint)(lineageTuple.checkpoint), lineageTuple.metadata ?? {
|
|
390
|
+
source: "fork",
|
|
391
|
+
step: -1,
|
|
392
|
+
parents: {}
|
|
393
|
+
}, lineageTuple.checkpoint.channel_versions);
|
|
394
|
+
const writesByTask = /* @__PURE__ */ new Map();
|
|
395
|
+
for (const [taskId, channel, value] of lineageTuple.pendingWrites ?? []) {
|
|
396
|
+
let writes = writesByTask.get(taskId);
|
|
397
|
+
if (writes == null) {
|
|
398
|
+
writes = [];
|
|
399
|
+
writesByTask.set(taskId, writes);
|
|
400
|
+
}
|
|
401
|
+
writes.push([channel, value]);
|
|
402
|
+
}
|
|
403
|
+
for (const [taskId, writes] of writesByTask) await this.checkpointer.putWrites(storedConfig, writes, taskId);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
async createResumeManifest(parentToolCallIds) {
|
|
408
|
+
if (this.checkpointer == null) return;
|
|
409
|
+
const executions = [];
|
|
410
|
+
for (const [parentToolCallId, identity] of this.childExecutionIdentities) {
|
|
411
|
+
if (parentToolCallIds != null && !parentToolCallIds.has(parentToolCallId)) continue;
|
|
412
|
+
const checkpoints = await this.getLatestCheckpointSnapshot(identity.childThreadId);
|
|
413
|
+
if (checkpoints.length === 0) continue;
|
|
414
|
+
const activeChildRun = this.activeChildRuns.get(identity.childThreadId);
|
|
415
|
+
const configuredHookSessionId = activeChildRun?.invokeConfig?.configurable?.run_id;
|
|
416
|
+
const hookSessionId = typeof configuredHookSessionId === "string" && configuredHookSessionId.length > 0 ? configuredHookSessionId : this.parentRunId;
|
|
417
|
+
const approvalReplays = this.hookRegistry?.snapshotPendingToolApprovals(identity.approvalExecutionScope, identity.approvalExecutionScope) ?? [];
|
|
418
|
+
const descendant = activeChildRun?.pendingInterrupts.map((pendingInterrupt) => require_SubagentReplay.getSubagentResumeManifest(pendingInterrupt.value)).find((manifest) => manifest != null);
|
|
419
|
+
const graphState = activeChildRun?.graph.createSubagentResumeState(hookSessionId) ?? {
|
|
420
|
+
toolCallSteps: [],
|
|
421
|
+
toolSessions: [],
|
|
422
|
+
toolNodes: [],
|
|
423
|
+
eagerToolUsage: [],
|
|
424
|
+
eagerToolSuppressions: []
|
|
425
|
+
};
|
|
426
|
+
executions.push({
|
|
427
|
+
parentToolCallId,
|
|
428
|
+
childRunId: identity.childRunId,
|
|
429
|
+
approvalExecutionScope: identity.approvalExecutionScope,
|
|
430
|
+
checkpoints,
|
|
431
|
+
graphState,
|
|
432
|
+
approvalReplays,
|
|
433
|
+
...descendant == null ? {} : { descendant }
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
if (executions.length === 0) return;
|
|
437
|
+
return {
|
|
438
|
+
version: 1,
|
|
439
|
+
executions
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
getResumeManifest(parentToolCallIds) {
|
|
443
|
+
return this.createResumeManifest(parentToolCallIds);
|
|
444
|
+
}
|
|
445
|
+
getChildCheckpointThreadIds() {
|
|
446
|
+
const threadIds = new Set(this.checkpointThreadIds);
|
|
447
|
+
for (const activeChildRun of this.activeChildRuns.values()) for (const threadId of this.getGraphChildCheckpointThreadIds(activeChildRun.graph)) threadIds.add(threadId);
|
|
448
|
+
return [...threadIds];
|
|
449
|
+
}
|
|
450
|
+
resetCheckpointThreadIds() {
|
|
451
|
+
this.checkpointThreadIds.clear();
|
|
452
|
+
}
|
|
453
|
+
getGraphChildCheckpointThreadIds(graph) {
|
|
454
|
+
return graph.getChildCheckpointThreadIds?.() ?? [];
|
|
455
|
+
}
|
|
456
|
+
clearChildGraph(graph) {
|
|
457
|
+
for (const threadId of this.getGraphChildCheckpointThreadIds(graph)) this.checkpointThreadIds.add(threadId);
|
|
458
|
+
graph.clearHeavyState();
|
|
459
|
+
}
|
|
460
|
+
clearHeavyState() {
|
|
461
|
+
for (const activeChildRun of this.activeChildRuns.values()) this.clearChildGraph(activeChildRun.graph);
|
|
462
|
+
this.activeChildRuns.clear();
|
|
463
|
+
this.completedChildResults.clear();
|
|
464
|
+
for (const identity of this.childExecutionIdentities.values()) this.hookRegistry?.clearSession(identity.approvalExecutionScope);
|
|
465
|
+
this.childExecutionIdentities.clear();
|
|
466
|
+
this.startedChildRuns.clear();
|
|
467
|
+
this.completedChildRuns.clear();
|
|
468
|
+
this.replayCheckpointWorkflow = void 0;
|
|
469
|
+
}
|
|
470
|
+
async getSettledToolOutput(call, config) {
|
|
471
|
+
const parentToolCallId = call.id;
|
|
472
|
+
if (this.humanInTheLoop?.enabled !== true || this.checkpointer == null || parentToolCallId == null || parentToolCallId === "") return;
|
|
473
|
+
const parentConfigurable = config.configurable;
|
|
474
|
+
const threadId = parentConfigurable?.thread_id;
|
|
475
|
+
const { childRunId, childThreadId, approvalExecutionScope } = await this.resolveChildExecutionIdentity({
|
|
476
|
+
threadId: typeof threadId === "string" ? threadId : void 0,
|
|
477
|
+
parentToolCallId,
|
|
478
|
+
parentConfigurable
|
|
479
|
+
});
|
|
480
|
+
this.childExecutionIdentities.set(parentToolCallId, {
|
|
481
|
+
childRunId,
|
|
482
|
+
childThreadId,
|
|
483
|
+
approvalExecutionScope
|
|
484
|
+
});
|
|
485
|
+
this.checkpointThreadIds.add(childThreadId);
|
|
486
|
+
const messages = getCheckpointMessages((await this.checkpointer.getTuple({ configurable: { thread_id: childThreadId } }))?.checkpoint.channel_values.messages);
|
|
487
|
+
const marker = getSubagentCheckpointMarker(messages, parentToolCallId);
|
|
488
|
+
const persistedHookSessionId = marker?.hookSessionId ?? getSubagentHookSessionId(messages);
|
|
489
|
+
const currentHookSessionId = parentConfigurable?.run_id;
|
|
490
|
+
if (persistedHookSessionId != null && typeof currentHookSessionId === "string" && currentHookSessionId.length > 0) this.hookRegistry?.copySession(persistedHookSessionId, currentHookSessionId);
|
|
491
|
+
return marker?.settledOutput == null ? void 0 : deserializeToolOutput(marker.settledOutput);
|
|
492
|
+
}
|
|
493
|
+
async persistSettledToolOutput(call, config, settled) {
|
|
494
|
+
const parentToolCallId = call.id;
|
|
495
|
+
if (this.humanInTheLoop?.enabled !== true || this.checkpointer == null || parentToolCallId == null || parentToolCallId === "") return;
|
|
496
|
+
const parentConfigurable = config.configurable;
|
|
497
|
+
const threadId = parentConfigurable?.thread_id;
|
|
498
|
+
const { childRunId, childThreadId, approvalExecutionScope } = await this.resolveChildExecutionIdentity({
|
|
499
|
+
threadId: typeof threadId === "string" ? threadId : void 0,
|
|
500
|
+
parentToolCallId,
|
|
501
|
+
parentConfigurable
|
|
502
|
+
});
|
|
503
|
+
this.childExecutionIdentities.set(parentToolCallId, {
|
|
504
|
+
childRunId,
|
|
505
|
+
childThreadId,
|
|
506
|
+
approvalExecutionScope
|
|
507
|
+
});
|
|
508
|
+
this.checkpointThreadIds.add(childThreadId);
|
|
509
|
+
const activeChildRun = this.activeChildRuns.get(childThreadId);
|
|
510
|
+
const persistedOutput = serializeToolOutput(settled);
|
|
511
|
+
if (activeChildRun != null) {
|
|
512
|
+
await this.persistChildCheckpointMarker(activeChildRun, parentToolCallId, persistedOutput);
|
|
513
|
+
this.clearChildGraph(activeChildRun.graph);
|
|
514
|
+
this.activeChildRuns.delete(childThreadId);
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
this.replayCheckpointWorkflow ??= createReplayCheckpointWorkflow(this.checkpointer);
|
|
518
|
+
await this.replayCheckpointWorkflow.updateState({ configurable: { thread_id: childThreadId } }, { messages: [createSubagentCheckpointMarkerMessage({
|
|
519
|
+
version: 1,
|
|
520
|
+
parentToolCallId,
|
|
521
|
+
lifecycleComplete: true,
|
|
522
|
+
hookSessionId: typeof parentConfigurable?.run_id === "string" ? parentConfigurable.run_id : this.parentRunId,
|
|
523
|
+
childRunId,
|
|
524
|
+
settledOutput: persistedOutput
|
|
525
|
+
})] }, SUBAGENT_REPLAY_NODE);
|
|
526
|
+
}
|
|
527
|
+
async persistChildCheckpointMarker(activeChildRun, parentToolCallId, settledOutput) {
|
|
528
|
+
if (this.humanInTheLoop?.enabled !== true || activeChildRun.workflow.updateState == null || activeChildRun.invokeConfig == null) return;
|
|
529
|
+
await activeChildRun.workflow.updateState(activeChildRun.invokeConfig, { messages: [createSubagentCheckpointMarkerMessage({
|
|
530
|
+
version: 1,
|
|
531
|
+
parentToolCallId,
|
|
532
|
+
lifecycleComplete: true,
|
|
533
|
+
hookSessionId: typeof activeChildRun.invokeConfig.configurable?.run_id === "string" ? activeChildRun.invokeConfig.configurable.run_id : this.parentRunId,
|
|
534
|
+
childRunId: activeChildRun.childRunId,
|
|
535
|
+
...settledOutput == null ? {} : { settledOutput }
|
|
536
|
+
})] }, activeChildRun.childAgentId);
|
|
537
|
+
}
|
|
80
538
|
async execute(params) {
|
|
81
539
|
const { description, subagentType, threadId, parentToolCallId } = params;
|
|
82
540
|
/** Captured ONCE per execution, preferring the controller the parent
|
|
@@ -97,32 +555,25 @@ var SubagentExecutor = class {
|
|
|
97
555
|
content: "Error: Maximum subagent nesting depth exceeded.",
|
|
98
556
|
messages: []
|
|
99
557
|
};
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
* Both decisions block execution and return a "Blocked" tool result.
|
|
120
|
-
*/
|
|
121
|
-
if (hookResult.decision === "deny" || hookResult.decision === "ask") return {
|
|
122
|
-
content: `Blocked: ${hookResult.reason ?? "Blocked by hook"}`,
|
|
123
|
-
messages: []
|
|
124
|
-
};
|
|
125
|
-
}
|
|
558
|
+
if (this.humanInTheLoop?.enabled === true && (parentToolCallId == null || parentToolCallId === "")) return {
|
|
559
|
+
content: "Error: Resumable subagent execution requires a parent tool call ID.",
|
|
560
|
+
messages: []
|
|
561
|
+
};
|
|
562
|
+
const executionSuffix = parentToolCallId ?? (0, nanoid.nanoid)(8);
|
|
563
|
+
const { childRunId, childThreadId, approvalExecutionScope, resumeExecution } = await this.resolveChildExecutionIdentity({
|
|
564
|
+
threadId,
|
|
565
|
+
parentToolCallId: executionSuffix,
|
|
566
|
+
parentConfigurable: params.parentConfigurable
|
|
567
|
+
});
|
|
568
|
+
this.childExecutionIdentities.set(executionSuffix, {
|
|
569
|
+
childRunId,
|
|
570
|
+
childThreadId,
|
|
571
|
+
approvalExecutionScope
|
|
572
|
+
});
|
|
573
|
+
const childExecutionKey = childThreadId;
|
|
574
|
+
const childAgentId = config.agentInputs.agentId || `${this.parentAgentId ?? "agent"}_sub_${executionSuffix}`;
|
|
575
|
+
const completedChildResult = this.completedChildResults.get(childExecutionKey);
|
|
576
|
+
if (completedChildResult != null) return completedChildResult;
|
|
126
577
|
const parentRegistry = this.getParentHandlerRegistry();
|
|
127
578
|
const forwardingEnabled = parentRegistry != null;
|
|
128
579
|
/**
|
|
@@ -136,10 +587,10 @@ var SubagentExecutor = class {
|
|
|
136
587
|
*/
|
|
137
588
|
const hasToolExecuteHandler = parentRegistry?.getHandler("on_tool_execute") != null;
|
|
138
589
|
const childInputs = buildChildInputs(config, childAgentId, this.maxDepth, hasToolExecuteHandler);
|
|
139
|
-
const childRunId = `${this.parentRunId}_sub_${(0, nanoid.nanoid)(8)}`;
|
|
140
590
|
const maxTurns = config.maxTurns ?? DEFAULT_MAX_TURNS;
|
|
141
591
|
const hostUsageSink = this.usageSink;
|
|
142
|
-
const
|
|
592
|
+
const cachedChildRun = this.activeChildRuns.get(childExecutionKey);
|
|
593
|
+
const childGraph = cachedChildRun?.graph ?? this.createChildGraph({
|
|
143
594
|
runId: childRunId,
|
|
144
595
|
signal: childSignal,
|
|
145
596
|
agents: [childInputs],
|
|
@@ -176,17 +627,21 @@ var SubagentExecutor = class {
|
|
|
176
627
|
parentToolCallId
|
|
177
628
|
});
|
|
178
629
|
const forwarder = forwarding?.handler;
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
subagentType,
|
|
182
|
-
subagentAgentId: childAgentId,
|
|
183
|
-
parentToolCallId,
|
|
184
|
-
phase: "start",
|
|
185
|
-
label: `Subagent "${subagentType}" started`
|
|
186
|
-
});
|
|
630
|
+
let childAlreadyStarted = this.startedChildRuns.has(childExecutionKey);
|
|
631
|
+
let childAlreadyCompleted = this.completedChildRuns.has(childExecutionKey);
|
|
187
632
|
let result;
|
|
633
|
+
let recoveredComplete = false;
|
|
634
|
+
let recoveredInProgress = false;
|
|
188
635
|
try {
|
|
189
|
-
const workflow = childGraph.createWorkflow();
|
|
636
|
+
const workflow = cachedChildRun?.workflow ?? childGraph.createWorkflow();
|
|
637
|
+
const activeChildRun = cachedChildRun ?? {
|
|
638
|
+
graph: childGraph,
|
|
639
|
+
workflow,
|
|
640
|
+
pendingInterrupts: [],
|
|
641
|
+
childAgentId,
|
|
642
|
+
childRunId
|
|
643
|
+
};
|
|
644
|
+
if (cachedChildRun == null) this.activeChildRuns.set(childExecutionKey, activeChildRun);
|
|
190
645
|
/**
|
|
191
646
|
* When `parentHandlerRegistry` is provided (forwarding mode), attach a
|
|
192
647
|
* lightweight callback that intercepts the child's `on_custom_event`
|
|
@@ -224,46 +679,136 @@ var SubagentExecutor = class {
|
|
|
224
679
|
}));
|
|
225
680
|
const callbacks = callbackHandlers;
|
|
226
681
|
/**
|
|
227
|
-
* Inherit the parent's host `configurable`
|
|
228
|
-
*
|
|
229
|
-
*
|
|
230
|
-
*
|
|
231
|
-
*
|
|
682
|
+
* Inherit the parent's host `configurable` while binding LangGraph's
|
|
683
|
+
* checkpoint identity to a stable child id derived from the durable
|
|
684
|
+
* parent thread and checkpoint fork. The parent thread id cannot be
|
|
685
|
+
* reused here: parent and child share one checkpointer when nested HITL
|
|
686
|
+
* is enabled, and root checkpoint namespaces are normalized by
|
|
687
|
+
* LangGraph, so a shared `thread_id` would collide with the parent.
|
|
232
688
|
*
|
|
233
|
-
*
|
|
234
|
-
*
|
|
235
|
-
*
|
|
236
|
-
*
|
|
237
|
-
* Forwarding `run_id` / `parent_run_id` / `thread_id` makes
|
|
238
|
-
* `ToolNode`'s hook lookups (`hasHookFor(eventName, runId)`),
|
|
239
|
-
* `ToolOutputReferenceRegistry` keying, and trace lineage all
|
|
240
|
-
* resolve to the parent's session for tools dispatched from the
|
|
241
|
-
* subagent — so `PreToolUse` / `PostToolUse` hooks the host
|
|
242
|
-
* registered against the parent's run fire for subagent tool
|
|
243
|
-
* calls too. "Same run" matches the user-perceptual mental model.
|
|
244
|
-
*
|
|
245
|
-
* `thread_id` falls back to `childRunId` only when the parent
|
|
246
|
-
* didn't supply one (legacy behavior preserved for hosts that
|
|
247
|
-
* never set thread_id).
|
|
248
|
-
*
|
|
249
|
-
* NOTE: a future revision will likely make this configurable per
|
|
250
|
-
* spawn type — e.g. a background / async subagent that runs after
|
|
251
|
-
* the parent's run completes wants isolation, not inheritance.
|
|
252
|
-
* For now the inheritance path matches LibreChat's primary use
|
|
253
|
-
* case (synchronous subagents within a single user turn).
|
|
689
|
+
* `run_id` still propagates as the parent run id, which is the key used
|
|
690
|
+
* for session-scoped hook lookup. Child hook inputs therefore retain
|
|
691
|
+
* the parent policy scope while their `threadId` truthfully identifies
|
|
692
|
+
* the independently checkpointed child execution.
|
|
254
693
|
*/
|
|
255
694
|
const inheritedConfigurable = sanitizeChildConfigurable(params.parentConfigurable);
|
|
256
|
-
|
|
695
|
+
const resumeAttemptId = getResumeAttemptId(params.parentConfigurable, this.parentRunId);
|
|
696
|
+
const currentHookSessionId = typeof inheritedConfigurable.run_id === "string" && inheritedConfigurable.run_id.length > 0 ? inheritedConfigurable.run_id : this.parentRunId;
|
|
697
|
+
if (cachedChildRun == null && resumeExecution != null) childGraph.restoreSubagentResumeState(resumeExecution.graphState, currentHookSessionId);
|
|
698
|
+
const childInvokeConfig = {
|
|
257
699
|
recursionLimit: maxTurns * RECURSION_MULTIPLIER,
|
|
258
700
|
signal: childSignal,
|
|
259
701
|
callbacks,
|
|
260
702
|
runName: `subagent:${subagentType}`,
|
|
261
703
|
configurable: {
|
|
262
|
-
|
|
263
|
-
...
|
|
704
|
+
...inheritedConfigurable,
|
|
705
|
+
...this.humanInTheLoop?.enabled === true ? { [require_types.TOOL_APPROVAL_EXECUTION_SCOPE_CONFIG_KEY]: approvalExecutionScope } : {},
|
|
706
|
+
...resumeExecution?.descendant == null ? {} : {
|
|
707
|
+
[require_SubagentReplay.SUBAGENT_RESUME_MANIFEST_CONFIG_KEY]: resumeExecution.descendant,
|
|
708
|
+
[require_SubagentReplay.SUBAGENT_RESUME_ATTEMPT_CONFIG_KEY]: resumeAttemptId
|
|
709
|
+
},
|
|
710
|
+
thread_id: this.humanInTheLoop?.enabled === true ? childThreadId : inheritedConfigurable.thread_id ?? childRunId
|
|
264
711
|
}
|
|
265
|
-
}
|
|
712
|
+
};
|
|
713
|
+
activeChildRun.invokeConfig = childInvokeConfig;
|
|
714
|
+
if (cachedChildRun == null && this.humanInTheLoop?.enabled === true) {
|
|
715
|
+
/** Rehydrate child-owned interrupt state when a host rebuilds Run
|
|
716
|
+
* around the same durable checkpointer after a process boundary. */
|
|
717
|
+
const persistedState = await workflow.getState(childInvokeConfig);
|
|
718
|
+
const checkpointMessages = getCheckpointMessages(persistedState.values?.messages);
|
|
719
|
+
const persistedHookSessionId = getSubagentHookSessionId(checkpointMessages);
|
|
720
|
+
if (persistedHookSessionId != null) this.hookRegistry?.copySession(persistedHookSessionId, currentHookSessionId);
|
|
721
|
+
const persistedInterrupts = getPersistedInterrupts(persistedState);
|
|
722
|
+
if (persistedInterrupts.length > 0) {
|
|
723
|
+
activeChildRun.pendingInterrupts = persistedInterrupts;
|
|
724
|
+
this.startedChildRuns.add(childExecutionKey);
|
|
725
|
+
childAlreadyStarted = true;
|
|
726
|
+
} else if (persistedState.next.length > 0) {
|
|
727
|
+
recoveredInProgress = true;
|
|
728
|
+
childAlreadyStarted = true;
|
|
729
|
+
this.startedChildRuns.add(childExecutionKey);
|
|
730
|
+
} else if (persistedState.next.length === 0) {
|
|
731
|
+
const persistedMessages = getPersistedMessages(persistedState);
|
|
732
|
+
if (persistedMessages != null) {
|
|
733
|
+
const marker = getSubagentCheckpointMarker(checkpointMessages, executionSuffix);
|
|
734
|
+
result = { messages: persistedMessages };
|
|
735
|
+
recoveredComplete = true;
|
|
736
|
+
childAlreadyStarted = true;
|
|
737
|
+
childAlreadyCompleted = marker?.lifecycleComplete === true;
|
|
738
|
+
this.startedChildRuns.add(childExecutionKey);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
if (!recoveredComplete) {
|
|
743
|
+
const childResumeMap = getChildResumeMap(activeChildRun.pendingInterrupts, params.parentConfigurable);
|
|
744
|
+
if (activeChildRun.pendingInterrupts.length > 0 && childResumeMap == null) throw new _langchain_langgraph.GraphInterrupt(activeChildRun.pendingInterrupts);
|
|
745
|
+
let childInput;
|
|
746
|
+
if (childResumeMap != null) childInput = new _langchain_langgraph.Command({ resume: childResumeMap });
|
|
747
|
+
else if (recoveredInProgress) childInput = null;
|
|
748
|
+
else childInput = { messages: [new _langchain_core_messages.HumanMessage({
|
|
749
|
+
content: description,
|
|
750
|
+
additional_kwargs: {
|
|
751
|
+
[SUBAGENT_HOOK_SESSION_KEY]: currentHookSessionId,
|
|
752
|
+
[SUBAGENT_RUN_ID_KEY]: childRunId
|
|
753
|
+
}
|
|
754
|
+
})] };
|
|
755
|
+
if (!childAlreadyStarted && this.hookRegistry?.hasHookFor("SubagentStart", this.parentRunId) === true) {
|
|
756
|
+
const hookResult = await require_executeHooks.executeHooks({
|
|
757
|
+
registry: this.hookRegistry,
|
|
758
|
+
input: {
|
|
759
|
+
hook_event_name: "SubagentStart",
|
|
760
|
+
runId: this.parentRunId,
|
|
761
|
+
threadId,
|
|
762
|
+
parentAgentId: this.parentAgentId,
|
|
763
|
+
agentId: childAgentId,
|
|
764
|
+
agentType: subagentType,
|
|
765
|
+
inputs: [new _langchain_core_messages.HumanMessage(description)]
|
|
766
|
+
},
|
|
767
|
+
sessionId: this.parentRunId,
|
|
768
|
+
matchQuery: subagentType
|
|
769
|
+
}).catch(() => HOOK_FALLBACK);
|
|
770
|
+
if (hookResult.decision === "deny" || hookResult.decision === "ask") {
|
|
771
|
+
this.clearChildGraph(childGraph);
|
|
772
|
+
this.activeChildRuns.delete(childExecutionKey);
|
|
773
|
+
return {
|
|
774
|
+
content: `Blocked: ${hookResult.reason ?? "Blocked by hook"}`,
|
|
775
|
+
messages: []
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
this.startedChildRuns.add(childExecutionKey);
|
|
780
|
+
if (forwarder && !childAlreadyStarted) await this.emitSubagentUpdate(parentRegistry, {
|
|
781
|
+
childRunId,
|
|
782
|
+
subagentType,
|
|
783
|
+
subagentAgentId: childAgentId,
|
|
784
|
+
parentToolCallId,
|
|
785
|
+
phase: "start",
|
|
786
|
+
label: `Subagent "${subagentType}" started`
|
|
787
|
+
});
|
|
788
|
+
let childResult;
|
|
789
|
+
if (this.humanInTheLoop?.enabled === true)
|
|
790
|
+
/** Execute as an independently checkpointed root instead of inheriting
|
|
791
|
+
* the parent's Pregel namespace. Parent decisions are routed explicitly
|
|
792
|
+
* by interrupt id, so concurrent children keep isolated resume state. */
|
|
793
|
+
childResult = await _langchain_core_singletons.AsyncLocalStorageProviderSingleton.runWithConfig(childInvokeConfig, () => workflow.invoke(childInput, childInvokeConfig));
|
|
794
|
+
else childResult = await workflow.invoke(childInput, childInvokeConfig);
|
|
795
|
+
const childInterrupts = (0, _langchain_langgraph.isInterrupted)(childResult) ? childResult[_langchain_langgraph.INTERRUPT] : void 0;
|
|
796
|
+
if (childInterrupts != null && childInterrupts.length > 0) throw new _langchain_langgraph.GraphInterrupt(childInterrupts);
|
|
797
|
+
result = { messages: childResult.messages };
|
|
798
|
+
}
|
|
266
799
|
} catch (error) {
|
|
800
|
+
if ((0, _langchain_langgraph.isGraphInterrupt)(error)) {
|
|
801
|
+
const activeChildRun = this.activeChildRuns.get(childExecutionKey);
|
|
802
|
+
if (activeChildRun != null) activeChildRun.pendingInterrupts = error.interrupts;
|
|
803
|
+
const resumeManifest = activeChildRun == null || parentToolCallId == null ? void 0 : await this.createResumeManifest();
|
|
804
|
+
await forwarding?.drain();
|
|
805
|
+
throw new _langchain_langgraph.GraphInterrupt(addSubagentScope(error.interrupts, {
|
|
806
|
+
run_id: childRunId,
|
|
807
|
+
agent_id: childAgentId,
|
|
808
|
+
subagent_type: subagentType,
|
|
809
|
+
parent_tool_call_id: parentToolCallId
|
|
810
|
+
}, resumeManifest));
|
|
811
|
+
}
|
|
267
812
|
/** Aborted before any observational work below: parallel siblings — in
|
|
268
813
|
* this executor and, via the graph-scoped breaker, under other
|
|
269
814
|
* parallel agent nodes — stream on the composed child signal, and
|
|
@@ -285,7 +830,8 @@ var SubagentExecutor = class {
|
|
|
285
830
|
data: { message: errorMessage }
|
|
286
831
|
});
|
|
287
832
|
}
|
|
288
|
-
|
|
833
|
+
this.clearChildGraph(childGraph);
|
|
834
|
+
this.activeChildRuns.delete(childExecutionKey);
|
|
289
835
|
/**
|
|
290
836
|
* A tripped stream circuit breaker is a safety abort, not a recoverable
|
|
291
837
|
* subagent failure: converting it into a tool result would let the
|
|
@@ -299,8 +845,9 @@ var SubagentExecutor = class {
|
|
|
299
845
|
messages: []
|
|
300
846
|
};
|
|
301
847
|
}
|
|
848
|
+
if (result == null) throw new Error("Subagent completed without producing graph state.");
|
|
302
849
|
const filteredContent = filterSubagentResult(result.messages);
|
|
303
|
-
if (this.hookRegistry?.hasHookFor("SubagentStop", this.parentRunId) === true)
|
|
850
|
+
if (!childAlreadyCompleted && this.hookRegistry?.hasHookFor("SubagentStop", this.parentRunId) === true)
|
|
304
851
|
/**
|
|
305
852
|
* Awaited (not fire-and-forget) for deterministic test synchronization
|
|
306
853
|
* and consistency with PostCompact. The parent is already waiting on the
|
|
@@ -320,7 +867,7 @@ var SubagentExecutor = class {
|
|
|
320
867
|
sessionId: this.parentRunId,
|
|
321
868
|
matchQuery: subagentType
|
|
322
869
|
}).catch(() => {});
|
|
323
|
-
if (forwarding) {
|
|
870
|
+
if (forwarding && !childAlreadyCompleted) {
|
|
324
871
|
await forwarding.drain();
|
|
325
872
|
await this.emitSubagentUpdate(parentRegistry, {
|
|
326
873
|
childRunId,
|
|
@@ -331,11 +878,18 @@ var SubagentExecutor = class {
|
|
|
331
878
|
label: `Subagent "${subagentType}" finished`
|
|
332
879
|
});
|
|
333
880
|
}
|
|
334
|
-
|
|
335
|
-
|
|
881
|
+
if (!childAlreadyCompleted) {
|
|
882
|
+
const activeChildRun = this.activeChildRuns.get(childExecutionKey);
|
|
883
|
+
if (activeChildRun != null && parentToolCallId != null) await this.persistChildCheckpointMarker(activeChildRun, parentToolCallId);
|
|
884
|
+
}
|
|
885
|
+
this.completedChildRuns.add(childExecutionKey);
|
|
886
|
+
this.clearChildGraph(childGraph);
|
|
887
|
+
const completedResult = {
|
|
336
888
|
content: filteredContent,
|
|
337
889
|
messages: result.messages
|
|
338
890
|
};
|
|
891
|
+
this.completedChildResults.set(childExecutionKey, completedResult);
|
|
892
|
+
return completedResult;
|
|
339
893
|
}
|
|
340
894
|
/**
|
|
341
895
|
* Emits a single {@link GraphEvents.ON_SUBAGENT_UPDATE} envelope through the
|
|
@@ -582,7 +1136,7 @@ function sanitizeChildConfigurable(parentConfigurable) {
|
|
|
582
1136
|
return Object.fromEntries(Object.entries(parentConfigurable).filter(([key]) => !isLangGraphRuntimeConfigKey(key)));
|
|
583
1137
|
}
|
|
584
1138
|
function isLangGraphRuntimeConfigKey(key) {
|
|
585
|
-
return key.startsWith(LANGGRAPH_RUNTIME_CONFIG_PREFIX) || LANGGRAPH_CHECKPOINT_CONFIG_KEYS.has(key) || key === "lc_run_breaker_scope";
|
|
1139
|
+
return key.startsWith(LANGGRAPH_RUNTIME_CONFIG_PREFIX) || LANGGRAPH_CHECKPOINT_CONFIG_KEYS.has(key) || key === "__librechat_subagent_resume_attempt" || key === "__librechat_subagent_resume_manifest" || key === "__librechat_subagent_parent_batch" || key === "lc_run_breaker_scope";
|
|
586
1140
|
}
|
|
587
1141
|
function sanitizeForwardedSubagentUpdateData(eventName, data) {
|
|
588
1142
|
if (eventName === "on_tool_execute") return sanitizeToolExecuteUpdateData(data);
|
|
@@ -767,6 +1321,9 @@ function summarizeEvent(eventName, data) {
|
|
|
767
1321
|
* pure tool_use (e.g. the subagent hit `maxTurns` mid-tool-call), the walk
|
|
768
1322
|
* continues to earlier AIMessages so partial progress is salvaged — this
|
|
769
1323
|
* matches Claude Code's behavior in `agentToolUtils.finalizeAgentTool`.
|
|
1324
|
+
* Consecutive streamed text-delta blocks with the same provider index are
|
|
1325
|
+
* coalesced without adding whitespace. Annotation-only text blocks are
|
|
1326
|
+
* ignored; complete text blocks and distinct delta indexes remain separated.
|
|
770
1327
|
* Returns "Task completed" only when no AIMessage in the history contains
|
|
771
1328
|
* any text.
|
|
772
1329
|
*/
|
|
@@ -780,8 +1337,36 @@ function filterSubagentResult(messages) {
|
|
|
780
1337
|
}
|
|
781
1338
|
if (!Array.isArray(content)) continue;
|
|
782
1339
|
const textParts = [];
|
|
783
|
-
|
|
784
|
-
|
|
1340
|
+
let textDeltaParts = [];
|
|
1341
|
+
let textDeltaIndex;
|
|
1342
|
+
const flushTextDeltaParts = () => {
|
|
1343
|
+
if (textDeltaParts.length === 0) return;
|
|
1344
|
+
textParts.push(textDeltaParts.join(""));
|
|
1345
|
+
textDeltaParts = [];
|
|
1346
|
+
textDeltaIndex = void 0;
|
|
1347
|
+
};
|
|
1348
|
+
for (const block of content) {
|
|
1349
|
+
if (typeof block === "string") {
|
|
1350
|
+
flushTextDeltaParts();
|
|
1351
|
+
if (block !== "") textParts.push(block);
|
|
1352
|
+
continue;
|
|
1353
|
+
}
|
|
1354
|
+
const type = "type" in block && typeof block.type === "string" ? block.type : "";
|
|
1355
|
+
const isTextDelta = type === TEXT_DELTA_CONTENT_TYPE;
|
|
1356
|
+
const text = (type === "text" || isTextDelta) && "text" in block && typeof block.text === "string" ? block.text : "";
|
|
1357
|
+
if (isTextDelta) {
|
|
1358
|
+
if (text === "") continue;
|
|
1359
|
+
const index = "index" in block && (typeof block.index === "string" || typeof block.index === "number") ? block.index : void 0;
|
|
1360
|
+
if (textDeltaIndex != null && index != null && index !== textDeltaIndex) flushTextDeltaParts();
|
|
1361
|
+
textDeltaIndex ??= index;
|
|
1362
|
+
textDeltaParts.push(text);
|
|
1363
|
+
continue;
|
|
1364
|
+
}
|
|
1365
|
+
if (type === "text" && text === "") continue;
|
|
1366
|
+
flushTextDeltaParts();
|
|
1367
|
+
if (text !== "") textParts.push(text);
|
|
1368
|
+
}
|
|
1369
|
+
flushTextDeltaParts();
|
|
785
1370
|
if (textParts.length > 0) return textParts.join("\n");
|
|
786
1371
|
}
|
|
787
1372
|
return "Task completed";
|
|
@@ -848,11 +1433,10 @@ function buildChildInputs(config, childAgentId, parentMaxDepth, keepToolDefiniti
|
|
|
848
1433
|
* Host-supplied direct tools are scrubbed from INHERITED configs only.
|
|
849
1434
|
* A self-spawn config's `agentInputs` is a shallow spread of the parent's
|
|
850
1435
|
* `_sourceInputs`, so without this a parent-scoped graph tool (e.g. an
|
|
851
|
-
* interrupt-raising ask_user_question
|
|
852
|
-
*
|
|
853
|
-
*
|
|
854
|
-
*
|
|
855
|
-
* host choice and keeps them (Codex #289 P2).
|
|
1436
|
+
* interrupt-raising ask_user_question) would silently become available to
|
|
1437
|
+
* the child. An EXPLICIT child config that lists its own `graphTools` is a
|
|
1438
|
+
* deliberate host choice and keeps them (Codex #289 P2); with HITL enabled,
|
|
1439
|
+
* those tools use the shared checkpointer and can pause and resume safely.
|
|
856
1440
|
*/
|
|
857
1441
|
graphTools: config.self === true ? void 0 : agentInputs.graphTools
|
|
858
1442
|
};
|