@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
package/src/run.ts
CHANGED
|
@@ -4,21 +4,28 @@ import { PromptTemplate } from '@langchain/core/prompts';
|
|
|
4
4
|
import { RunnableLambda } from '@langchain/core/runnables';
|
|
5
5
|
import { AzureChatOpenAI, ChatOpenAI } from '@langchain/openai';
|
|
6
6
|
import { BaseCallbackHandler } from '@langchain/core/callbacks/base';
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
BaseMessage,
|
|
9
|
+
HumanMessage,
|
|
10
|
+
SystemMessage,
|
|
11
|
+
} from '@langchain/core/messages';
|
|
8
12
|
import {
|
|
9
13
|
Command,
|
|
10
14
|
INTERRUPT,
|
|
11
15
|
MemorySaver,
|
|
12
16
|
isInterrupted,
|
|
13
17
|
} from '@langchain/langgraph';
|
|
14
|
-
import type {
|
|
15
|
-
MessageContentComplex,
|
|
16
|
-
BaseMessage,
|
|
17
|
-
} from '@langchain/core/messages';
|
|
18
18
|
import type { StringPromptValue } from '@langchain/core/prompt_values';
|
|
19
|
+
import type { MessageContentComplex } from '@langchain/core/messages';
|
|
19
20
|
import type { RunnableConfig } from '@langchain/core/runnables';
|
|
20
21
|
import type { HookRegistry } from '@/hooks';
|
|
21
22
|
import type * as t from '@/types';
|
|
23
|
+
import {
|
|
24
|
+
requireValidSubagentResumeManifest,
|
|
25
|
+
stripSubagentResumeManifest,
|
|
26
|
+
SUBAGENT_RESUME_ATTEMPT_CONFIG_KEY,
|
|
27
|
+
SUBAGENT_RESUME_MANIFEST_CONFIG_KEY,
|
|
28
|
+
} from '@/tools/subagent/SubagentReplay';
|
|
22
29
|
import {
|
|
23
30
|
createLangfuseTraceMetadata,
|
|
24
31
|
createLangfuseHandler,
|
|
@@ -121,19 +128,83 @@ function isLangGraphResumeMapForInterrupt(
|
|
|
121
128
|
return Object.prototype.hasOwnProperty.call(value, interruptId);
|
|
122
129
|
}
|
|
123
130
|
|
|
131
|
+
function getInterruptHookSessionId(payload: unknown): string | undefined {
|
|
132
|
+
const publicPayload = stripSubagentResumeManifest(payload);
|
|
133
|
+
if (
|
|
134
|
+
publicPayload == null ||
|
|
135
|
+
typeof publicPayload !== 'object' ||
|
|
136
|
+
(publicPayload as { type?: unknown }).type !== 'tool_approval'
|
|
137
|
+
) {
|
|
138
|
+
return undefined;
|
|
139
|
+
}
|
|
140
|
+
const sessionId = (publicPayload as { hook_session_id?: unknown })
|
|
141
|
+
.hook_session_id;
|
|
142
|
+
return typeof sessionId === 'string' && sessionId.length > 0
|
|
143
|
+
? sessionId
|
|
144
|
+
: undefined;
|
|
145
|
+
}
|
|
146
|
+
|
|
124
147
|
type InterruptStateSnapshot = {
|
|
125
148
|
config?: RunnableConfig;
|
|
149
|
+
values?: { messages?: BaseMessage[] };
|
|
126
150
|
tasks?: Array<{
|
|
127
|
-
interrupts?: Array<{ id?: string }>;
|
|
151
|
+
interrupts?: Array<{ id?: string; value?: unknown }>;
|
|
128
152
|
}>;
|
|
129
153
|
};
|
|
130
154
|
|
|
131
155
|
type WorkflowWithStateHistory = {
|
|
156
|
+
getState?(config: RunnableConfig): Promise<InterruptStateSnapshot>;
|
|
132
157
|
getStateHistory?(
|
|
133
158
|
config: RunnableConfig
|
|
134
159
|
): AsyncIterableIterator<InterruptStateSnapshot>;
|
|
135
160
|
};
|
|
136
161
|
|
|
162
|
+
function getFirstPersistedInterrupt(
|
|
163
|
+
snapshot: InterruptStateSnapshot
|
|
164
|
+
): { id: string; value: unknown } | undefined {
|
|
165
|
+
for (const task of snapshot.tasks ?? []) {
|
|
166
|
+
for (const pendingInterrupt of task.interrupts ?? []) {
|
|
167
|
+
if (
|
|
168
|
+
typeof pendingInterrupt.id === 'string' &&
|
|
169
|
+
pendingInterrupt.id.length > 0
|
|
170
|
+
) {
|
|
171
|
+
return { id: pendingInterrupt.id, value: pendingInterrupt.value };
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return undefined;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function getPersistedMessages(
|
|
179
|
+
snapshot: InterruptStateSnapshot
|
|
180
|
+
): BaseMessage[] | undefined {
|
|
181
|
+
const messages = snapshot.values?.messages;
|
|
182
|
+
if (!Array.isArray(messages) || !messages.every(BaseMessage.isInstance)) {
|
|
183
|
+
return undefined;
|
|
184
|
+
}
|
|
185
|
+
return messages;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
type ResumeCommandUpdate = ConstructorParameters<typeof Command>[0]['update'];
|
|
189
|
+
|
|
190
|
+
function getResumeUpdateMessages(
|
|
191
|
+
update: ResumeCommandUpdate
|
|
192
|
+
): BaseMessage[] | undefined {
|
|
193
|
+
if (update == null) {
|
|
194
|
+
return undefined;
|
|
195
|
+
}
|
|
196
|
+
const messages = Array.isArray(update)
|
|
197
|
+
? update.find(([key]) => key === 'messages')?.[1]
|
|
198
|
+
: update.messages;
|
|
199
|
+
if (BaseMessage.isInstance(messages)) {
|
|
200
|
+
return [messages];
|
|
201
|
+
}
|
|
202
|
+
if (!Array.isArray(messages) || !messages.every(BaseMessage.isInstance)) {
|
|
203
|
+
return undefined;
|
|
204
|
+
}
|
|
205
|
+
return messages;
|
|
206
|
+
}
|
|
207
|
+
|
|
137
208
|
export class Run<_T extends t.BaseGraphState> {
|
|
138
209
|
id: string;
|
|
139
210
|
private tokenCounter?: t.TokenCounter;
|
|
@@ -227,8 +298,6 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
227
298
|
/** Default to legacy graph for 'standard' or undefined type */
|
|
228
299
|
this.graphRunnable = this.createLegacyGraph(config.graphConfig);
|
|
229
300
|
if (this.Graph) {
|
|
230
|
-
this.Graph.compileOptions =
|
|
231
|
-
config.graphConfig.compileOptions ?? this.Graph.compileOptions;
|
|
232
301
|
this.Graph.handlerRegistry = handlerRegistry;
|
|
233
302
|
}
|
|
234
303
|
}
|
|
@@ -542,6 +611,10 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
542
611
|
return this.Graph.getRunMessages();
|
|
543
612
|
}
|
|
544
613
|
|
|
614
|
+
getChildCheckpointThreadIds(): string[] {
|
|
615
|
+
return this.Graph?.getChildCheckpointThreadIds() ?? [];
|
|
616
|
+
}
|
|
617
|
+
|
|
545
618
|
/**
|
|
546
619
|
* Returns a defensive snapshot of tools discovered by the current run.
|
|
547
620
|
* Pass an agent id for that context, or omit it for the ordered union across
|
|
@@ -759,6 +832,10 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
759
832
|
recursionLimit,
|
|
760
833
|
configurable: { ...callerConfig.configurable },
|
|
761
834
|
};
|
|
835
|
+
if (!isResume) {
|
|
836
|
+
delete config.configurable?.[SUBAGENT_RESUME_ATTEMPT_CONFIG_KEY];
|
|
837
|
+
delete config.configurable?.[SUBAGENT_RESUME_MANIFEST_CONFIG_KEY];
|
|
838
|
+
}
|
|
762
839
|
|
|
763
840
|
/**
|
|
764
841
|
* Cancellation can arrive either at graph construction or per-call through
|
|
@@ -1248,7 +1325,13 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
1248
1325
|
getInterrupt<TPayload = t.HumanInterruptPayload>():
|
|
1249
1326
|
| t.RunInterruptResult<TPayload>
|
|
1250
1327
|
| undefined {
|
|
1251
|
-
|
|
1328
|
+
if (this._interrupt == null) {
|
|
1329
|
+
return undefined;
|
|
1330
|
+
}
|
|
1331
|
+
return {
|
|
1332
|
+
...this._interrupt,
|
|
1333
|
+
payload: stripSubagentResumeManifest(this._interrupt.payload),
|
|
1334
|
+
} as t.RunInterruptResult<TPayload>;
|
|
1252
1335
|
}
|
|
1253
1336
|
|
|
1254
1337
|
/**
|
|
@@ -1331,6 +1414,10 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
1331
1414
|
'update' | 'goto'
|
|
1332
1415
|
>
|
|
1333
1416
|
): Promise<MessageContentComplex[] | undefined> {
|
|
1417
|
+
const resumeConfig = await this.resolveInterruptResumeConfig(
|
|
1418
|
+
callerConfig,
|
|
1419
|
+
commandOptions?.update
|
|
1420
|
+
);
|
|
1334
1421
|
const interruptId = this._interrupt?.interruptId;
|
|
1335
1422
|
const scopedResume =
|
|
1336
1423
|
typeof interruptId === 'string' &&
|
|
@@ -1338,7 +1425,6 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
1338
1425
|
!isLangGraphResumeMapForInterrupt(resumeValue, interruptId)
|
|
1339
1426
|
? { [interruptId]: resumeValue }
|
|
1340
1427
|
: resumeValue;
|
|
1341
|
-
const resumeConfig = await this.resolveInterruptResumeConfig(callerConfig);
|
|
1342
1428
|
// langgraph 1.4.5 applies resume + state update + reroute in one superstep
|
|
1343
1429
|
// (single checkpoint). `update`/`goto` are omitted unless the caller sets them.
|
|
1344
1430
|
return this.processStream(
|
|
@@ -1357,9 +1443,29 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
1357
1443
|
}
|
|
1358
1444
|
|
|
1359
1445
|
private async resolveInterruptResumeConfig(
|
|
1360
|
-
callerConfig: t.RunStreamConfig
|
|
1446
|
+
callerConfig: t.RunStreamConfig,
|
|
1447
|
+
resumeUpdate?: ResumeCommandUpdate
|
|
1361
1448
|
): Promise<t.RunStreamConfig> {
|
|
1449
|
+
await this.restoreInterruptFromCheckpoint(callerConfig, resumeUpdate);
|
|
1362
1450
|
const interrupt = this._interrupt;
|
|
1451
|
+
const resumeManifest = requireValidSubagentResumeManifest(
|
|
1452
|
+
interrupt?.payload
|
|
1453
|
+
);
|
|
1454
|
+
const resumeConfigurable = { ...callerConfig.configurable };
|
|
1455
|
+
delete resumeConfigurable[SUBAGENT_RESUME_ATTEMPT_CONFIG_KEY];
|
|
1456
|
+
delete resumeConfigurable[SUBAGENT_RESUME_MANIFEST_CONFIG_KEY];
|
|
1457
|
+
resumeConfigurable[SUBAGENT_RESUME_ATTEMPT_CONFIG_KEY] = nanoid();
|
|
1458
|
+
if (resumeManifest != null) {
|
|
1459
|
+
resumeConfigurable[SUBAGENT_RESUME_MANIFEST_CONFIG_KEY] = resumeManifest;
|
|
1460
|
+
}
|
|
1461
|
+
const manifestConfig = {
|
|
1462
|
+
...callerConfig,
|
|
1463
|
+
configurable: resumeConfigurable,
|
|
1464
|
+
};
|
|
1465
|
+
const hookSessionId = getInterruptHookSessionId(interrupt?.payload);
|
|
1466
|
+
if (hookSessionId != null) {
|
|
1467
|
+
this.hookRegistry?.copySession(hookSessionId, this.id);
|
|
1468
|
+
}
|
|
1363
1469
|
const interruptId = interrupt?.interruptId;
|
|
1364
1470
|
const workflow = this.graphRunnable as
|
|
1365
1471
|
| (t.CompiledStateWorkflow & WorkflowWithStateHistory)
|
|
@@ -1367,9 +1473,9 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
1367
1473
|
const stateHistory = workflow?.getStateHistory;
|
|
1368
1474
|
if (interrupt?.checkpointId != null && interrupt.checkpointId.length > 0) {
|
|
1369
1475
|
return {
|
|
1370
|
-
...
|
|
1476
|
+
...manifestConfig,
|
|
1371
1477
|
configurable: {
|
|
1372
|
-
...
|
|
1478
|
+
...manifestConfig.configurable,
|
|
1373
1479
|
checkpoint_id: interrupt.checkpointId,
|
|
1374
1480
|
...(typeof interrupt.checkpointNs === 'string'
|
|
1375
1481
|
? { checkpoint_ns: interrupt.checkpointNs }
|
|
@@ -1383,12 +1489,12 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
1383
1489
|
interruptId.length === 0 ||
|
|
1384
1490
|
typeof stateHistory !== 'function'
|
|
1385
1491
|
) {
|
|
1386
|
-
return
|
|
1492
|
+
return manifestConfig;
|
|
1387
1493
|
}
|
|
1388
1494
|
|
|
1389
1495
|
for await (const snapshot of stateHistory.call(
|
|
1390
1496
|
this.graphRunnable,
|
|
1391
|
-
|
|
1497
|
+
manifestConfig as RunnableConfig
|
|
1392
1498
|
)) {
|
|
1393
1499
|
const hasMatchingInterrupt =
|
|
1394
1500
|
snapshot.tasks?.some(
|
|
@@ -1411,9 +1517,9 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
1411
1517
|
...(typeof checkpointNs === 'string' ? { checkpointNs } : {}),
|
|
1412
1518
|
};
|
|
1413
1519
|
return {
|
|
1414
|
-
...
|
|
1520
|
+
...manifestConfig,
|
|
1415
1521
|
configurable: {
|
|
1416
|
-
...
|
|
1522
|
+
...manifestConfig.configurable,
|
|
1417
1523
|
checkpoint_id: checkpointId,
|
|
1418
1524
|
...(typeof checkpointNs === 'string'
|
|
1419
1525
|
? { checkpoint_ns: checkpointNs }
|
|
@@ -1423,7 +1529,47 @@ export class Run<_T extends t.BaseGraphState> {
|
|
|
1423
1529
|
}
|
|
1424
1530
|
}
|
|
1425
1531
|
|
|
1426
|
-
return
|
|
1532
|
+
return manifestConfig;
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
private async restoreInterruptFromCheckpoint(
|
|
1536
|
+
callerConfig: t.RunStreamConfig,
|
|
1537
|
+
resumeUpdate?: ResumeCommandUpdate
|
|
1538
|
+
): Promise<void> {
|
|
1539
|
+
if (this._interrupt != null || this.humanInTheLoop?.enabled !== true) {
|
|
1540
|
+
return;
|
|
1541
|
+
}
|
|
1542
|
+
const workflow = this.graphRunnable as
|
|
1543
|
+
| (t.CompiledStateWorkflow & WorkflowWithStateHistory)
|
|
1544
|
+
| undefined;
|
|
1545
|
+
if (typeof workflow?.getState !== 'function') {
|
|
1546
|
+
return;
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
const snapshot = await workflow.getState(callerConfig as RunnableConfig);
|
|
1550
|
+
const persistedInterrupt = getFirstPersistedInterrupt(snapshot);
|
|
1551
|
+
if (persistedInterrupt == null) {
|
|
1552
|
+
return;
|
|
1553
|
+
}
|
|
1554
|
+
const persistedMessages = getPersistedMessages(snapshot);
|
|
1555
|
+
if (persistedMessages != null) {
|
|
1556
|
+
this.Graph?.restoreCheckpointMessages(
|
|
1557
|
+
persistedMessages,
|
|
1558
|
+
getResumeUpdateMessages(resumeUpdate)
|
|
1559
|
+
);
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
const checkpointConfigurable = snapshot.config?.configurable;
|
|
1563
|
+
const checkpointId = checkpointConfigurable?.checkpoint_id;
|
|
1564
|
+
const checkpointNs = checkpointConfigurable?.checkpoint_ns;
|
|
1565
|
+
const threadId = callerConfig.configurable?.thread_id;
|
|
1566
|
+
this._interrupt = {
|
|
1567
|
+
interruptId: persistedInterrupt.id,
|
|
1568
|
+
payload: persistedInterrupt.value,
|
|
1569
|
+
...(typeof threadId === 'string' ? { threadId } : {}),
|
|
1570
|
+
...(typeof checkpointId === 'string' ? { checkpointId } : {}),
|
|
1571
|
+
...(typeof checkpointNs === 'string' ? { checkpointNs } : {}),
|
|
1572
|
+
};
|
|
1427
1573
|
}
|
|
1428
1574
|
|
|
1429
1575
|
private createSystemCallback<K extends keyof t.ClientCallbacks>(
|
|
@@ -934,6 +934,30 @@ export class AgentSession {
|
|
|
934
934
|
});
|
|
935
935
|
}
|
|
936
936
|
|
|
937
|
+
private async recordChildCheckpointThreads(params: {
|
|
938
|
+
source: 'run' | 'resume';
|
|
939
|
+
runId: string;
|
|
940
|
+
run: Run<t.IState>;
|
|
941
|
+
}): Promise<void> {
|
|
942
|
+
if (!this.checkpointing.enabled || this.store == null) {
|
|
943
|
+
return;
|
|
944
|
+
}
|
|
945
|
+
const recordedThreadIds = new Set(
|
|
946
|
+
this.store.getCheckpoints().map((checkpoint) => checkpoint.data.threadId)
|
|
947
|
+
);
|
|
948
|
+
for (const threadId of params.run.getChildCheckpointThreadIds()) {
|
|
949
|
+
if (recordedThreadIds.has(threadId)) {
|
|
950
|
+
continue;
|
|
951
|
+
}
|
|
952
|
+
recordedThreadIds.add(threadId);
|
|
953
|
+
await this.store.appendCheckpoint({
|
|
954
|
+
source: params.source,
|
|
955
|
+
runId: params.runId,
|
|
956
|
+
threadId,
|
|
957
|
+
});
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
|
|
937
961
|
private getCheckpointThreadIds(): string[] {
|
|
938
962
|
const threadIds = new Set<string>([this.threadId]);
|
|
939
963
|
for (const checkpoint of this.store?.getCheckpoints() ?? []) {
|
|
@@ -1006,6 +1030,7 @@ export class AgentSession {
|
|
|
1006
1030
|
const sessionState = createSessionRunState(
|
|
1007
1031
|
isSessionThread ? (this.store?.getPath() ?? []) : []
|
|
1008
1032
|
);
|
|
1033
|
+
let run: Run<t.IState> | undefined;
|
|
1009
1034
|
try {
|
|
1010
1035
|
const runConfig: t.RunConfig = {
|
|
1011
1036
|
...this.runConfig,
|
|
@@ -1024,7 +1049,7 @@ export class AgentSession {
|
|
|
1024
1049
|
...handlerResult.handlers,
|
|
1025
1050
|
},
|
|
1026
1051
|
};
|
|
1027
|
-
|
|
1052
|
+
run = await Run.create<t.IState>(runConfig);
|
|
1028
1053
|
let messages = inputMessages;
|
|
1029
1054
|
if (!useCheckpointState && sessionState.messages.length > 0) {
|
|
1030
1055
|
messages = sessionState.messages;
|
|
@@ -1070,6 +1095,11 @@ export class AgentSession {
|
|
|
1070
1095
|
checkpointId: interrupt?.checkpointId,
|
|
1071
1096
|
checkpointNs: interrupt?.checkpointNs,
|
|
1072
1097
|
});
|
|
1098
|
+
await this.recordChildCheckpointThreads({
|
|
1099
|
+
source: 'run',
|
|
1100
|
+
runId,
|
|
1101
|
+
run,
|
|
1102
|
+
});
|
|
1073
1103
|
const contentParts = (content ?? handlerResult.contentParts).filter(
|
|
1074
1104
|
(part): part is t.MessageContentComplex => part != null
|
|
1075
1105
|
);
|
|
@@ -1099,6 +1129,13 @@ export class AgentSession {
|
|
|
1099
1129
|
threadId,
|
|
1100
1130
|
config: callerConfig,
|
|
1101
1131
|
});
|
|
1132
|
+
if (run != null) {
|
|
1133
|
+
await this.recordChildCheckpointThreads({
|
|
1134
|
+
source: 'run',
|
|
1135
|
+
runId,
|
|
1136
|
+
run,
|
|
1137
|
+
});
|
|
1138
|
+
}
|
|
1102
1139
|
throw error;
|
|
1103
1140
|
}
|
|
1104
1141
|
}
|
|
@@ -1348,8 +1385,9 @@ export class AgentSession {
|
|
|
1348
1385
|
const sessionState = createSessionRunState(
|
|
1349
1386
|
isSessionThread ? (this.store?.getPath() ?? []) : []
|
|
1350
1387
|
);
|
|
1388
|
+
let run: Run<t.IState> | undefined;
|
|
1351
1389
|
try {
|
|
1352
|
-
|
|
1390
|
+
run = await Run.create<t.IState>({
|
|
1353
1391
|
...this.runConfig,
|
|
1354
1392
|
runId,
|
|
1355
1393
|
graphConfig: applyCheckpointingToGraphConfig(
|
|
@@ -1400,6 +1438,11 @@ export class AgentSession {
|
|
|
1400
1438
|
checkpointId: interrupt?.checkpointId,
|
|
1401
1439
|
checkpointNs: interrupt?.checkpointNs,
|
|
1402
1440
|
});
|
|
1441
|
+
await this.recordChildCheckpointThreads({
|
|
1442
|
+
source: 'resume',
|
|
1443
|
+
runId,
|
|
1444
|
+
run,
|
|
1445
|
+
});
|
|
1403
1446
|
const contentParts = (content ?? handlerResult.contentParts).filter(
|
|
1404
1447
|
(part): part is t.MessageContentComplex => part != null
|
|
1405
1448
|
);
|
|
@@ -1425,6 +1468,13 @@ export class AgentSession {
|
|
|
1425
1468
|
threadId,
|
|
1426
1469
|
config: callerConfig,
|
|
1427
1470
|
});
|
|
1471
|
+
if (run != null) {
|
|
1472
|
+
await this.recordChildCheckpointThreads({
|
|
1473
|
+
source: 'resume',
|
|
1474
|
+
runId,
|
|
1475
|
+
run,
|
|
1476
|
+
});
|
|
1477
|
+
}
|
|
1428
1478
|
throw error;
|
|
1429
1479
|
}
|
|
1430
1480
|
}
|
|
@@ -25,6 +25,9 @@ type MockRun = {
|
|
|
25
25
|
>;
|
|
26
26
|
getInterrupt: jest.MockedFunction<Run<t.IState>['getInterrupt']>;
|
|
27
27
|
getHaltReason: jest.MockedFunction<Run<t.IState>['getHaltReason']>;
|
|
28
|
+
getChildCheckpointThreadIds: jest.MockedFunction<
|
|
29
|
+
Run<t.IState>['getChildCheckpointThreadIds']
|
|
30
|
+
>;
|
|
28
31
|
};
|
|
29
32
|
|
|
30
33
|
function createMockRun(outputText = 'ok'): MockRun {
|
|
@@ -45,6 +48,7 @@ function createMockRun(outputText = 'ok'): MockRun {
|
|
|
45
48
|
getCalibrationRatio: jest.fn(() => 1),
|
|
46
49
|
getInterrupt: jest.fn(() => undefined),
|
|
47
50
|
getHaltReason: jest.fn(() => undefined),
|
|
51
|
+
getChildCheckpointThreadIds: jest.fn(() => []),
|
|
48
52
|
};
|
|
49
53
|
}
|
|
50
54
|
|
|
@@ -1119,6 +1123,55 @@ describe('JsonlSessionStore', () => {
|
|
|
1119
1123
|
});
|
|
1120
1124
|
});
|
|
1121
1125
|
|
|
1126
|
+
it('records and resets child checkpoint threads owned by a run', async () => {
|
|
1127
|
+
const checkpointer = new MemorySaver();
|
|
1128
|
+
const childThreadId = 'subagent:owned-child';
|
|
1129
|
+
const mockRun = createMockRun('child result');
|
|
1130
|
+
mockRun.getChildCheckpointThreadIds.mockReturnValue([childThreadId]);
|
|
1131
|
+
mockRunCreate(mockRun);
|
|
1132
|
+
const session = await createAgentSession({
|
|
1133
|
+
cwd: dir,
|
|
1134
|
+
runId: 'template-run',
|
|
1135
|
+
checkpointing: { checkpointer },
|
|
1136
|
+
graphConfig: {
|
|
1137
|
+
type: 'standard',
|
|
1138
|
+
llmConfig: {
|
|
1139
|
+
provider: 'openAI' as never,
|
|
1140
|
+
model: 'test-model',
|
|
1141
|
+
},
|
|
1142
|
+
instructions: 'test',
|
|
1143
|
+
},
|
|
1144
|
+
});
|
|
1145
|
+
await putCheckpoint({
|
|
1146
|
+
checkpointer,
|
|
1147
|
+
threadId: childThreadId,
|
|
1148
|
+
id: 'checkpoint_child',
|
|
1149
|
+
});
|
|
1150
|
+
|
|
1151
|
+
await session.run('fresh turn', { runId: 'run_with_child' });
|
|
1152
|
+
|
|
1153
|
+
expect(
|
|
1154
|
+
session.getSessionStore()?.getCheckpoints(childThreadId).at(-1)?.data
|
|
1155
|
+
).toMatchObject({
|
|
1156
|
+
source: 'run',
|
|
1157
|
+
runId: 'run_with_child',
|
|
1158
|
+
threadId: childThreadId,
|
|
1159
|
+
});
|
|
1160
|
+
const firstMessage = session
|
|
1161
|
+
.getSessionStore()
|
|
1162
|
+
?.getPath()
|
|
1163
|
+
.find((entry) => entry.type === 'message');
|
|
1164
|
+
await session.branch(firstMessage?.id ?? '', { position: 'at' });
|
|
1165
|
+
|
|
1166
|
+
const tuple = await checkpointer.getTuple({
|
|
1167
|
+
configurable: { thread_id: childThreadId },
|
|
1168
|
+
});
|
|
1169
|
+
expect(tuple).toBeUndefined();
|
|
1170
|
+
expect(
|
|
1171
|
+
session.getSessionStore()?.getCheckpoints(childThreadId).at(-1)?.data
|
|
1172
|
+
).toMatchObject({ source: 'reset', reason: 'branch' });
|
|
1173
|
+
});
|
|
1174
|
+
|
|
1122
1175
|
it('keeps checkpoint state when branching to the active JSONL leaf', async () => {
|
|
1123
1176
|
const checkpointer = new MemorySaver();
|
|
1124
1177
|
const session = await createAgentSession({
|
|
@@ -21,6 +21,7 @@ import * as providers from '@/llm/providers';
|
|
|
21
21
|
import { Run } from '@/run';
|
|
22
22
|
|
|
23
23
|
const CHILD_RESPONSE = 'Research result: Paris is the capital of France.';
|
|
24
|
+
const OVERRIDDEN_CHILD_RESPONSE = 'Deterministic child override result.';
|
|
24
25
|
|
|
25
26
|
const callerConfig: Partial<RunnableConfig> & {
|
|
26
27
|
version: 'v1' | 'v2';
|
|
@@ -226,6 +227,49 @@ describe('Subagent Integration', () => {
|
|
|
226
227
|
expect(subagentTool).toBeDefined();
|
|
227
228
|
});
|
|
228
229
|
|
|
230
|
+
it('only applies an explicitly configured subagent model override', async () => {
|
|
231
|
+
const invokeSubagent = async (
|
|
232
|
+
overrideSubagents: boolean
|
|
233
|
+
): Promise<string> => {
|
|
234
|
+
const run = await Run.create<t.IState>({
|
|
235
|
+
runId: `subagent-model-override-${overrideSubagents}-${Date.now()}`,
|
|
236
|
+
graphConfig: {
|
|
237
|
+
type: 'standard',
|
|
238
|
+
agents: [createParentAgent()],
|
|
239
|
+
},
|
|
240
|
+
returnContent: true,
|
|
241
|
+
skipCleanup: true,
|
|
242
|
+
});
|
|
243
|
+
const graph = run.Graph as StandardGraph;
|
|
244
|
+
const model = new FakeListChatModel({
|
|
245
|
+
responses: [OVERRIDDEN_CHILD_RESPONSE],
|
|
246
|
+
});
|
|
247
|
+
graph.overrideModel = model;
|
|
248
|
+
if (overrideSubagents) {
|
|
249
|
+
graph.setSubagentModelOverride(model);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const context = graph.agentContexts.get('parent');
|
|
253
|
+
const subagentTool = (context?.graphTools as t.GenericTool[]).find(
|
|
254
|
+
(tool) => 'name' in tool && tool.name === Constants.SUBAGENT
|
|
255
|
+
);
|
|
256
|
+
expect(subagentTool).toBeDefined();
|
|
257
|
+
|
|
258
|
+
return String(
|
|
259
|
+
await subagentTool!.invoke(
|
|
260
|
+
{
|
|
261
|
+
description: 'What is the capital of France?',
|
|
262
|
+
subagent_type: 'researcher',
|
|
263
|
+
},
|
|
264
|
+
callerConfig
|
|
265
|
+
)
|
|
266
|
+
);
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
await expect(invokeSubagent(false)).resolves.toBe(CHILD_RESPONSE);
|
|
270
|
+
await expect(invokeSubagent(true)).resolves.toBe(OVERRIDDEN_CHILD_RESPONSE);
|
|
271
|
+
});
|
|
272
|
+
|
|
229
273
|
it('inherits eager event-tool settings into self-spawn child graphs', async () => {
|
|
230
274
|
const originalCreateWorkflow = StandardGraph.prototype.createWorkflow;
|
|
231
275
|
const observedChildGraphs: Array<{
|