@positronic/core 0.0.3 → 0.0.5
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/package.json +5 -1
- package/CLAUDE.md +0 -141
- package/dist/src/dsl/brain-runner.test.js +0 -733
- package/dist/src/dsl/brain.test.js +0 -4225
- package/dist/src/test-utils.js +0 -474
- package/dist/src/testing.js +0 -3
- package/dist/types/test-utils.d.ts +0 -94
- package/dist/types/test-utils.d.ts.map +0 -1
- package/dist/types/testing.d.ts +0 -2
- package/dist/types/testing.d.ts.map +0 -1
- package/docs/core-testing-guide.md +0 -289
- package/src/adapters/types.ts +0 -5
- package/src/clients/types.ts +0 -54
- package/src/dsl/brain-runner.test.ts +0 -384
- package/src/dsl/brain-runner.ts +0 -111
- package/src/dsl/brain.test.ts +0 -1981
- package/src/dsl/brain.ts +0 -740
- package/src/dsl/constants.ts +0 -16
- package/src/dsl/json-patch.ts +0 -42
- package/src/dsl/types.ts +0 -13
- package/src/index.ts +0 -36
- package/src/resources/resource-loader.ts +0 -8
- package/src/resources/resources.ts +0 -267
- package/src/test-utils.ts +0 -254
- package/test/resources.test.ts +0 -248
- package/tsconfig.json +0 -10
|
@@ -1,384 +0,0 @@
|
|
|
1
|
-
import { BrainRunner } from './brain-runner.js';
|
|
2
|
-
import { brain, type SerializedStep } from './brain.js';
|
|
3
|
-
import { BRAIN_EVENTS, STATUS } from './constants.js';
|
|
4
|
-
import { jest, describe, it, expect, beforeEach } from '@jest/globals';
|
|
5
|
-
import { ObjectGenerator } from '../clients/types.js';
|
|
6
|
-
import { Adapter } from '../adapters/types.js';
|
|
7
|
-
import { createResources, type Resources } from '../resources/resources.js';
|
|
8
|
-
import type { ResourceLoader } from '../resources/resource-loader.js';
|
|
9
|
-
import { z } from 'zod';
|
|
10
|
-
|
|
11
|
-
describe('BrainRunner', () => {
|
|
12
|
-
const mockGenerateObject = jest.fn<ObjectGenerator['generateObject']>();
|
|
13
|
-
const mockClient: jest.Mocked<ObjectGenerator> = {
|
|
14
|
-
generateObject: mockGenerateObject,
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
const mockDispatch = jest.fn<Adapter['dispatch']>();
|
|
18
|
-
const mockAdapter: jest.Mocked<Adapter> = {
|
|
19
|
-
dispatch: mockDispatch,
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
beforeEach(() => {
|
|
23
|
-
jest.clearAllMocks();
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it('should run a brain and dispatch events to adapters', async () => {
|
|
27
|
-
const runner = new BrainRunner({
|
|
28
|
-
adapters: [mockAdapter],
|
|
29
|
-
client: mockClient,
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
const testBrain = brain('Test Brain')
|
|
33
|
-
.step('First Step', () => ({ value: 42 }))
|
|
34
|
-
.step('Async Step', async ({ state }) => {
|
|
35
|
-
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
36
|
-
return { ...state, asyncValue: 'completed' };
|
|
37
|
-
})
|
|
38
|
-
.step('Final Step', ({ state }) => ({
|
|
39
|
-
...state,
|
|
40
|
-
finalValue: state.value * 2,
|
|
41
|
-
}));
|
|
42
|
-
|
|
43
|
-
await runner.run(testBrain);
|
|
44
|
-
|
|
45
|
-
// Verify adapter received all events in correct order
|
|
46
|
-
expect(mockAdapter.dispatch).toHaveBeenCalledWith(
|
|
47
|
-
expect.objectContaining({
|
|
48
|
-
type: BRAIN_EVENTS.START,
|
|
49
|
-
brainTitle: 'Test Brain',
|
|
50
|
-
})
|
|
51
|
-
);
|
|
52
|
-
|
|
53
|
-
expect(mockAdapter.dispatch).toHaveBeenCalledWith(
|
|
54
|
-
expect.objectContaining({
|
|
55
|
-
type: BRAIN_EVENTS.STEP_COMPLETE,
|
|
56
|
-
stepTitle: 'First Step',
|
|
57
|
-
})
|
|
58
|
-
);
|
|
59
|
-
|
|
60
|
-
expect(mockAdapter.dispatch).toHaveBeenCalledWith(
|
|
61
|
-
expect.objectContaining({
|
|
62
|
-
type: BRAIN_EVENTS.STEP_COMPLETE,
|
|
63
|
-
stepTitle: 'Async Step',
|
|
64
|
-
})
|
|
65
|
-
);
|
|
66
|
-
|
|
67
|
-
expect(mockAdapter.dispatch).toHaveBeenCalledWith(
|
|
68
|
-
expect.objectContaining({
|
|
69
|
-
type: BRAIN_EVENTS.STEP_COMPLETE,
|
|
70
|
-
stepTitle: 'Final Step',
|
|
71
|
-
})
|
|
72
|
-
);
|
|
73
|
-
|
|
74
|
-
expect(mockAdapter.dispatch).toHaveBeenCalledWith(
|
|
75
|
-
expect.objectContaining({
|
|
76
|
-
type: BRAIN_EVENTS.COMPLETE,
|
|
77
|
-
status: STATUS.COMPLETE,
|
|
78
|
-
})
|
|
79
|
-
);
|
|
80
|
-
|
|
81
|
-
// Verify the order of events
|
|
82
|
-
const stepCompletions = mockAdapter.dispatch.mock.calls
|
|
83
|
-
.filter((call) => (call[0] as any).type === BRAIN_EVENTS.STEP_COMPLETE)
|
|
84
|
-
.map((call) => (call[0] as any).stepTitle);
|
|
85
|
-
|
|
86
|
-
expect(stepCompletions).toEqual(['First Step', 'Async Step', 'Final Step']);
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it('should handle brain errors', async () => {
|
|
90
|
-
const runner = new BrainRunner({
|
|
91
|
-
adapters: [mockAdapter],
|
|
92
|
-
client: mockClient,
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
const errorBrain = brain('Error Brain').step('Error Step', () => {
|
|
96
|
-
throw new Error('Test error');
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
try {
|
|
100
|
-
await runner.run(errorBrain);
|
|
101
|
-
} catch (error) {
|
|
102
|
-
// Expected error
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Verify error event was dispatched
|
|
106
|
-
expect(mockAdapter.dispatch).toHaveBeenCalledWith(
|
|
107
|
-
expect.objectContaining({
|
|
108
|
-
type: BRAIN_EVENTS.ERROR,
|
|
109
|
-
error: expect.objectContaining({
|
|
110
|
-
message: 'Test error',
|
|
111
|
-
}),
|
|
112
|
-
})
|
|
113
|
-
);
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
it('should maintain state between steps', async () => {
|
|
117
|
-
const runner = new BrainRunner({
|
|
118
|
-
adapters: [],
|
|
119
|
-
client: mockClient,
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
const testBrain = brain('Test Brain')
|
|
123
|
-
.step('First Step', () => ({ count: 1 }))
|
|
124
|
-
.step('Second Step', ({ state }) => ({
|
|
125
|
-
count: state.count + 1,
|
|
126
|
-
}));
|
|
127
|
-
|
|
128
|
-
const result = await runner.run(testBrain);
|
|
129
|
-
|
|
130
|
-
expect(result.count).toEqual(2);
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
it('should pass resources to step actions', async () => {
|
|
134
|
-
const mockLoad = jest.fn(
|
|
135
|
-
async (
|
|
136
|
-
resourceName: string,
|
|
137
|
-
type?: 'text' | 'binary'
|
|
138
|
-
): Promise<string | Buffer> => {
|
|
139
|
-
if (type === 'binary') {
|
|
140
|
-
return Buffer.from(`content of ${resourceName}`);
|
|
141
|
-
}
|
|
142
|
-
return `content of ${resourceName}`;
|
|
143
|
-
}
|
|
144
|
-
) as jest.MockedFunction<ResourceLoader['load']>;
|
|
145
|
-
|
|
146
|
-
const mockResourceLoader: ResourceLoader = {
|
|
147
|
-
load: mockLoad,
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
const testManifest = {
|
|
151
|
-
myTextFile: {
|
|
152
|
-
type: 'text' as const,
|
|
153
|
-
key: 'myTextFile',
|
|
154
|
-
path: '/test/myTextFile.txt',
|
|
155
|
-
},
|
|
156
|
-
myBinaryFile: {
|
|
157
|
-
type: 'binary' as const,
|
|
158
|
-
key: 'myBinaryFile',
|
|
159
|
-
path: '/test/myBinaryFile.bin',
|
|
160
|
-
},
|
|
161
|
-
} as const;
|
|
162
|
-
|
|
163
|
-
const testResources = createResources(mockResourceLoader, testManifest);
|
|
164
|
-
|
|
165
|
-
const runner = new BrainRunner({
|
|
166
|
-
adapters: [],
|
|
167
|
-
client: mockClient,
|
|
168
|
-
}).withResources(testResources);
|
|
169
|
-
|
|
170
|
-
let textContent: string | undefined;
|
|
171
|
-
let binaryContent: Buffer | undefined;
|
|
172
|
-
|
|
173
|
-
const resourceConsumingBrain = brain('Resource Brain').step(
|
|
174
|
-
'Load Resources',
|
|
175
|
-
async ({ resources }) => {
|
|
176
|
-
textContent = await (resources.myTextFile as any).loadText();
|
|
177
|
-
binaryContent = await (resources.myBinaryFile as any).loadBinary();
|
|
178
|
-
return {};
|
|
179
|
-
}
|
|
180
|
-
);
|
|
181
|
-
|
|
182
|
-
await runner.run(resourceConsumingBrain);
|
|
183
|
-
|
|
184
|
-
expect(mockLoad).toHaveBeenCalledWith('myTextFile', 'text');
|
|
185
|
-
expect(mockLoad).toHaveBeenCalledWith('myBinaryFile', 'binary');
|
|
186
|
-
expect(textContent).toBe('content of myTextFile');
|
|
187
|
-
expect(binaryContent?.toString()).toBe('content of myBinaryFile');
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
it('should chain adapters with withAdapters method', async () => {
|
|
191
|
-
const mockAdapter2: jest.Mocked<Adapter> = {
|
|
192
|
-
dispatch: jest.fn(),
|
|
193
|
-
};
|
|
194
|
-
const mockAdapter3: jest.Mocked<Adapter> = {
|
|
195
|
-
dispatch: jest.fn(),
|
|
196
|
-
};
|
|
197
|
-
|
|
198
|
-
const runner = new BrainRunner({
|
|
199
|
-
adapters: [mockAdapter],
|
|
200
|
-
client: mockClient,
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
// Chain additional adapters
|
|
204
|
-
const updatedRunner = runner.withAdapters([mockAdapter2, mockAdapter3]);
|
|
205
|
-
|
|
206
|
-
const testBrain = brain('Test Brain').step('Step 1', () => ({ value: 1 }));
|
|
207
|
-
|
|
208
|
-
await updatedRunner.run(testBrain);
|
|
209
|
-
|
|
210
|
-
// Verify all adapters received events
|
|
211
|
-
expect(mockAdapter.dispatch).toHaveBeenCalledWith(
|
|
212
|
-
expect.objectContaining({
|
|
213
|
-
type: BRAIN_EVENTS.START,
|
|
214
|
-
})
|
|
215
|
-
);
|
|
216
|
-
expect(mockAdapter2.dispatch).toHaveBeenCalledWith(
|
|
217
|
-
expect.objectContaining({
|
|
218
|
-
type: BRAIN_EVENTS.START,
|
|
219
|
-
})
|
|
220
|
-
);
|
|
221
|
-
expect(mockAdapter3.dispatch).toHaveBeenCalledWith(
|
|
222
|
-
expect.objectContaining({
|
|
223
|
-
type: BRAIN_EVENTS.START,
|
|
224
|
-
})
|
|
225
|
-
);
|
|
226
|
-
|
|
227
|
-
// Verify all adapters received the same number of events
|
|
228
|
-
expect(mockAdapter.dispatch).toHaveBeenCalledTimes(
|
|
229
|
-
mockAdapter2.dispatch.mock.calls.length
|
|
230
|
-
);
|
|
231
|
-
expect(mockAdapter2.dispatch).toHaveBeenCalledTimes(
|
|
232
|
-
mockAdapter3.dispatch.mock.calls.length
|
|
233
|
-
);
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
it('should replace client with withClient method', async () => {
|
|
237
|
-
const originalClient: jest.Mocked<ObjectGenerator> = {
|
|
238
|
-
generateObject: jest.fn(),
|
|
239
|
-
};
|
|
240
|
-
const newClient: jest.Mocked<ObjectGenerator> = {
|
|
241
|
-
generateObject: jest.fn(),
|
|
242
|
-
};
|
|
243
|
-
|
|
244
|
-
// Configure the new client's response
|
|
245
|
-
newClient.generateObject.mockResolvedValue({ result: 'from new client' });
|
|
246
|
-
|
|
247
|
-
const runner = new BrainRunner({
|
|
248
|
-
adapters: [],
|
|
249
|
-
client: originalClient,
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
// Replace the client
|
|
253
|
-
const updatedRunner = runner.withClient(newClient);
|
|
254
|
-
|
|
255
|
-
// Define schema once to ensure same reference
|
|
256
|
-
const testSchema = z.object({ result: z.string() });
|
|
257
|
-
|
|
258
|
-
const testBrain = brain('Test Brain').step(
|
|
259
|
-
'Generate',
|
|
260
|
-
async ({ client }) => {
|
|
261
|
-
const response = await client.generateObject({
|
|
262
|
-
prompt: 'test prompt',
|
|
263
|
-
schema: testSchema,
|
|
264
|
-
schemaName: 'TestSchema',
|
|
265
|
-
});
|
|
266
|
-
return { generated: response.result };
|
|
267
|
-
}
|
|
268
|
-
);
|
|
269
|
-
|
|
270
|
-
const result = await updatedRunner.run(testBrain);
|
|
271
|
-
|
|
272
|
-
// Verify new client was used, not the original
|
|
273
|
-
expect(originalClient.generateObject).not.toHaveBeenCalled();
|
|
274
|
-
expect(newClient.generateObject).toHaveBeenCalledWith({
|
|
275
|
-
prompt: 'test prompt',
|
|
276
|
-
schema: testSchema,
|
|
277
|
-
schemaName: 'TestSchema',
|
|
278
|
-
});
|
|
279
|
-
expect(result.generated).toBe('from new client');
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
it('should apply patches from initialCompletedSteps and continue from correct state', async () => {
|
|
283
|
-
const runner = new BrainRunner({
|
|
284
|
-
adapters: [mockAdapter],
|
|
285
|
-
client: mockClient,
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
// Simulate completed steps with patches
|
|
289
|
-
const completedSteps: SerializedStep[] = [
|
|
290
|
-
{
|
|
291
|
-
id: 'step-1',
|
|
292
|
-
title: 'First Step',
|
|
293
|
-
status: STATUS.COMPLETE,
|
|
294
|
-
patch: [
|
|
295
|
-
{
|
|
296
|
-
op: 'add',
|
|
297
|
-
path: '/count',
|
|
298
|
-
value: 10,
|
|
299
|
-
},
|
|
300
|
-
],
|
|
301
|
-
},
|
|
302
|
-
{
|
|
303
|
-
id: 'step-2',
|
|
304
|
-
title: 'Second Step',
|
|
305
|
-
status: STATUS.COMPLETE,
|
|
306
|
-
patch: [
|
|
307
|
-
{
|
|
308
|
-
op: 'add',
|
|
309
|
-
path: '/name',
|
|
310
|
-
value: 'test',
|
|
311
|
-
},
|
|
312
|
-
],
|
|
313
|
-
},
|
|
314
|
-
];
|
|
315
|
-
|
|
316
|
-
const testBrain = brain('Test Brain')
|
|
317
|
-
.step('First Step', () => ({ count: 10 }))
|
|
318
|
-
.step('Second Step', ({ state }) => ({ ...state, name: 'test' }))
|
|
319
|
-
.step('Third Step', ({ state }) => ({
|
|
320
|
-
...state,
|
|
321
|
-
count: state.count + 5,
|
|
322
|
-
message: `${state.name} completed`,
|
|
323
|
-
}));
|
|
324
|
-
|
|
325
|
-
const result = await runner.run(testBrain, {
|
|
326
|
-
initialCompletedSteps: completedSteps,
|
|
327
|
-
brainRunId: 'test-run-123',
|
|
328
|
-
});
|
|
329
|
-
|
|
330
|
-
// Verify the final state includes patches from completed steps
|
|
331
|
-
expect(result).toEqual({
|
|
332
|
-
count: 15,
|
|
333
|
-
name: 'test',
|
|
334
|
-
message: 'test completed',
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
// Verify that the brain runner applied the patches correctly
|
|
338
|
-
// The runner should have seen all steps execute, but the first two were already completed
|
|
339
|
-
const stepCompleteEvents = mockAdapter.dispatch.mock.calls.filter(
|
|
340
|
-
(call) => call[0].type === BRAIN_EVENTS.STEP_COMPLETE
|
|
341
|
-
);
|
|
342
|
-
|
|
343
|
-
// All steps will emit complete events in the current implementation
|
|
344
|
-
expect(stepCompleteEvents.length).toBeGreaterThanOrEqual(1);
|
|
345
|
-
});
|
|
346
|
-
|
|
347
|
-
it('should stop execution after specified number of steps with endAfter parameter', async () => {
|
|
348
|
-
const runner = new BrainRunner({
|
|
349
|
-
adapters: [mockAdapter],
|
|
350
|
-
client: mockClient,
|
|
351
|
-
});
|
|
352
|
-
|
|
353
|
-
const testBrain = brain('Test Brain')
|
|
354
|
-
.step('Step 1', () => ({ step1: 'done' }))
|
|
355
|
-
.step('Step 2', ({ state }) => ({ ...state, step2: 'done' }))
|
|
356
|
-
.step('Step 3', ({ state }) => ({ ...state, step3: 'done' }))
|
|
357
|
-
.step('Step 4', ({ state }) => ({ ...state, step4: 'done' }));
|
|
358
|
-
|
|
359
|
-
// Run brain but stop after 2 steps
|
|
360
|
-
const result = await runner.run(testBrain, {
|
|
361
|
-
endAfter: 2,
|
|
362
|
-
});
|
|
363
|
-
|
|
364
|
-
// Verify state only has results from first 2 steps
|
|
365
|
-
expect(result).toEqual({
|
|
366
|
-
step1: 'done',
|
|
367
|
-
step2: 'done',
|
|
368
|
-
});
|
|
369
|
-
|
|
370
|
-
// Verify only 2 step complete events were dispatched
|
|
371
|
-
const stepCompleteEvents = mockAdapter.dispatch.mock.calls
|
|
372
|
-
.filter((call) => call[0].type === BRAIN_EVENTS.STEP_COMPLETE)
|
|
373
|
-
.map((call) => (call[0] as any).stepTitle);
|
|
374
|
-
|
|
375
|
-
expect(stepCompleteEvents).toEqual(['Step 1', 'Step 2']);
|
|
376
|
-
|
|
377
|
-
// Verify that COMPLETE event was NOT dispatched (brain didn't finish)
|
|
378
|
-
const completeEvents = mockAdapter.dispatch.mock.calls.filter(
|
|
379
|
-
(call) => call[0].type === BRAIN_EVENTS.COMPLETE
|
|
380
|
-
);
|
|
381
|
-
|
|
382
|
-
expect(completeEvents.length).toBe(0);
|
|
383
|
-
});
|
|
384
|
-
});
|
package/src/dsl/brain-runner.ts
DELETED
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
import { BRAIN_EVENTS } from './constants.js';
|
|
2
|
-
import { applyPatches } from './json-patch.js';
|
|
3
|
-
import type { Adapter } from '../adapters/types.js';
|
|
4
|
-
import type { SerializedStep, Brain } from './brain.js';
|
|
5
|
-
import type { State } from './types.js';
|
|
6
|
-
import type { ObjectGenerator } from '../clients/types.js';
|
|
7
|
-
import type { Resources } from '../resources/resources.js';
|
|
8
|
-
|
|
9
|
-
export class BrainRunner {
|
|
10
|
-
constructor(
|
|
11
|
-
private options: {
|
|
12
|
-
adapters: Adapter[];
|
|
13
|
-
client: ObjectGenerator;
|
|
14
|
-
resources?: Resources;
|
|
15
|
-
}
|
|
16
|
-
) {}
|
|
17
|
-
|
|
18
|
-
withAdapters(adapters: Adapter[]): BrainRunner {
|
|
19
|
-
const { adapters: existingAdapters } = this.options;
|
|
20
|
-
return new BrainRunner({
|
|
21
|
-
...this.options,
|
|
22
|
-
adapters: [...existingAdapters, ...adapters],
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
withClient(client: ObjectGenerator): BrainRunner {
|
|
27
|
-
return new BrainRunner({
|
|
28
|
-
...this.options,
|
|
29
|
-
client,
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
withResources(resources: Resources): BrainRunner {
|
|
34
|
-
return new BrainRunner({
|
|
35
|
-
...this.options,
|
|
36
|
-
resources,
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
async run<TOptions extends object = {}, TState extends State = {}>(
|
|
41
|
-
brain: Brain<TOptions, TState, any>,
|
|
42
|
-
{
|
|
43
|
-
initialState = {} as TState,
|
|
44
|
-
options,
|
|
45
|
-
initialCompletedSteps,
|
|
46
|
-
brainRunId,
|
|
47
|
-
endAfter,
|
|
48
|
-
}: {
|
|
49
|
-
initialState?: TState;
|
|
50
|
-
options?: TOptions;
|
|
51
|
-
initialCompletedSteps?: SerializedStep[] | never;
|
|
52
|
-
brainRunId?: string | never;
|
|
53
|
-
endAfter?: number;
|
|
54
|
-
} = {}
|
|
55
|
-
): Promise<TState> {
|
|
56
|
-
const { adapters, client, resources } = this.options;
|
|
57
|
-
|
|
58
|
-
let currentState = initialState ?? ({} as TState);
|
|
59
|
-
let stepNumber = 1;
|
|
60
|
-
|
|
61
|
-
// Apply any patches from completed steps
|
|
62
|
-
// to the initial state so that the brain
|
|
63
|
-
// starts with a state that reflects all of the completed steps.
|
|
64
|
-
// Need to do this when a brain is restarted with completed steps.
|
|
65
|
-
initialCompletedSteps?.forEach((step) => {
|
|
66
|
-
if (step.patch) {
|
|
67
|
-
currentState = applyPatches(currentState, [step.patch]) as TState;
|
|
68
|
-
stepNumber++;
|
|
69
|
-
}
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
const brainRun =
|
|
73
|
-
brainRunId && initialCompletedSteps
|
|
74
|
-
? brain.run({
|
|
75
|
-
initialState,
|
|
76
|
-
initialCompletedSteps,
|
|
77
|
-
brainRunId,
|
|
78
|
-
options,
|
|
79
|
-
client,
|
|
80
|
-
resources: resources ?? {},
|
|
81
|
-
})
|
|
82
|
-
: brain.run({
|
|
83
|
-
initialState,
|
|
84
|
-
options,
|
|
85
|
-
client,
|
|
86
|
-
brainRunId,
|
|
87
|
-
resources: resources ?? {},
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
for await (const event of brainRun) {
|
|
91
|
-
// Dispatch event to all adapters
|
|
92
|
-
await Promise.all(adapters.map((adapter) => adapter.dispatch(event)));
|
|
93
|
-
|
|
94
|
-
// Update current state when steps complete
|
|
95
|
-
if (event.type === BRAIN_EVENTS.STEP_COMPLETE) {
|
|
96
|
-
if (event.patch) {
|
|
97
|
-
currentState = applyPatches(currentState, [event.patch]) as TState;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Check if we should stop after this step
|
|
101
|
-
if (endAfter && stepNumber >= endAfter) {
|
|
102
|
-
return currentState;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
stepNumber++;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
return currentState;
|
|
110
|
-
}
|
|
111
|
-
}
|