@librechat/agents 3.2.52 → 3.2.53
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/run.cjs +16 -0
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/esm/run.mjs +16 -0
- package/dist/esm/run.mjs.map +1 -1
- package/dist/types/run.d.ts +9 -9
- package/dist/types/session/types.d.ts +1 -0
- package/dist/types/types/run.d.ts +17 -0
- package/package.json +5 -2
- package/src/run.ts +24 -22
- package/src/session/types.ts +4 -1
- package/src/specs/durability-checkpoint.integration.test.ts +243 -0
- package/src/tools/__tests__/hitl.test.ts +167 -0
- package/src/types/run.ts +19 -0
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { MongoClient } from 'mongodb';
|
|
2
|
+
import { HumanMessage } from '@langchain/core/messages';
|
|
3
|
+
import { MongoMemoryServer } from 'mongodb-memory-server-core';
|
|
4
|
+
import { MongoDBSaver } from '@langchain/langgraph-checkpoint-mongodb';
|
|
5
|
+
import { describe, it, expect, beforeAll, afterAll } from '@jest/globals';
|
|
6
|
+
import {
|
|
7
|
+
END,
|
|
8
|
+
START,
|
|
9
|
+
Command,
|
|
10
|
+
Annotation,
|
|
11
|
+
StateGraph,
|
|
12
|
+
interrupt,
|
|
13
|
+
} from '@langchain/langgraph';
|
|
14
|
+
import type * as t from '@/types';
|
|
15
|
+
import { Providers } from '@/common';
|
|
16
|
+
import { Run } from '@/run';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* End-to-end proof against a real Mongo store that the durability default is
|
|
20
|
+
* the storage optimization it claims to be, not just a config flag. A real
|
|
21
|
+
* `Run` with a fake streaming model (no provider API hit) writes through the
|
|
22
|
+
* official MongoDBSaver, and a langgraph interrupt graph exercises the HITL
|
|
23
|
+
* pause/resume boundary. `exit` persists only at the exit/interrupt boundary
|
|
24
|
+
* with no per-superstep writes; `async` checkpoints every superstep.
|
|
25
|
+
*/
|
|
26
|
+
describe('durability default — real Mongo checkpointer', () => {
|
|
27
|
+
let mongod: MongoMemoryServer;
|
|
28
|
+
let client: MongoClient;
|
|
29
|
+
|
|
30
|
+
beforeAll(async () => {
|
|
31
|
+
// First CI run downloads a mongod binary — allow time beyond the 60s default.
|
|
32
|
+
mongod = await MongoMemoryServer.create();
|
|
33
|
+
client = new MongoClient(mongod.getUri());
|
|
34
|
+
await client.connect();
|
|
35
|
+
}, 120000);
|
|
36
|
+
|
|
37
|
+
afterAll(async () => {
|
|
38
|
+
await client.close();
|
|
39
|
+
await mongod.stop();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const counts = async (
|
|
43
|
+
dbName: string
|
|
44
|
+
): Promise<{ checkpoints: number; writes: number }> => {
|
|
45
|
+
const db = client.db(dbName);
|
|
46
|
+
const [checkpoints, writes] = await Promise.all([
|
|
47
|
+
db.collection('checkpoints').countDocuments({}),
|
|
48
|
+
db.collection('checkpoint_writes').countDocuments({}),
|
|
49
|
+
]);
|
|
50
|
+
return { checkpoints, writes };
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
describe('normal run (real Run + fake model)', () => {
|
|
54
|
+
const runOnce = async (
|
|
55
|
+
dbName: string,
|
|
56
|
+
durability?: t.Durability
|
|
57
|
+
): Promise<void> => {
|
|
58
|
+
const run = await Run.create<t.IState>({
|
|
59
|
+
runId: `durability-${dbName}`,
|
|
60
|
+
graphConfig: {
|
|
61
|
+
type: 'standard',
|
|
62
|
+
agents: [
|
|
63
|
+
{
|
|
64
|
+
agentId: 'a',
|
|
65
|
+
provider: Providers.OPENAI,
|
|
66
|
+
clientOptions: { modelName: 'gpt-4o-mini', apiKey: 'test-key' },
|
|
67
|
+
instructions: 'noop',
|
|
68
|
+
maxContextTokens: 8000,
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
compileOptions: {
|
|
72
|
+
checkpointer: new MongoDBSaver({ client, dbName }),
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
// Fake streaming model: one assistant turn, no tool calls -> agent -> END.
|
|
77
|
+
run.Graph?.overrideTestModel(['done'], 1);
|
|
78
|
+
await run.processStream(
|
|
79
|
+
{ messages: [new HumanMessage('hi')] },
|
|
80
|
+
{
|
|
81
|
+
version: 'v2',
|
|
82
|
+
...(durability != null ? { durability } : {}),
|
|
83
|
+
configurable: { thread_id: dbName },
|
|
84
|
+
}
|
|
85
|
+
);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
it('exit default writes one checkpoint and no per-superstep writes; async writes more of both', async () => {
|
|
89
|
+
await runOnce('normal_exit');
|
|
90
|
+
await runOnce('normal_async', 'async');
|
|
91
|
+
|
|
92
|
+
const exitCounts = await counts('normal_exit');
|
|
93
|
+
const asyncCounts = await counts('normal_async');
|
|
94
|
+
|
|
95
|
+
// exit: only the final checkpoint, zero per-superstep pending writes.
|
|
96
|
+
expect(exitCounts).toEqual({ checkpoints: 1, writes: 0 });
|
|
97
|
+
// async: a checkpoint plus pending writes for every superstep.
|
|
98
|
+
expect(asyncCounts.checkpoints).toBeGreaterThan(exitCounts.checkpoints);
|
|
99
|
+
expect(asyncCounts.writes).toBeGreaterThan(exitCounts.writes);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe('HITL interrupt (langgraph graph + real saver)', () => {
|
|
104
|
+
const State = Annotation.Root({
|
|
105
|
+
steps: Annotation<string[]>({
|
|
106
|
+
reducer: (a, b) => a.concat(b),
|
|
107
|
+
default: () => [],
|
|
108
|
+
}),
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const drain = async (stream: AsyncIterable<unknown>): Promise<void> => {
|
|
112
|
+
for await (const _event of stream) {
|
|
113
|
+
// Consume the event stream exactly like Run.processStream does.
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const runInterruptGraph = async (
|
|
118
|
+
dbName: string,
|
|
119
|
+
durability: t.Durability
|
|
120
|
+
): Promise<{
|
|
121
|
+
atPause: { checkpoints: number; writes: number };
|
|
122
|
+
done: { checkpoints: number; writes: number };
|
|
123
|
+
}> => {
|
|
124
|
+
const saver = new MongoDBSaver({ client, dbName });
|
|
125
|
+
const graph = new StateGraph(State)
|
|
126
|
+
.addNode('a', () => ({ steps: ['a'] }))
|
|
127
|
+
.addNode('b', () => {
|
|
128
|
+
interrupt({ ask: 'approve?' });
|
|
129
|
+
return { steps: ['b'] };
|
|
130
|
+
})
|
|
131
|
+
.addNode('c', () => ({ steps: ['c'] }))
|
|
132
|
+
.addEdge(START, 'a')
|
|
133
|
+
.addEdge('a', 'b')
|
|
134
|
+
.addEdge('b', 'c')
|
|
135
|
+
.addEdge('c', END)
|
|
136
|
+
.compile({ checkpointer: saver });
|
|
137
|
+
|
|
138
|
+
// durability rides in the 2nd-arg config, exactly as run.ts threads it.
|
|
139
|
+
const cfg = {
|
|
140
|
+
configurable: { thread_id: dbName },
|
|
141
|
+
durability,
|
|
142
|
+
version: 'v2' as const,
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
await drain(graph.streamEvents({ steps: [] }, cfg, { raiseError: true }));
|
|
146
|
+
const atPause = await counts(dbName);
|
|
147
|
+
|
|
148
|
+
await drain(
|
|
149
|
+
graph.streamEvents(new Command({ resume: 'ok' }), cfg, {
|
|
150
|
+
raiseError: true,
|
|
151
|
+
})
|
|
152
|
+
);
|
|
153
|
+
const done = await counts(dbName);
|
|
154
|
+
|
|
155
|
+
return { atPause, done };
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
it('exit checkpoints only at the interrupt boundary and final exit; async checkpoints every superstep', async () => {
|
|
159
|
+
const exitRun = await runInterruptGraph('hitl_exit', 'exit');
|
|
160
|
+
const asyncRun = await runInterruptGraph('hitl_async', 'async');
|
|
161
|
+
|
|
162
|
+
// exit: one checkpoint at the pause (the interrupt), two once resumed
|
|
163
|
+
// (interrupt boundary + final exit).
|
|
164
|
+
expect(exitRun.atPause.checkpoints).toBe(1);
|
|
165
|
+
expect(exitRun.done.checkpoints).toBe(2);
|
|
166
|
+
|
|
167
|
+
// async: a checkpoint for every superstep, strictly more at each stage.
|
|
168
|
+
expect(asyncRun.atPause.checkpoints).toBeGreaterThan(
|
|
169
|
+
exitRun.atPause.checkpoints
|
|
170
|
+
);
|
|
171
|
+
expect(asyncRun.done.checkpoints).toBeGreaterThan(
|
|
172
|
+
exitRun.done.checkpoints
|
|
173
|
+
);
|
|
174
|
+
expect(asyncRun.done.writes).toBeGreaterThan(exitRun.done.writes);
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
describe('multiple interrupts (sequential ask-user-questions)', () => {
|
|
179
|
+
const State = Annotation.Root({
|
|
180
|
+
qa: Annotation<string[]>({
|
|
181
|
+
reducer: (a, b) => a.concat(b),
|
|
182
|
+
default: () => [],
|
|
183
|
+
}),
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
const drain = async (stream: AsyncIterable<unknown>): Promise<void> => {
|
|
187
|
+
for await (const _event of stream) {
|
|
188
|
+
// Consume the event stream.
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
it('checkpoints each interrupt boundary, chains them, and discards none across resumes', async () => {
|
|
193
|
+
const dbName = 'multi_exit';
|
|
194
|
+
const saver = new MongoDBSaver({ client, dbName });
|
|
195
|
+
const graph = new StateGraph(State)
|
|
196
|
+
.addNode('ask1', () => ({ qa: [`Q1=${interrupt({ q: 'Q1' })}`] }))
|
|
197
|
+
.addNode('ask2', () => ({ qa: [`Q2=${interrupt({ q: 'Q2' })}`] }))
|
|
198
|
+
.addNode('done', () => ({ qa: ['done'] }))
|
|
199
|
+
.addEdge(START, 'ask1')
|
|
200
|
+
.addEdge('ask1', 'ask2')
|
|
201
|
+
.addEdge('ask2', 'done')
|
|
202
|
+
.addEdge('done', END)
|
|
203
|
+
.compile({ checkpointer: saver });
|
|
204
|
+
|
|
205
|
+
const cfg = {
|
|
206
|
+
configurable: { thread_id: dbName },
|
|
207
|
+
durability: 'exit' as const,
|
|
208
|
+
version: 'v2' as const,
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
// First question -> pauses at ask1, one checkpoint at that boundary.
|
|
212
|
+
await drain(graph.streamEvents({ qa: [] }, cfg, { raiseError: true }));
|
|
213
|
+
let state = await graph.getState(cfg);
|
|
214
|
+
expect(state.next).toEqual(['ask1']);
|
|
215
|
+
expect((await counts(dbName)).checkpoints).toBe(1);
|
|
216
|
+
|
|
217
|
+
// Answer 1 -> the run continues and the LLM asks a second question.
|
|
218
|
+
// The chain grows to 2: the first interrupt checkpoint is retained as
|
|
219
|
+
// the parent, not discarded or overwritten.
|
|
220
|
+
await drain(
|
|
221
|
+
graph.streamEvents(new Command({ resume: 'A1' }), cfg, {
|
|
222
|
+
raiseError: true,
|
|
223
|
+
})
|
|
224
|
+
);
|
|
225
|
+
state = await graph.getState(cfg);
|
|
226
|
+
expect(state.next).toEqual(['ask2']);
|
|
227
|
+
expect(state.values.qa).toEqual(['Q1=A1']);
|
|
228
|
+
expect((await counts(dbName)).checkpoints).toBe(2);
|
|
229
|
+
|
|
230
|
+
// Answer 2 -> run completes; both answers applied, three checkpoints
|
|
231
|
+
// total (two interrupt boundaries + final exit).
|
|
232
|
+
await drain(
|
|
233
|
+
graph.streamEvents(new Command({ resume: 'A2' }), cfg, {
|
|
234
|
+
raiseError: true,
|
|
235
|
+
})
|
|
236
|
+
);
|
|
237
|
+
state = await graph.getState(cfg);
|
|
238
|
+
expect(state.next).toEqual([]);
|
|
239
|
+
expect(state.values.qa).toEqual(['Q1=A1', 'Q2=A2', 'done']);
|
|
240
|
+
expect((await counts(dbName)).checkpoints).toBe(3);
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
});
|
|
@@ -761,6 +761,173 @@ describe('Run integration — HITL fallback checkpointer + resume', () => {
|
|
|
761
761
|
expect(run.Graph?.compileOptions?.checkpointer).toBe(hostCheckpointer);
|
|
762
762
|
});
|
|
763
763
|
|
|
764
|
+
it('processStream defaults durability to "exit" when a checkpointer is active', async () => {
|
|
765
|
+
const { Run } = await import('@/run');
|
|
766
|
+
const { Providers } = await import('@/common');
|
|
767
|
+
|
|
768
|
+
const run = await Run.create<t.IState>({
|
|
769
|
+
runId: 'durability-exit-default',
|
|
770
|
+
graphConfig: {
|
|
771
|
+
type: 'standard',
|
|
772
|
+
agents: [
|
|
773
|
+
{
|
|
774
|
+
agentId: 'a',
|
|
775
|
+
provider: Providers.OPENAI,
|
|
776
|
+
clientOptions: { modelName: 'gpt-4o-mini', apiKey: 'test-key' },
|
|
777
|
+
instructions: 'noop',
|
|
778
|
+
maxContextTokens: 8000,
|
|
779
|
+
},
|
|
780
|
+
],
|
|
781
|
+
},
|
|
782
|
+
humanInTheLoop: { enabled: true },
|
|
783
|
+
});
|
|
784
|
+
expect(run.Graph?.compileOptions?.checkpointer).toBeInstanceOf(MemorySaver);
|
|
785
|
+
|
|
786
|
+
const graph = new StateGraph(MessagesAnnotation)
|
|
787
|
+
.addNode('noop', (): MessagesUpdate => ({ messages: [] }))
|
|
788
|
+
.addEdge(START, 'noop')
|
|
789
|
+
.addEdge('noop', END)
|
|
790
|
+
.compile();
|
|
791
|
+
const spy = jest.spyOn(graph, 'streamEvents');
|
|
792
|
+
run.graphRunnable = graph as unknown as t.CompiledStateWorkflow;
|
|
793
|
+
|
|
794
|
+
await run.processStream(
|
|
795
|
+
{ messages: [] },
|
|
796
|
+
{ version: 'v2', configurable: { thread_id: 't' } }
|
|
797
|
+
);
|
|
798
|
+
|
|
799
|
+
const streamedConfig = spy.mock.calls[0]?.[1] as
|
|
800
|
+
| t.RunStreamConfig
|
|
801
|
+
| undefined;
|
|
802
|
+
expect(streamedConfig?.durability).toBe('exit');
|
|
803
|
+
});
|
|
804
|
+
|
|
805
|
+
it('processStream respects an explicit caller durability over the checkpointer default', async () => {
|
|
806
|
+
const { Run } = await import('@/run');
|
|
807
|
+
const { Providers } = await import('@/common');
|
|
808
|
+
|
|
809
|
+
const run = await Run.create<t.IState>({
|
|
810
|
+
runId: 'durability-explicit-override',
|
|
811
|
+
graphConfig: {
|
|
812
|
+
type: 'standard',
|
|
813
|
+
agents: [
|
|
814
|
+
{
|
|
815
|
+
agentId: 'a',
|
|
816
|
+
provider: Providers.OPENAI,
|
|
817
|
+
clientOptions: { modelName: 'gpt-4o-mini', apiKey: 'test-key' },
|
|
818
|
+
instructions: 'noop',
|
|
819
|
+
maxContextTokens: 8000,
|
|
820
|
+
},
|
|
821
|
+
],
|
|
822
|
+
},
|
|
823
|
+
humanInTheLoop: { enabled: true },
|
|
824
|
+
});
|
|
825
|
+
|
|
826
|
+
const graph = new StateGraph(MessagesAnnotation)
|
|
827
|
+
.addNode('noop', (): MessagesUpdate => ({ messages: [] }))
|
|
828
|
+
.addEdge(START, 'noop')
|
|
829
|
+
.addEdge('noop', END)
|
|
830
|
+
.compile();
|
|
831
|
+
const spy = jest.spyOn(graph, 'streamEvents');
|
|
832
|
+
run.graphRunnable = graph as unknown as t.CompiledStateWorkflow;
|
|
833
|
+
|
|
834
|
+
await run.processStream(
|
|
835
|
+
{ messages: [] },
|
|
836
|
+
{ version: 'v2', durability: 'sync', configurable: { thread_id: 't' } }
|
|
837
|
+
);
|
|
838
|
+
|
|
839
|
+
const streamedConfig = spy.mock.calls[0]?.[1] as
|
|
840
|
+
| t.RunStreamConfig
|
|
841
|
+
| undefined;
|
|
842
|
+
expect(streamedConfig?.durability).toBe('sync');
|
|
843
|
+
});
|
|
844
|
+
|
|
845
|
+
it('processStream leaves durability unset when no checkpointer is active', async () => {
|
|
846
|
+
const { Run } = await import('@/run');
|
|
847
|
+
const { Providers } = await import('@/common');
|
|
848
|
+
|
|
849
|
+
const run = await Run.create<t.IState>({
|
|
850
|
+
runId: 'durability-no-checkpointer',
|
|
851
|
+
graphConfig: {
|
|
852
|
+
type: 'standard',
|
|
853
|
+
agents: [
|
|
854
|
+
{
|
|
855
|
+
agentId: 'a',
|
|
856
|
+
provider: Providers.OPENAI,
|
|
857
|
+
clientOptions: { modelName: 'gpt-4o-mini', apiKey: 'test-key' },
|
|
858
|
+
instructions: 'noop',
|
|
859
|
+
maxContextTokens: 8000,
|
|
860
|
+
},
|
|
861
|
+
],
|
|
862
|
+
},
|
|
863
|
+
// humanInTheLoop omitted — no checkpointer installed
|
|
864
|
+
});
|
|
865
|
+
expect(run.Graph?.compileOptions?.checkpointer).toBeUndefined();
|
|
866
|
+
|
|
867
|
+
const graph = new StateGraph(MessagesAnnotation)
|
|
868
|
+
.addNode('noop', (): MessagesUpdate => ({ messages: [] }))
|
|
869
|
+
.addEdge(START, 'noop')
|
|
870
|
+
.addEdge('noop', END)
|
|
871
|
+
.compile();
|
|
872
|
+
const spy = jest.spyOn(graph, 'streamEvents');
|
|
873
|
+
run.graphRunnable = graph as unknown as t.CompiledStateWorkflow;
|
|
874
|
+
|
|
875
|
+
await run.processStream(
|
|
876
|
+
{ messages: [] },
|
|
877
|
+
{ version: 'v2', configurable: { thread_id: 't' } }
|
|
878
|
+
);
|
|
879
|
+
|
|
880
|
+
const streamedConfig = spy.mock.calls[0]?.[1] as
|
|
881
|
+
| t.RunStreamConfig
|
|
882
|
+
| undefined;
|
|
883
|
+
expect(streamedConfig?.durability).toBeUndefined();
|
|
884
|
+
});
|
|
885
|
+
|
|
886
|
+
it('defaults durability to "exit" when HITL installs the fallback checkpointer but caller compileOptions omit it', async () => {
|
|
887
|
+
const { Run } = await import('@/run');
|
|
888
|
+
const { Providers } = await import('@/common');
|
|
889
|
+
|
|
890
|
+
const run = await Run.create<t.IState>({
|
|
891
|
+
runId: 'durability-hitl-fallback-compileopts',
|
|
892
|
+
graphConfig: {
|
|
893
|
+
type: 'standard',
|
|
894
|
+
agents: [
|
|
895
|
+
{
|
|
896
|
+
agentId: 'a',
|
|
897
|
+
provider: Providers.OPENAI,
|
|
898
|
+
clientOptions: { modelName: 'gpt-4o-mini', apiKey: 'test-key' },
|
|
899
|
+
instructions: 'noop',
|
|
900
|
+
maxContextTokens: 8000,
|
|
901
|
+
},
|
|
902
|
+
],
|
|
903
|
+
// Caller compileOptions without a checkpointer: HITL adds a MemorySaver
|
|
904
|
+
// fallback to the compiled graph, but the constructor restores this raw
|
|
905
|
+
// metadata (no checkpointer) onto Graph.compileOptions.
|
|
906
|
+
compileOptions: { interruptBefore: [] },
|
|
907
|
+
},
|
|
908
|
+
humanInTheLoop: { enabled: true },
|
|
909
|
+
});
|
|
910
|
+
expect(run.Graph?.compileOptions?.checkpointer).toBeUndefined();
|
|
911
|
+
|
|
912
|
+
const graph = new StateGraph(MessagesAnnotation)
|
|
913
|
+
.addNode('noop', (): MessagesUpdate => ({ messages: [] }))
|
|
914
|
+
.addEdge(START, 'noop')
|
|
915
|
+
.addEdge('noop', END)
|
|
916
|
+
.compile();
|
|
917
|
+
const spy = jest.spyOn(graph, 'streamEvents');
|
|
918
|
+
run.graphRunnable = graph as unknown as t.CompiledStateWorkflow;
|
|
919
|
+
|
|
920
|
+
await run.processStream(
|
|
921
|
+
{ messages: [] },
|
|
922
|
+
{ version: 'v2', configurable: { thread_id: 't' } }
|
|
923
|
+
);
|
|
924
|
+
|
|
925
|
+
const streamedConfig = spy.mock.calls[0]?.[1] as
|
|
926
|
+
| t.RunStreamConfig
|
|
927
|
+
| undefined;
|
|
928
|
+
expect(streamedConfig?.durability).toBe('exit');
|
|
929
|
+
});
|
|
930
|
+
|
|
764
931
|
it('Run.resume forwards update + goto into the resume Command (langgraph 1.4.5)', async () => {
|
|
765
932
|
const { Run } = await import('@/run');
|
|
766
933
|
const { Providers } = await import('@/common');
|
package/src/types/run.ts
CHANGED
|
@@ -261,3 +261,22 @@ export type EventStreamOptions = {
|
|
|
261
261
|
callbacks?: g.ClientCallbacks;
|
|
262
262
|
keepContent?: boolean;
|
|
263
263
|
};
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* When to persist checkpoints during a run. Mirrors langgraph's option:
|
|
267
|
+
* `'async'`/`'sync'` checkpoint every superstep; `'exit'` checkpoints only
|
|
268
|
+
* at the graph's exit/interrupt boundary. Kept as a local union to avoid
|
|
269
|
+
* coupling to langgraph internals (it is not exported from the root).
|
|
270
|
+
*/
|
|
271
|
+
export type Durability = 'async' | 'sync' | 'exit';
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Config accepted by `processStream`/`resume`. Extends `RunnableConfig` with
|
|
275
|
+
* the stream `version`, an optional `run_id`, and an optional `durability`
|
|
276
|
+
* override forwarded to langgraph's run options.
|
|
277
|
+
*/
|
|
278
|
+
export type RunStreamConfig = Partial<RunnableConfig> & {
|
|
279
|
+
version: 'v1' | 'v2';
|
|
280
|
+
run_id?: string;
|
|
281
|
+
durability?: Durability;
|
|
282
|
+
};
|