@positronic/core 0.0.3 → 0.0.4
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
package/src/dsl/brain.ts
DELETED
|
@@ -1,740 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
import { v4 as uuidv4 } from 'uuid';
|
|
3
|
-
import type { ObjectGenerator } from '../clients/types.js';
|
|
4
|
-
import type { State, JsonPatch } from './types.js';
|
|
5
|
-
import { STATUS, BRAIN_EVENTS } from './constants.js';
|
|
6
|
-
import { createPatch, applyPatches } from './json-patch.js';
|
|
7
|
-
import type { Resources } from '../resources/resources.js';
|
|
8
|
-
|
|
9
|
-
export type SerializedError = {
|
|
10
|
-
name: string;
|
|
11
|
-
message: string;
|
|
12
|
-
stack?: string;
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
// New Event Type System
|
|
16
|
-
// Base event interface with only type and options
|
|
17
|
-
interface BaseEvent<TOptions extends object = object> {
|
|
18
|
-
type: (typeof BRAIN_EVENTS)[keyof typeof BRAIN_EVENTS];
|
|
19
|
-
options: TOptions;
|
|
20
|
-
brainRunId: string;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// 1. Brain Events (all include brain title/description)
|
|
24
|
-
interface BrainBaseEvent<TOptions extends object = object>
|
|
25
|
-
extends BaseEvent<TOptions> {
|
|
26
|
-
brainTitle: string;
|
|
27
|
-
brainDescription?: string;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export interface BrainStartEvent<TOptions extends object = object>
|
|
31
|
-
extends BrainBaseEvent<TOptions> {
|
|
32
|
-
type: typeof BRAIN_EVENTS.START | typeof BRAIN_EVENTS.RESTART;
|
|
33
|
-
initialState: State;
|
|
34
|
-
status: typeof STATUS.RUNNING;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export interface BrainCompleteEvent<TOptions extends object = object>
|
|
38
|
-
extends BrainBaseEvent<TOptions> {
|
|
39
|
-
type: typeof BRAIN_EVENTS.COMPLETE;
|
|
40
|
-
status: typeof STATUS.COMPLETE;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export interface BrainErrorEvent<TOptions extends object = object>
|
|
44
|
-
extends BrainBaseEvent<TOptions> {
|
|
45
|
-
type: typeof BRAIN_EVENTS.ERROR;
|
|
46
|
-
status: typeof STATUS.ERROR;
|
|
47
|
-
error: SerializedError;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// 2. Step Status Event (just steps array and base event properties)
|
|
51
|
-
export interface StepStatusEvent<TOptions extends object = object>
|
|
52
|
-
extends BaseEvent<TOptions> {
|
|
53
|
-
type: typeof BRAIN_EVENTS.STEP_STATUS;
|
|
54
|
-
steps: SerializedStepStatus[];
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// 3. Step Events (include step-specific properties)
|
|
58
|
-
export interface StepStartedEvent<TOptions extends object = object>
|
|
59
|
-
extends BaseEvent<TOptions> {
|
|
60
|
-
type: typeof BRAIN_EVENTS.STEP_START;
|
|
61
|
-
status: typeof STATUS.RUNNING;
|
|
62
|
-
stepTitle: string;
|
|
63
|
-
stepId: string;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export interface StepCompletedEvent<TOptions extends object = object>
|
|
67
|
-
extends BaseEvent<TOptions> {
|
|
68
|
-
type: typeof BRAIN_EVENTS.STEP_COMPLETE;
|
|
69
|
-
status: typeof STATUS.RUNNING;
|
|
70
|
-
stepTitle: string;
|
|
71
|
-
stepId: string;
|
|
72
|
-
patch: JsonPatch;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Union type of all possible events
|
|
76
|
-
export type BrainEvent<TOptions extends object = object> =
|
|
77
|
-
| BrainStartEvent<TOptions>
|
|
78
|
-
| BrainCompleteEvent<TOptions>
|
|
79
|
-
| BrainErrorEvent<TOptions>
|
|
80
|
-
| StepStatusEvent<TOptions>
|
|
81
|
-
| StepStartedEvent<TOptions>
|
|
82
|
-
| StepCompletedEvent<TOptions>;
|
|
83
|
-
|
|
84
|
-
export interface SerializedStep {
|
|
85
|
-
title: string;
|
|
86
|
-
status: (typeof STATUS)[keyof typeof STATUS];
|
|
87
|
-
id: string;
|
|
88
|
-
patch?: JsonPatch;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// New type for Step Status Event, omitting the patch
|
|
92
|
-
export type SerializedStepStatus = Omit<SerializedStep, 'patch'>;
|
|
93
|
-
|
|
94
|
-
// Type for brain structure
|
|
95
|
-
export interface BrainStructure {
|
|
96
|
-
title: string;
|
|
97
|
-
description?: string;
|
|
98
|
-
steps: Array<{
|
|
99
|
-
type: 'step' | 'brain';
|
|
100
|
-
title: string;
|
|
101
|
-
innerBrain?: BrainStructure;
|
|
102
|
-
}>;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Type for the brain function
|
|
106
|
-
export interface BrainFactory {
|
|
107
|
-
<
|
|
108
|
-
TOptions extends object = object,
|
|
109
|
-
TState extends State = object,
|
|
110
|
-
TServices extends object = object
|
|
111
|
-
>(
|
|
112
|
-
brainConfig: string | { title: string; description?: string }
|
|
113
|
-
): Brain<TOptions, TState, TServices>;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
type StepBlock<
|
|
117
|
-
TStateIn,
|
|
118
|
-
TStateOut,
|
|
119
|
-
TOptions extends object = object,
|
|
120
|
-
TServices extends object = object
|
|
121
|
-
> = {
|
|
122
|
-
type: 'step';
|
|
123
|
-
title: string;
|
|
124
|
-
action: (
|
|
125
|
-
params: {
|
|
126
|
-
state: TStateIn;
|
|
127
|
-
options: TOptions;
|
|
128
|
-
client: ObjectGenerator;
|
|
129
|
-
resources: Resources;
|
|
130
|
-
} & TServices
|
|
131
|
-
) => TStateOut | Promise<TStateOut>;
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
type BrainBlock<
|
|
135
|
-
TOuterState,
|
|
136
|
-
TInnerState extends State,
|
|
137
|
-
TNewState,
|
|
138
|
-
TOptions extends object = object,
|
|
139
|
-
TServices extends object = object
|
|
140
|
-
> = {
|
|
141
|
-
type: 'brain';
|
|
142
|
-
title: string;
|
|
143
|
-
innerBrain: Brain<TOptions, TInnerState, TServices>;
|
|
144
|
-
initialState: State | ((outerState: TOuterState) => State);
|
|
145
|
-
action: (
|
|
146
|
-
outerState: TOuterState,
|
|
147
|
-
innerState: TInnerState,
|
|
148
|
-
services: TServices
|
|
149
|
-
) => TNewState;
|
|
150
|
-
};
|
|
151
|
-
|
|
152
|
-
type Block<
|
|
153
|
-
TStateIn,
|
|
154
|
-
TStateOut,
|
|
155
|
-
TOptions extends object = object,
|
|
156
|
-
TServices extends object = object
|
|
157
|
-
> =
|
|
158
|
-
| StepBlock<TStateIn, TStateOut, TOptions, TServices>
|
|
159
|
-
| BrainBlock<TStateIn, any, TStateOut, TOptions, TServices>;
|
|
160
|
-
|
|
161
|
-
interface BaseRunParams<TOptions extends object = object> {
|
|
162
|
-
client: ObjectGenerator;
|
|
163
|
-
resources?: Resources;
|
|
164
|
-
options?: TOptions;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
export interface InitialRunParams<TOptions extends object = object>
|
|
168
|
-
extends BaseRunParams<TOptions> {
|
|
169
|
-
initialState?: State;
|
|
170
|
-
initialCompletedSteps?: never;
|
|
171
|
-
brainRunId?: string;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
export interface RerunParams<TOptions extends object = object>
|
|
175
|
-
extends BaseRunParams<TOptions> {
|
|
176
|
-
initialState: State;
|
|
177
|
-
initialCompletedSteps: SerializedStep[];
|
|
178
|
-
brainRunId: string;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
export class Brain<
|
|
182
|
-
TOptions extends object = object,
|
|
183
|
-
TState extends State = object,
|
|
184
|
-
TServices extends object = object
|
|
185
|
-
> {
|
|
186
|
-
private blocks: Block<any, any, TOptions, TServices>[] = [];
|
|
187
|
-
public type: 'brain' = 'brain';
|
|
188
|
-
private defaultOptions: Partial<TOptions> = {};
|
|
189
|
-
private services: TServices = {} as TServices;
|
|
190
|
-
|
|
191
|
-
constructor(public readonly title: string, private description?: string) {}
|
|
192
|
-
|
|
193
|
-
get structure(): BrainStructure {
|
|
194
|
-
return {
|
|
195
|
-
title: this.title,
|
|
196
|
-
description: this.description,
|
|
197
|
-
steps: this.blocks.map((block) => {
|
|
198
|
-
if (block.type === 'step') {
|
|
199
|
-
return {
|
|
200
|
-
type: 'step' as const,
|
|
201
|
-
title: block.title,
|
|
202
|
-
};
|
|
203
|
-
} else {
|
|
204
|
-
// block.type === 'brain'
|
|
205
|
-
return {
|
|
206
|
-
type: 'brain' as const,
|
|
207
|
-
title: block.title,
|
|
208
|
-
innerBrain: block.innerBrain.structure,
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
}),
|
|
212
|
-
};
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// New method to specify default options
|
|
216
|
-
withOptions(options: Partial<TOptions>): this {
|
|
217
|
-
this.defaultOptions = { ...this.defaultOptions, ...options };
|
|
218
|
-
return this;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
// New method to add services
|
|
222
|
-
withServices<TNewServices extends object>(
|
|
223
|
-
services: TNewServices
|
|
224
|
-
): Brain<TOptions, TState, TNewServices> {
|
|
225
|
-
const nextBrain = new Brain<TOptions, TState, TNewServices>(
|
|
226
|
-
this.title,
|
|
227
|
-
this.description
|
|
228
|
-
).withBlocks(this.blocks as any);
|
|
229
|
-
|
|
230
|
-
// Copy default options
|
|
231
|
-
nextBrain.withOptions(this.defaultOptions);
|
|
232
|
-
|
|
233
|
-
// Set services
|
|
234
|
-
nextBrain.services = services;
|
|
235
|
-
|
|
236
|
-
return nextBrain;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
step<TNewState extends State>(
|
|
240
|
-
title: string,
|
|
241
|
-
action: (
|
|
242
|
-
params: {
|
|
243
|
-
state: TState;
|
|
244
|
-
options: TOptions;
|
|
245
|
-
client: ObjectGenerator;
|
|
246
|
-
resources: Resources;
|
|
247
|
-
} & TServices
|
|
248
|
-
) => TNewState | Promise<TNewState>
|
|
249
|
-
) {
|
|
250
|
-
const stepBlock: StepBlock<TState, TNewState, TOptions, TServices> = {
|
|
251
|
-
type: 'step',
|
|
252
|
-
title,
|
|
253
|
-
action,
|
|
254
|
-
};
|
|
255
|
-
this.blocks.push(stepBlock);
|
|
256
|
-
return this.nextBrain<TNewState>();
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
brain<TInnerState extends State, TNewState extends State>(
|
|
260
|
-
title: string,
|
|
261
|
-
innerBrain: Brain<TOptions, TInnerState, TServices>,
|
|
262
|
-
action: (params: {
|
|
263
|
-
state: TState;
|
|
264
|
-
brainState: TInnerState;
|
|
265
|
-
services: TServices;
|
|
266
|
-
}) => TNewState,
|
|
267
|
-
initialState?: State | ((state: TState) => State)
|
|
268
|
-
) {
|
|
269
|
-
const nestedBlock: BrainBlock<
|
|
270
|
-
TState,
|
|
271
|
-
TInnerState,
|
|
272
|
-
TNewState,
|
|
273
|
-
TOptions,
|
|
274
|
-
TServices
|
|
275
|
-
> = {
|
|
276
|
-
type: 'brain',
|
|
277
|
-
title,
|
|
278
|
-
innerBrain,
|
|
279
|
-
initialState: initialState || (() => ({} as State)),
|
|
280
|
-
action: (outerState, innerState, services) =>
|
|
281
|
-
action({ state: outerState, brainState: innerState, services }),
|
|
282
|
-
};
|
|
283
|
-
this.blocks.push(nestedBlock);
|
|
284
|
-
return this.nextBrain<TNewState>();
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// TResponseKey:
|
|
288
|
-
// The response key must be a string literal, so if defining a response model
|
|
289
|
-
// a consumer of this brain must use "as const" to ensure the key is a string literal
|
|
290
|
-
// this type makes sure that the will get a ts error if they don't.
|
|
291
|
-
prompt<
|
|
292
|
-
TResponseKey extends string & { readonly brand?: unique symbol },
|
|
293
|
-
TSchema extends z.ZodObject<any>,
|
|
294
|
-
TNewState extends State = TState & {
|
|
295
|
-
[K in TResponseKey]: z.infer<TSchema>;
|
|
296
|
-
}
|
|
297
|
-
>(
|
|
298
|
-
title: string,
|
|
299
|
-
config: {
|
|
300
|
-
template: (
|
|
301
|
-
state: TState,
|
|
302
|
-
resources: Resources
|
|
303
|
-
) => string | Promise<string>;
|
|
304
|
-
outputSchema: {
|
|
305
|
-
schema: TSchema;
|
|
306
|
-
name: TResponseKey & (string extends TResponseKey ? never : unknown);
|
|
307
|
-
};
|
|
308
|
-
client?: ObjectGenerator;
|
|
309
|
-
},
|
|
310
|
-
reduce?: (
|
|
311
|
-
params: {
|
|
312
|
-
state: TState;
|
|
313
|
-
response: z.infer<TSchema>;
|
|
314
|
-
options: TOptions;
|
|
315
|
-
prompt: string;
|
|
316
|
-
resources: Resources;
|
|
317
|
-
} & TServices
|
|
318
|
-
) => TNewState | Promise<TNewState>
|
|
319
|
-
) {
|
|
320
|
-
const promptBlock: StepBlock<TState, TNewState, TOptions, TServices> = {
|
|
321
|
-
type: 'step',
|
|
322
|
-
title,
|
|
323
|
-
action: async ({
|
|
324
|
-
state,
|
|
325
|
-
client: runClient,
|
|
326
|
-
options,
|
|
327
|
-
resources,
|
|
328
|
-
...services
|
|
329
|
-
}) => {
|
|
330
|
-
const { template, outputSchema, client: stepClient } = config;
|
|
331
|
-
const { schema, name: schemaName } = outputSchema;
|
|
332
|
-
const client = stepClient ?? runClient;
|
|
333
|
-
const prompt = await template(state, resources);
|
|
334
|
-
const response = await client.generateObject({
|
|
335
|
-
schema,
|
|
336
|
-
schemaName,
|
|
337
|
-
prompt,
|
|
338
|
-
});
|
|
339
|
-
const stateWithResponse = {
|
|
340
|
-
...state,
|
|
341
|
-
[config.outputSchema.name]: response,
|
|
342
|
-
};
|
|
343
|
-
|
|
344
|
-
return reduce
|
|
345
|
-
? reduce({
|
|
346
|
-
state,
|
|
347
|
-
response,
|
|
348
|
-
options,
|
|
349
|
-
prompt,
|
|
350
|
-
resources,
|
|
351
|
-
...(services as TServices),
|
|
352
|
-
})
|
|
353
|
-
: (stateWithResponse as unknown as TNewState);
|
|
354
|
-
},
|
|
355
|
-
};
|
|
356
|
-
this.blocks.push(promptBlock);
|
|
357
|
-
return this.nextBrain<TNewState>();
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
// Overload signatures
|
|
361
|
-
run(params: InitialRunParams<TOptions>): AsyncGenerator<BrainEvent<TOptions>>;
|
|
362
|
-
run(params: RerunParams<TOptions>): AsyncGenerator<BrainEvent<TOptions>>;
|
|
363
|
-
|
|
364
|
-
// Implementation signature
|
|
365
|
-
async *run(
|
|
366
|
-
params: InitialRunParams<TOptions> | RerunParams<TOptions>
|
|
367
|
-
): AsyncGenerator<BrainEvent<TOptions>> {
|
|
368
|
-
const { title, description, blocks } = this;
|
|
369
|
-
|
|
370
|
-
// Merge default options with provided options
|
|
371
|
-
const mergedOptions = {
|
|
372
|
-
...this.defaultOptions,
|
|
373
|
-
...(params.options || {}),
|
|
374
|
-
} as TOptions;
|
|
375
|
-
|
|
376
|
-
const stream = new BrainEventStream({
|
|
377
|
-
title,
|
|
378
|
-
description,
|
|
379
|
-
blocks,
|
|
380
|
-
...params,
|
|
381
|
-
options: mergedOptions,
|
|
382
|
-
services: this.services,
|
|
383
|
-
});
|
|
384
|
-
|
|
385
|
-
yield* stream.next();
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
private withBlocks(blocks: Block<any, any, TOptions, TServices>[]): this {
|
|
389
|
-
this.blocks = blocks;
|
|
390
|
-
return this;
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
private nextBrain<TNewState extends State>(): Brain<
|
|
394
|
-
TOptions,
|
|
395
|
-
TNewState,
|
|
396
|
-
TServices
|
|
397
|
-
> {
|
|
398
|
-
// Pass default options to the next brain
|
|
399
|
-
const nextBrain = new Brain<TOptions, TNewState, TServices>(
|
|
400
|
-
this.title,
|
|
401
|
-
this.description
|
|
402
|
-
).withBlocks(this.blocks as any);
|
|
403
|
-
|
|
404
|
-
// Copy default options to the next brain
|
|
405
|
-
nextBrain.withOptions(this.defaultOptions);
|
|
406
|
-
|
|
407
|
-
// Copy services to the next brain
|
|
408
|
-
nextBrain.services = this.services;
|
|
409
|
-
|
|
410
|
-
return nextBrain;
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
class Step {
|
|
415
|
-
public id: string;
|
|
416
|
-
private patch?: JsonPatch;
|
|
417
|
-
private status: (typeof STATUS)[keyof typeof STATUS] = STATUS.PENDING;
|
|
418
|
-
|
|
419
|
-
constructor(public block: Block<any, any, any, any>, id?: string) {
|
|
420
|
-
this.id = id || uuidv4();
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
withPatch(patch: JsonPatch | undefined) {
|
|
424
|
-
this.patch = patch;
|
|
425
|
-
return this;
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
withStatus(status: (typeof STATUS)[keyof typeof STATUS]) {
|
|
429
|
-
this.status = status;
|
|
430
|
-
return this;
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
get serialized(): SerializedStep {
|
|
434
|
-
return {
|
|
435
|
-
id: this.id,
|
|
436
|
-
title: this.block.title,
|
|
437
|
-
status: this.status,
|
|
438
|
-
patch:
|
|
439
|
-
typeof this.patch === 'string' ? JSON.parse(this.patch) : this.patch,
|
|
440
|
-
};
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
class BrainEventStream<
|
|
445
|
-
TOptions extends object = object,
|
|
446
|
-
TState extends State = object,
|
|
447
|
-
TServices extends object = object
|
|
448
|
-
> {
|
|
449
|
-
private currentState: TState;
|
|
450
|
-
private steps: Step[];
|
|
451
|
-
private currentStepIndex: number = 0;
|
|
452
|
-
private initialState: TState;
|
|
453
|
-
private brainRunId: string;
|
|
454
|
-
private title: string;
|
|
455
|
-
private description?: string;
|
|
456
|
-
private client: ObjectGenerator;
|
|
457
|
-
private options: TOptions;
|
|
458
|
-
private services: TServices;
|
|
459
|
-
private resources: Resources;
|
|
460
|
-
|
|
461
|
-
constructor(
|
|
462
|
-
params: (InitialRunParams<TOptions> | RerunParams<TOptions>) & {
|
|
463
|
-
title: string;
|
|
464
|
-
description?: string;
|
|
465
|
-
blocks: Block<any, any, TOptions, TServices>[];
|
|
466
|
-
services: TServices;
|
|
467
|
-
}
|
|
468
|
-
) {
|
|
469
|
-
const {
|
|
470
|
-
initialState = {} as TState,
|
|
471
|
-
initialCompletedSteps,
|
|
472
|
-
blocks,
|
|
473
|
-
title,
|
|
474
|
-
description,
|
|
475
|
-
brainRunId: providedBrainRunId,
|
|
476
|
-
options = {} as TOptions,
|
|
477
|
-
client,
|
|
478
|
-
services,
|
|
479
|
-
resources = {} as Resources,
|
|
480
|
-
} = params;
|
|
481
|
-
|
|
482
|
-
this.initialState = initialState as TState;
|
|
483
|
-
this.title = title;
|
|
484
|
-
this.description = description;
|
|
485
|
-
this.client = client;
|
|
486
|
-
this.options = options;
|
|
487
|
-
this.services = services;
|
|
488
|
-
this.resources = resources;
|
|
489
|
-
// Initialize steps array with UUIDs and pending status
|
|
490
|
-
this.steps = blocks.map((block, index) => {
|
|
491
|
-
const completedStep = initialCompletedSteps?.[index];
|
|
492
|
-
if (completedStep) {
|
|
493
|
-
return new Step(block, completedStep.id)
|
|
494
|
-
.withStatus(completedStep.status)
|
|
495
|
-
.withPatch(completedStep.patch);
|
|
496
|
-
}
|
|
497
|
-
return new Step(block);
|
|
498
|
-
});
|
|
499
|
-
|
|
500
|
-
this.currentState = clone(this.initialState);
|
|
501
|
-
|
|
502
|
-
for (const step of this.steps) {
|
|
503
|
-
if (step.serialized.status === STATUS.COMPLETE && step.serialized.patch) {
|
|
504
|
-
this.currentState = applyPatches(this.currentState, [
|
|
505
|
-
step.serialized.patch,
|
|
506
|
-
]) as TState;
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
// Use provided ID if available, otherwise generate one
|
|
511
|
-
this.brainRunId = providedBrainRunId ?? uuidv4();
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
async *next(): AsyncGenerator<BrainEvent<TOptions>> {
|
|
515
|
-
const {
|
|
516
|
-
steps,
|
|
517
|
-
title: brainTitle,
|
|
518
|
-
description: brainDescription,
|
|
519
|
-
currentState,
|
|
520
|
-
options,
|
|
521
|
-
brainRunId,
|
|
522
|
-
} = this;
|
|
523
|
-
|
|
524
|
-
try {
|
|
525
|
-
const hasCompletedSteps = steps.some(
|
|
526
|
-
(step) => step.serialized.status !== STATUS.PENDING
|
|
527
|
-
);
|
|
528
|
-
yield {
|
|
529
|
-
type: hasCompletedSteps ? BRAIN_EVENTS.RESTART : BRAIN_EVENTS.START,
|
|
530
|
-
status: STATUS.RUNNING,
|
|
531
|
-
brainTitle,
|
|
532
|
-
brainDescription,
|
|
533
|
-
initialState: currentState,
|
|
534
|
-
options,
|
|
535
|
-
brainRunId,
|
|
536
|
-
};
|
|
537
|
-
|
|
538
|
-
// Emit initial step status after brain starts
|
|
539
|
-
yield {
|
|
540
|
-
type: BRAIN_EVENTS.STEP_STATUS,
|
|
541
|
-
steps: steps.map((step) => {
|
|
542
|
-
const { patch, ...rest } = step.serialized;
|
|
543
|
-
return rest;
|
|
544
|
-
}),
|
|
545
|
-
options,
|
|
546
|
-
brainRunId,
|
|
547
|
-
};
|
|
548
|
-
|
|
549
|
-
// Process each step
|
|
550
|
-
while (this.currentStepIndex < steps.length) {
|
|
551
|
-
const step = steps[this.currentStepIndex];
|
|
552
|
-
|
|
553
|
-
// Skip completed steps
|
|
554
|
-
if (step.serialized.status === STATUS.COMPLETE) {
|
|
555
|
-
this.currentStepIndex++;
|
|
556
|
-
continue;
|
|
557
|
-
}
|
|
558
|
-
// Step start event
|
|
559
|
-
yield {
|
|
560
|
-
type: BRAIN_EVENTS.STEP_START,
|
|
561
|
-
status: STATUS.RUNNING,
|
|
562
|
-
stepTitle: step.block.title,
|
|
563
|
-
stepId: step.id,
|
|
564
|
-
options,
|
|
565
|
-
brainRunId,
|
|
566
|
-
};
|
|
567
|
-
|
|
568
|
-
step.withStatus(STATUS.RUNNING);
|
|
569
|
-
|
|
570
|
-
// Step Status Event to indicate that the step is running
|
|
571
|
-
yield {
|
|
572
|
-
type: BRAIN_EVENTS.STEP_STATUS,
|
|
573
|
-
steps: steps.map((step) => {
|
|
574
|
-
const { patch, ...rest } = step.serialized;
|
|
575
|
-
return rest;
|
|
576
|
-
}),
|
|
577
|
-
options,
|
|
578
|
-
brainRunId,
|
|
579
|
-
};
|
|
580
|
-
|
|
581
|
-
// Execute step and yield the STEP_COMPLETE event and
|
|
582
|
-
// all events from inner brains if any
|
|
583
|
-
yield* this.executeStep(step);
|
|
584
|
-
|
|
585
|
-
// Step Status Event
|
|
586
|
-
yield {
|
|
587
|
-
type: BRAIN_EVENTS.STEP_STATUS,
|
|
588
|
-
steps: steps.map((step) => {
|
|
589
|
-
const { patch, ...rest } = step.serialized;
|
|
590
|
-
return rest;
|
|
591
|
-
}),
|
|
592
|
-
options,
|
|
593
|
-
brainRunId,
|
|
594
|
-
};
|
|
595
|
-
|
|
596
|
-
this.currentStepIndex++;
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
yield {
|
|
600
|
-
type: BRAIN_EVENTS.COMPLETE,
|
|
601
|
-
status: STATUS.COMPLETE,
|
|
602
|
-
brainTitle,
|
|
603
|
-
brainDescription,
|
|
604
|
-
brainRunId,
|
|
605
|
-
options,
|
|
606
|
-
};
|
|
607
|
-
} catch (err: any) {
|
|
608
|
-
const error = err as Error;
|
|
609
|
-
const currentStep = steps[this.currentStepIndex];
|
|
610
|
-
currentStep?.withStatus(STATUS.ERROR);
|
|
611
|
-
|
|
612
|
-
yield {
|
|
613
|
-
type: BRAIN_EVENTS.ERROR,
|
|
614
|
-
status: STATUS.ERROR,
|
|
615
|
-
brainTitle,
|
|
616
|
-
brainDescription,
|
|
617
|
-
brainRunId,
|
|
618
|
-
error: {
|
|
619
|
-
name: error.name,
|
|
620
|
-
message: error.message,
|
|
621
|
-
stack: error.stack,
|
|
622
|
-
},
|
|
623
|
-
options,
|
|
624
|
-
};
|
|
625
|
-
|
|
626
|
-
// Step Status Event
|
|
627
|
-
yield {
|
|
628
|
-
type: BRAIN_EVENTS.STEP_STATUS,
|
|
629
|
-
steps: steps.map((step) => {
|
|
630
|
-
const { patch, ...rest } = step.serialized;
|
|
631
|
-
return rest;
|
|
632
|
-
}),
|
|
633
|
-
options,
|
|
634
|
-
brainRunId,
|
|
635
|
-
};
|
|
636
|
-
|
|
637
|
-
throw error;
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
private async *executeStep(step: Step): AsyncGenerator<BrainEvent<TOptions>> {
|
|
642
|
-
const block = step.block as Block<any, any, TOptions, TServices>;
|
|
643
|
-
|
|
644
|
-
if (block.type === 'brain') {
|
|
645
|
-
const initialState =
|
|
646
|
-
typeof block.initialState === 'function'
|
|
647
|
-
? block.initialState(this.currentState)
|
|
648
|
-
: block.initialState;
|
|
649
|
-
|
|
650
|
-
// Run inner brain and yield all its events
|
|
651
|
-
let patches: JsonPatch[] = [];
|
|
652
|
-
const innerRun = block.innerBrain.run({
|
|
653
|
-
resources: this.resources,
|
|
654
|
-
client: this.client,
|
|
655
|
-
initialState,
|
|
656
|
-
options: this.options ?? ({} as TOptions),
|
|
657
|
-
});
|
|
658
|
-
|
|
659
|
-
for await (const event of innerRun) {
|
|
660
|
-
yield event; // Forward all inner brain events
|
|
661
|
-
if (event.type === BRAIN_EVENTS.STEP_COMPLETE) {
|
|
662
|
-
patches.push(event.patch);
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
// Apply collected patches to get final inner state
|
|
667
|
-
const innerState = applyPatches(initialState, patches);
|
|
668
|
-
|
|
669
|
-
// Get previous state before action
|
|
670
|
-
const prevState = this.currentState;
|
|
671
|
-
|
|
672
|
-
// Update state with inner brain results
|
|
673
|
-
this.currentState = await block.action(
|
|
674
|
-
this.currentState,
|
|
675
|
-
innerState,
|
|
676
|
-
this.services
|
|
677
|
-
);
|
|
678
|
-
yield* this.completeStep(step, prevState);
|
|
679
|
-
} else {
|
|
680
|
-
// Get previous state before action
|
|
681
|
-
const prevState = this.currentState;
|
|
682
|
-
|
|
683
|
-
// Execute regular step
|
|
684
|
-
this.currentState = await block.action({
|
|
685
|
-
state: this.currentState,
|
|
686
|
-
options: this.options ?? ({} as TOptions),
|
|
687
|
-
client: this.client,
|
|
688
|
-
resources: this.resources,
|
|
689
|
-
...this.services,
|
|
690
|
-
});
|
|
691
|
-
yield* this.completeStep(step, prevState);
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
private *completeStep(
|
|
696
|
-
step: Step,
|
|
697
|
-
prevState: TState
|
|
698
|
-
): Generator<BrainEvent<TOptions>> {
|
|
699
|
-
step.withStatus(STATUS.COMPLETE);
|
|
700
|
-
|
|
701
|
-
// Create patch for the state change
|
|
702
|
-
const patch = createPatch(prevState, this.currentState);
|
|
703
|
-
step.withPatch(patch);
|
|
704
|
-
|
|
705
|
-
yield {
|
|
706
|
-
type: BRAIN_EVENTS.STEP_COMPLETE,
|
|
707
|
-
status: STATUS.RUNNING,
|
|
708
|
-
stepTitle: step.block.title,
|
|
709
|
-
stepId: step.id,
|
|
710
|
-
patch,
|
|
711
|
-
options: this.options ?? ({} as TOptions),
|
|
712
|
-
brainRunId: this.brainRunId,
|
|
713
|
-
};
|
|
714
|
-
}
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
const brainNamesAreUnique = process.env.NODE_ENV !== 'test';
|
|
718
|
-
|
|
719
|
-
const brainNames = new Set<string>();
|
|
720
|
-
export const brain: BrainFactory = function <
|
|
721
|
-
TOptions extends object = object,
|
|
722
|
-
TState extends State = object,
|
|
723
|
-
TServices extends object = object
|
|
724
|
-
>(brainConfig: string | { title: string; description?: string }) {
|
|
725
|
-
const title =
|
|
726
|
-
typeof brainConfig === 'string' ? brainConfig : brainConfig.title;
|
|
727
|
-
const description =
|
|
728
|
-
typeof brainConfig === 'string' ? undefined : brainConfig.description;
|
|
729
|
-
if (brainNamesAreUnique && brainNames.has(title)) {
|
|
730
|
-
throw new Error(
|
|
731
|
-
`Brain with name "${title}" already exists. Brain names must be unique.`
|
|
732
|
-
);
|
|
733
|
-
}
|
|
734
|
-
if (brainNamesAreUnique) {
|
|
735
|
-
brainNames.add(title);
|
|
736
|
-
}
|
|
737
|
-
return new Brain<TOptions, TState, TServices>(title, description);
|
|
738
|
-
};
|
|
739
|
-
|
|
740
|
-
const clone = <T>(value: T): T => structuredClone(value);
|