@falai/agent 1.0.2 → 1.1.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/adapters/MemoryAdapter.d.ts.map +1 -1
- package/dist/adapters/MemoryAdapter.js +2 -1
- package/dist/adapters/MemoryAdapter.js.map +1 -1
- package/dist/adapters/MongoAdapter.d.ts.map +1 -1
- package/dist/adapters/MongoAdapter.js +2 -1
- package/dist/adapters/MongoAdapter.js.map +1 -1
- package/dist/adapters/OpenSearchAdapter.d.ts.map +1 -1
- package/dist/adapters/OpenSearchAdapter.js +2 -1
- package/dist/adapters/OpenSearchAdapter.js.map +1 -1
- package/dist/adapters/PostgreSQLAdapter.d.ts.map +1 -1
- package/dist/adapters/PostgreSQLAdapter.js +2 -1
- package/dist/adapters/PostgreSQLAdapter.js.map +1 -1
- package/dist/adapters/PrismaAdapter.d.ts.map +1 -1
- package/dist/adapters/PrismaAdapter.js +2 -1
- package/dist/adapters/PrismaAdapter.js.map +1 -1
- package/dist/adapters/RedisAdapter.d.ts.map +1 -1
- package/dist/adapters/RedisAdapter.js.map +1 -1
- package/dist/adapters/SQLiteAdapter.d.ts.map +1 -1
- package/dist/adapters/SQLiteAdapter.js.map +1 -1
- package/dist/cjs/adapters/MemoryAdapter.d.ts.map +1 -1
- package/dist/cjs/adapters/MemoryAdapter.js +2 -1
- package/dist/cjs/adapters/MemoryAdapter.js.map +1 -1
- package/dist/cjs/adapters/MongoAdapter.d.ts.map +1 -1
- package/dist/cjs/adapters/MongoAdapter.js +2 -1
- package/dist/cjs/adapters/MongoAdapter.js.map +1 -1
- package/dist/cjs/adapters/OpenSearchAdapter.d.ts.map +1 -1
- package/dist/cjs/adapters/OpenSearchAdapter.js +2 -1
- package/dist/cjs/adapters/OpenSearchAdapter.js.map +1 -1
- package/dist/cjs/adapters/PostgreSQLAdapter.d.ts.map +1 -1
- package/dist/cjs/adapters/PostgreSQLAdapter.js +2 -1
- package/dist/cjs/adapters/PostgreSQLAdapter.js.map +1 -1
- package/dist/cjs/adapters/PrismaAdapter.d.ts.map +1 -1
- package/dist/cjs/adapters/PrismaAdapter.js +2 -1
- package/dist/cjs/adapters/PrismaAdapter.js.map +1 -1
- package/dist/cjs/adapters/RedisAdapter.d.ts.map +1 -1
- package/dist/cjs/adapters/RedisAdapter.js.map +1 -1
- package/dist/cjs/adapters/SQLiteAdapter.d.ts.map +1 -1
- package/dist/cjs/adapters/SQLiteAdapter.js.map +1 -1
- package/dist/cjs/core/BatchExecutor.d.ts +6 -0
- package/dist/cjs/core/BatchExecutor.d.ts.map +1 -1
- package/dist/cjs/core/BatchExecutor.js +14 -5
- package/dist/cjs/core/BatchExecutor.js.map +1 -1
- package/dist/cjs/core/ResponseModal.d.ts.map +1 -1
- package/dist/cjs/core/ResponseModal.js +1 -0
- package/dist/cjs/core/ResponseModal.js.map +1 -1
- package/dist/cjs/core/SessionManager.d.ts.map +1 -1
- package/dist/cjs/core/SessionManager.js +4 -11
- package/dist/cjs/core/SessionManager.js.map +1 -1
- package/dist/cjs/index.d.ts +1 -1
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +3 -2
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/types/agent.d.ts +11 -0
- package/dist/cjs/types/agent.d.ts.map +1 -1
- package/dist/cjs/types/route.d.ts +1 -1
- package/dist/cjs/types/route.d.ts.map +1 -1
- package/dist/cjs/utils/session.d.ts +6 -0
- package/dist/cjs/utils/session.d.ts.map +1 -1
- package/dist/cjs/utils/session.js +26 -8
- package/dist/cjs/utils/session.js.map +1 -1
- package/dist/core/BatchExecutor.d.ts +6 -0
- package/dist/core/BatchExecutor.d.ts.map +1 -1
- package/dist/core/BatchExecutor.js +15 -6
- package/dist/core/BatchExecutor.js.map +1 -1
- package/dist/core/ResponseModal.d.ts.map +1 -1
- package/dist/core/ResponseModal.js +1 -0
- package/dist/core/ResponseModal.js.map +1 -1
- package/dist/core/SessionManager.d.ts.map +1 -1
- package/dist/core/SessionManager.js +4 -11
- package/dist/core/SessionManager.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/types/agent.d.ts +11 -0
- package/dist/types/agent.d.ts.map +1 -1
- package/dist/types/route.d.ts +1 -1
- package/dist/types/route.d.ts.map +1 -1
- package/dist/utils/session.d.ts +6 -0
- package/dist/utils/session.d.ts.map +1 -1
- package/dist/utils/session.js +26 -8
- package/dist/utils/session.js.map +1 -1
- package/docs/api/README.md +31 -2
- package/docs/api/overview.md +19 -11
- package/docs/architecture/multi-step-execution.md +37 -3
- package/docs/core/conversation-flows/data-collection.md +8 -2
- package/docs/core/conversation-flows/route-dsl.md +8 -1
- package/docs/core/conversation-flows/step-transitions.md +4 -0
- package/docs/core/conversation-flows/steps.md +3 -1
- package/docs/core/persistence/session-storage.md +12 -0
- package/docs/guides/migration/README.md +2 -0
- package/docs/guides/migration/multi-step-execution.md +29 -9
- package/examples/integrations/database-integration.ts +10 -9
- package/examples/persistence/custom-adapter.ts +12 -15
- package/package.json +1 -1
- package/src/adapters/MemoryAdapter.ts +6 -8
- package/src/adapters/MongoAdapter.ts +6 -8
- package/src/adapters/OpenSearchAdapter.ts +6 -8
- package/src/adapters/PostgreSQLAdapter.ts +6 -8
- package/src/adapters/PrismaAdapter.ts +4 -6
- package/src/adapters/RedisAdapter.ts +4 -7
- package/src/adapters/SQLiteAdapter.ts +6 -9
- package/src/core/BatchExecutor.ts +143 -125
- package/src/core/ResponseModal.ts +1 -0
- package/src/core/SessionManager.ts +4 -13
- package/src/index.ts +11 -11
- package/src/types/agent.ts +11 -0
- package/src/types/route.ts +8 -7
- package/src/utils/session.ts +48 -10
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
* and orchestrating their execution with a single LLM call.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import type {
|
|
9
|
-
BatchResult,
|
|
10
|
-
StoppedReason,
|
|
11
|
-
StepOptions,
|
|
12
|
-
BatchExecutionError,
|
|
13
|
-
BatchExecutionResult,
|
|
8
|
+
import type {
|
|
9
|
+
BatchResult,
|
|
10
|
+
StoppedReason,
|
|
11
|
+
StepOptions,
|
|
12
|
+
BatchExecutionError,
|
|
13
|
+
BatchExecutionResult,
|
|
14
14
|
StepRef,
|
|
15
15
|
BatchExecutionEvent,
|
|
16
16
|
BatchExecutionEventListener,
|
|
@@ -22,7 +22,7 @@ import type { StructuredSchema } from '../types/schema';
|
|
|
22
22
|
import { Step } from './Step';
|
|
23
23
|
import { Route } from './Route';
|
|
24
24
|
import { END_ROUTE_ID } from '../constants';
|
|
25
|
-
import { logger, createTemplateContext, mergeCollected } from '../utils';
|
|
25
|
+
import { logger, createTemplateContext, mergeCollected, createSession } from '../utils';
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
28
|
* Step configuration relevant for needs-input detection
|
|
@@ -108,6 +108,12 @@ export interface DetermineBatchParams<TContext, TData> {
|
|
|
108
108
|
sessionData: Partial<TData>;
|
|
109
109
|
/** Agent context for condition evaluation */
|
|
110
110
|
context: TContext;
|
|
111
|
+
/**
|
|
112
|
+
* Maximum number of steps to include in this batch.
|
|
113
|
+
* When reached, the batch stops with 'max_steps_reached'.
|
|
114
|
+
* Defaults to 1 (single-step execution).
|
|
115
|
+
*/
|
|
116
|
+
maxSteps?: number;
|
|
111
117
|
}
|
|
112
118
|
|
|
113
119
|
/**
|
|
@@ -195,7 +201,7 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
|
|
|
195
201
|
details,
|
|
196
202
|
};
|
|
197
203
|
this.emitEvent(event);
|
|
198
|
-
|
|
204
|
+
|
|
199
205
|
// Also log the event when debug mode is enabled
|
|
200
206
|
logger.debug(`[BatchExecutor] Event: ${type}`, details);
|
|
201
207
|
}
|
|
@@ -222,16 +228,16 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
|
|
|
222
228
|
* **Validates: Requirements 1.1, 1.4, 1.5, 7.1, 7.2, 7.3**
|
|
223
229
|
*/
|
|
224
230
|
async determineBatch(params: DetermineBatchParams<TContext, TData>): Promise<BatchResult<TContext, TData>> {
|
|
225
|
-
const { route, currentStep, sessionData, context } = params;
|
|
231
|
+
const { route, currentStep, sessionData, context, maxSteps = 1 } = params;
|
|
226
232
|
const startTime = Date.now();
|
|
227
|
-
|
|
233
|
+
|
|
228
234
|
const batchSteps: StepOptions<TContext, TData>[] = [];
|
|
229
235
|
let stoppedReason: StoppedReason = 'route_complete';
|
|
230
236
|
let stoppedAtStep: StepOptions<TContext, TData> | undefined;
|
|
231
|
-
|
|
237
|
+
|
|
232
238
|
// Get all steps in the route for traversal
|
|
233
239
|
const allSteps = route.getAllSteps();
|
|
234
|
-
|
|
240
|
+
|
|
235
241
|
// Find starting position
|
|
236
242
|
let startIndex = 0;
|
|
237
243
|
if (currentStep) {
|
|
@@ -240,40 +246,37 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
|
|
|
240
246
|
startIndex = currentIndex;
|
|
241
247
|
}
|
|
242
248
|
}
|
|
243
|
-
|
|
249
|
+
|
|
244
250
|
// Log batch determination start (Requirement 11.1)
|
|
245
251
|
logger.debug(`[BatchExecutor] Starting batch determination from step index ${startIndex}, total steps: ${allSteps.length}`);
|
|
246
|
-
|
|
252
|
+
|
|
247
253
|
// Emit batch_start event (Requirement 11.3)
|
|
248
254
|
this.emitBatchEvent('batch_start', {
|
|
249
255
|
stepId: currentStep?.id,
|
|
250
256
|
reason: `Starting batch determination from ${currentStep?.id || 'initial step'}`,
|
|
251
257
|
batchSize: 0,
|
|
252
258
|
});
|
|
253
|
-
|
|
259
|
+
|
|
254
260
|
// Create template context for condition evaluation
|
|
255
261
|
const templateContext = createTemplateContext<TContext, TData>({
|
|
256
262
|
context,
|
|
257
263
|
data: sessionData,
|
|
258
|
-
session: {
|
|
259
|
-
id: `batch-${Date.now()}`,
|
|
260
|
-
data: sessionData
|
|
261
|
-
} as SessionState<TData>,
|
|
264
|
+
session: createSession<TData>({ data: sessionData }),
|
|
262
265
|
});
|
|
263
|
-
|
|
266
|
+
|
|
264
267
|
// Walk through steps starting from current position
|
|
265
268
|
for (let i = startIndex; i < allSteps.length; i++) {
|
|
266
269
|
const step = allSteps[i];
|
|
267
270
|
const stepOptions = step.toOptions();
|
|
268
|
-
|
|
271
|
+
|
|
269
272
|
// Check for END_ROUTE (Requirement 2.2)
|
|
270
273
|
if (step.id === END_ROUTE_ID) {
|
|
271
274
|
stoppedReason = 'end_route';
|
|
272
275
|
stoppedAtStep = stepOptions;
|
|
273
|
-
|
|
276
|
+
|
|
274
277
|
// Log stopping reason (Requirement 11.2)
|
|
275
278
|
logger.debug(`[BatchExecutor] Reached END_ROUTE, stopping batch`);
|
|
276
|
-
|
|
279
|
+
|
|
277
280
|
// Emit batch_stop event (Requirement 11.3)
|
|
278
281
|
this.emitBatchEvent('batch_stop', {
|
|
279
282
|
stepId: step.id,
|
|
@@ -283,14 +286,14 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
|
|
|
283
286
|
});
|
|
284
287
|
break;
|
|
285
288
|
}
|
|
286
|
-
|
|
289
|
+
|
|
287
290
|
// Evaluate skipIf condition (Requirements 7.1, 7.2, 7.3)
|
|
288
291
|
let shouldSkip = false;
|
|
289
292
|
if (step.skipIf) {
|
|
290
293
|
try {
|
|
291
294
|
const skipResult = await step.evaluateSkipIf(templateContext);
|
|
292
295
|
shouldSkip = skipResult.shouldSkip;
|
|
293
|
-
|
|
296
|
+
|
|
294
297
|
// Log skipIf evaluation (Requirement 11.1)
|
|
295
298
|
logger.debug(`[BatchExecutor] Step ${step.id} skipIf evaluated to: ${shouldSkip}`);
|
|
296
299
|
} catch (error) {
|
|
@@ -299,12 +302,12 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
|
|
|
299
302
|
shouldSkip = false;
|
|
300
303
|
}
|
|
301
304
|
}
|
|
302
|
-
|
|
305
|
+
|
|
303
306
|
// If skipIf is true, skip this step and continue (Requirement 7.2)
|
|
304
307
|
if (shouldSkip) {
|
|
305
308
|
// Log step skip (Requirement 11.1)
|
|
306
309
|
logger.debug(`[BatchExecutor] Skipping step ${step.id} due to skipIf condition`);
|
|
307
|
-
|
|
310
|
+
|
|
308
311
|
// Emit step_skipped event (Requirement 11.3)
|
|
309
312
|
this.emitBatchEvent('step_skipped', {
|
|
310
313
|
stepId: step.id,
|
|
@@ -312,15 +315,15 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
|
|
|
312
315
|
});
|
|
313
316
|
continue;
|
|
314
317
|
}
|
|
315
|
-
|
|
318
|
+
|
|
316
319
|
// Evaluate needsInput (Requirements 1.2, 1.3)
|
|
317
320
|
const stepNeedsInput = needsInput(step, sessionData);
|
|
318
|
-
|
|
321
|
+
|
|
319
322
|
if (stepNeedsInput) {
|
|
320
323
|
// Requirement 1.5: Stop when a step needs input
|
|
321
324
|
stoppedReason = 'needs_input';
|
|
322
325
|
stoppedAtStep = stepOptions;
|
|
323
|
-
|
|
326
|
+
|
|
324
327
|
// Log stopping reason with details (Requirement 11.1, 11.2)
|
|
325
328
|
const missingRequires = step.requires?.filter(
|
|
326
329
|
field => (sessionData as Record<string, unknown>)[String(field)] === undefined
|
|
@@ -337,7 +340,7 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
|
|
|
337
340
|
}
|
|
338
341
|
|
|
339
342
|
logger.debug(`[BatchExecutor] Step ${step.id} needs input, stopping batch. Missing requires: [${missingRequires.join(', ')}], Collect fields: [${collectFields.join(', ')}]`);
|
|
340
|
-
|
|
343
|
+
|
|
341
344
|
// Emit batch_stop event (Requirement 11.3)
|
|
342
345
|
this.emitBatchEvent('batch_stop', {
|
|
343
346
|
stepId: step.id,
|
|
@@ -347,29 +350,44 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
|
|
|
347
350
|
});
|
|
348
351
|
break;
|
|
349
352
|
}
|
|
350
|
-
|
|
353
|
+
|
|
351
354
|
// Requirement 1.4: Step doesn't need input, include in batch
|
|
352
355
|
batchSteps.push(stepOptions);
|
|
353
|
-
|
|
356
|
+
|
|
354
357
|
// Log step inclusion with reason (Requirement 11.1)
|
|
355
358
|
logger.debug(`[BatchExecutor] Including step ${step.id} in batch (all requirements satisfied)`);
|
|
356
|
-
|
|
359
|
+
|
|
357
360
|
// Emit step_included event (Requirement 11.3)
|
|
358
361
|
this.emitBatchEvent('step_included', {
|
|
359
362
|
stepId: step.id,
|
|
360
363
|
reason: 'All requirements satisfied, no input needed',
|
|
361
364
|
batchSize: batchSteps.length,
|
|
362
365
|
});
|
|
363
|
-
|
|
366
|
+
|
|
367
|
+
// Check if we've reached the max steps limit
|
|
368
|
+
if (batchSteps.length >= maxSteps) {
|
|
369
|
+
stoppedReason = 'max_steps_reached';
|
|
370
|
+
|
|
371
|
+
logger.debug(`[BatchExecutor] Reached maxStepsPerBatch limit (${maxSteps}), stopping batch`);
|
|
372
|
+
|
|
373
|
+
this.emitBatchEvent('batch_stop', {
|
|
374
|
+
stepId: step.id,
|
|
375
|
+
reason: `Reached maxStepsPerBatch limit (${maxSteps})`,
|
|
376
|
+
stoppedReason: 'max_steps_reached',
|
|
377
|
+
batchSize: batchSteps.length,
|
|
378
|
+
});
|
|
379
|
+
break;
|
|
380
|
+
}
|
|
381
|
+
|
|
364
382
|
// Move to next step in the sequence
|
|
365
383
|
const transitions = step.getTransitions();
|
|
366
384
|
if (transitions.length === 0) {
|
|
367
385
|
// No more transitions, route is complete
|
|
368
386
|
stoppedReason = 'route_complete';
|
|
369
|
-
|
|
387
|
+
|
|
370
388
|
// Log stopping reason (Requirement 11.2)
|
|
371
389
|
logger.debug(`[BatchExecutor] No more transitions from step ${step.id}, route complete`);
|
|
372
|
-
|
|
390
|
+
|
|
373
391
|
// Emit batch_stop event (Requirement 11.3)
|
|
374
392
|
this.emitBatchEvent('batch_stop', {
|
|
375
393
|
stepId: step.id,
|
|
@@ -379,7 +397,7 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
|
|
|
379
397
|
});
|
|
380
398
|
break;
|
|
381
399
|
}
|
|
382
|
-
|
|
400
|
+
|
|
383
401
|
// For linear routes, follow the first transition
|
|
384
402
|
// For branching routes, we'd need more complex logic
|
|
385
403
|
const nextStep = transitions[0];
|
|
@@ -393,10 +411,10 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
|
|
|
393
411
|
// Next step is END_ROUTE
|
|
394
412
|
stoppedReason = 'end_route';
|
|
395
413
|
stoppedAtStep = nextStep.toOptions();
|
|
396
|
-
|
|
414
|
+
|
|
397
415
|
// Log stopping reason (Requirement 11.2)
|
|
398
416
|
logger.debug(`[BatchExecutor] Next step is END_ROUTE, stopping batch`);
|
|
399
|
-
|
|
417
|
+
|
|
400
418
|
// Emit batch_stop event (Requirement 11.3)
|
|
401
419
|
this.emitBatchEvent('batch_stop', {
|
|
402
420
|
stepId: nextStep.id,
|
|
@@ -408,11 +426,11 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
|
|
|
408
426
|
}
|
|
409
427
|
}
|
|
410
428
|
}
|
|
411
|
-
|
|
429
|
+
|
|
412
430
|
// Log batch determination complete with timing (Requirement 11.1, 11.2)
|
|
413
431
|
const determinationTime = Date.now() - startTime;
|
|
414
432
|
logger.debug(`[BatchExecutor] Batch determination complete. Steps: ${batchSteps.length}, Stopped reason: ${stoppedReason}, Time: ${determinationTime}ms`);
|
|
415
|
-
|
|
433
|
+
|
|
416
434
|
return {
|
|
417
435
|
steps: batchSteps,
|
|
418
436
|
stoppedReason,
|
|
@@ -434,13 +452,13 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
|
|
|
434
452
|
async executePrepareHooks(params: ExecuteHooksParams<TContext, TData>): Promise<HookExecutionResult> {
|
|
435
453
|
const { steps, context, data, executeHook } = params;
|
|
436
454
|
const executedSteps: string[] = [];
|
|
437
|
-
|
|
455
|
+
|
|
438
456
|
logger.debug(`[BatchExecutor] Executing prepare hooks for ${steps.length} steps`);
|
|
439
|
-
|
|
457
|
+
|
|
440
458
|
for (const step of steps) {
|
|
441
459
|
if (step.prepare) {
|
|
442
460
|
logger.debug(`[BatchExecutor] Executing prepare hook for step: ${step.id}`);
|
|
443
|
-
|
|
461
|
+
|
|
444
462
|
try {
|
|
445
463
|
await executeHook(step.prepare, context, data, step);
|
|
446
464
|
executedSteps.push(step.id || 'unknown');
|
|
@@ -449,7 +467,7 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
|
|
|
449
467
|
// Requirement 5.4: Stop on prepare hook failure
|
|
450
468
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
451
469
|
logger.error(`[BatchExecutor] Prepare hook failed for step ${step.id}: ${errorMessage}`);
|
|
452
|
-
|
|
470
|
+
|
|
453
471
|
return {
|
|
454
472
|
success: false,
|
|
455
473
|
executedSteps,
|
|
@@ -463,7 +481,7 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
|
|
|
463
481
|
}
|
|
464
482
|
}
|
|
465
483
|
}
|
|
466
|
-
|
|
484
|
+
|
|
467
485
|
logger.debug(`[BatchExecutor] All prepare hooks completed successfully`);
|
|
468
486
|
return {
|
|
469
487
|
success: true,
|
|
@@ -487,13 +505,13 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
|
|
|
487
505
|
const { steps, context, data, executeHook } = params;
|
|
488
506
|
const executedSteps: string[] = [];
|
|
489
507
|
const errors: Array<{ stepId: string; error: BatchExecutionError }> = [];
|
|
490
|
-
|
|
508
|
+
|
|
491
509
|
logger.debug(`[BatchExecutor] Executing finalize hooks for ${steps.length} steps`);
|
|
492
|
-
|
|
510
|
+
|
|
493
511
|
for (const step of steps) {
|
|
494
512
|
if (step.finalize) {
|
|
495
513
|
logger.debug(`[BatchExecutor] Executing finalize hook for step: ${step.id}`);
|
|
496
|
-
|
|
514
|
+
|
|
497
515
|
try {
|
|
498
516
|
await executeHook(step.finalize, context, data, step);
|
|
499
517
|
executedSteps.push(step.id || 'unknown');
|
|
@@ -502,7 +520,7 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
|
|
|
502
520
|
// Requirement 5.5: Log error and continue with remaining hooks
|
|
503
521
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
504
522
|
logger.error(`[BatchExecutor] Finalize hook failed for step ${step.id}: ${errorMessage}`);
|
|
505
|
-
|
|
523
|
+
|
|
506
524
|
errors.push({
|
|
507
525
|
stepId: step.id || 'unknown',
|
|
508
526
|
error: {
|
|
@@ -512,18 +530,18 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
|
|
|
512
530
|
details: error,
|
|
513
531
|
},
|
|
514
532
|
});
|
|
515
|
-
|
|
533
|
+
|
|
516
534
|
// Continue to next step despite error
|
|
517
535
|
}
|
|
518
536
|
}
|
|
519
537
|
}
|
|
520
|
-
|
|
538
|
+
|
|
521
539
|
if (errors.length > 0) {
|
|
522
540
|
logger.warn(`[BatchExecutor] ${errors.length} finalize hook(s) failed, but execution continued`);
|
|
523
541
|
} else {
|
|
524
542
|
logger.debug(`[BatchExecutor] All finalize hooks completed successfully`);
|
|
525
543
|
}
|
|
526
|
-
|
|
544
|
+
|
|
527
545
|
// Always return success for finalize hooks (errors are logged but don't stop execution)
|
|
528
546
|
return {
|
|
529
547
|
success: true,
|
|
@@ -571,36 +589,36 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
|
|
|
571
589
|
* **Validates: Requirements 9.1, 9.2, 9.3, 2.4**
|
|
572
590
|
*/
|
|
573
591
|
async executeBatch(params: ExecuteBatchParams<TContext, TData>): Promise<BatchExecutionResult<TData>> {
|
|
574
|
-
const {
|
|
575
|
-
batch,
|
|
576
|
-
session: initialSession,
|
|
577
|
-
context,
|
|
578
|
-
executeHook,
|
|
579
|
-
generateMessage,
|
|
592
|
+
const {
|
|
593
|
+
batch,
|
|
594
|
+
session: initialSession,
|
|
595
|
+
context,
|
|
596
|
+
executeHook,
|
|
597
|
+
generateMessage,
|
|
580
598
|
schema,
|
|
581
599
|
routeId,
|
|
582
600
|
} = params;
|
|
583
|
-
|
|
601
|
+
|
|
584
602
|
// Track timing for each phase (Requirement 11.1)
|
|
585
603
|
const timing: BatchExecutionTiming = {
|
|
586
604
|
totalMs: 0,
|
|
587
605
|
};
|
|
588
606
|
const batchStartTime = Date.now();
|
|
589
|
-
|
|
607
|
+
|
|
590
608
|
// Track the last successful session state for error recovery
|
|
591
609
|
let lastSuccessfulSession = initialSession;
|
|
592
610
|
let currentSession = initialSession;
|
|
593
|
-
|
|
611
|
+
|
|
594
612
|
// Track executed steps for the response
|
|
595
613
|
const executedSteps: StepRef[] = [];
|
|
596
|
-
|
|
614
|
+
|
|
597
615
|
// Log batch execution start with details (Requirement 11.1)
|
|
598
616
|
logger.debug(`[BatchExecutor] Starting batch execution with ${batch.steps.length} steps, route: ${routeId || 'unknown'}`);
|
|
599
|
-
|
|
617
|
+
|
|
600
618
|
// If batch is empty, return early with appropriate reason
|
|
601
619
|
if (batch.steps.length === 0) {
|
|
602
620
|
logger.debug(`[BatchExecutor] Empty batch, returning with stopped reason: ${batch.stoppedReason}`);
|
|
603
|
-
|
|
621
|
+
|
|
604
622
|
// Emit batch_complete event for empty batch (Requirement 11.3)
|
|
605
623
|
timing.totalMs = Date.now() - batchStartTime;
|
|
606
624
|
this.emitBatchEvent('batch_complete', {
|
|
@@ -609,7 +627,7 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
|
|
|
609
627
|
reason: 'Empty batch',
|
|
610
628
|
timing,
|
|
611
629
|
});
|
|
612
|
-
|
|
630
|
+
|
|
613
631
|
return {
|
|
614
632
|
message: '',
|
|
615
633
|
session: currentSession,
|
|
@@ -618,26 +636,26 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
|
|
|
618
636
|
collectedData: {},
|
|
619
637
|
};
|
|
620
638
|
}
|
|
621
|
-
|
|
639
|
+
|
|
622
640
|
// PHASE 1: Execute prepare hooks (Requirement 5.4 - stop on failure)
|
|
623
641
|
const prepareStartTime = Date.now();
|
|
624
642
|
logger.debug(`[BatchExecutor] Phase 1: Executing prepare hooks`);
|
|
625
|
-
|
|
643
|
+
|
|
626
644
|
const prepareResult = await this.executePrepareHooks({
|
|
627
645
|
steps: batch.steps,
|
|
628
646
|
context,
|
|
629
647
|
data: currentSession.data,
|
|
630
648
|
executeHook,
|
|
631
649
|
});
|
|
632
|
-
|
|
650
|
+
|
|
633
651
|
timing.prepareHooksMs = Date.now() - prepareStartTime;
|
|
634
652
|
logger.debug(`[BatchExecutor] Prepare hooks completed in ${timing.prepareHooksMs}ms`);
|
|
635
|
-
|
|
653
|
+
|
|
636
654
|
if (!prepareResult.success) {
|
|
637
655
|
// Requirement 9.3: Preserve partial progress on errors
|
|
638
656
|
// Requirement 2.4: Stop and include error information
|
|
639
657
|
logger.error(`[BatchExecutor] Prepare hook failed:`, prepareResult.error);
|
|
640
|
-
|
|
658
|
+
|
|
641
659
|
// Emit batch_complete event with error (Requirement 11.3)
|
|
642
660
|
timing.totalMs = Date.now() - batchStartTime;
|
|
643
661
|
this.emitBatchEvent('batch_complete', {
|
|
@@ -646,7 +664,7 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
|
|
|
646
664
|
reason: `Prepare hook failed: ${prepareResult.error?.message}`,
|
|
647
665
|
timing,
|
|
648
666
|
});
|
|
649
|
-
|
|
667
|
+
|
|
650
668
|
return {
|
|
651
669
|
message: '',
|
|
652
670
|
session: lastSuccessfulSession, // Return last successful state
|
|
@@ -658,22 +676,22 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
|
|
|
658
676
|
error: prepareResult.error,
|
|
659
677
|
};
|
|
660
678
|
}
|
|
661
|
-
|
|
679
|
+
|
|
662
680
|
// Update last successful session after prepare hooks complete
|
|
663
681
|
lastSuccessfulSession = currentSession;
|
|
664
|
-
|
|
682
|
+
|
|
665
683
|
// PHASE 2: Make LLM call (Requirement 9.1 - preserve session state on failure)
|
|
666
684
|
const llmStartTime = Date.now();
|
|
667
685
|
logger.debug(`[BatchExecutor] Phase 2: Making LLM call`);
|
|
668
|
-
|
|
686
|
+
|
|
669
687
|
let llmResponse: Record<string, unknown>;
|
|
670
688
|
let message: string;
|
|
671
|
-
|
|
689
|
+
|
|
672
690
|
try {
|
|
673
691
|
const result = await generateMessage();
|
|
674
692
|
llmResponse = result.structured || {};
|
|
675
693
|
message = result.message || '';
|
|
676
|
-
|
|
694
|
+
|
|
677
695
|
timing.llmCallMs = Date.now() - llmStartTime;
|
|
678
696
|
logger.debug(`[BatchExecutor] LLM call successful in ${timing.llmCallMs}ms`);
|
|
679
697
|
} catch (error) {
|
|
@@ -681,7 +699,7 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
|
|
|
681
699
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
682
700
|
timing.llmCallMs = Date.now() - llmStartTime;
|
|
683
701
|
logger.error(`[BatchExecutor] LLM call failed after ${timing.llmCallMs}ms:`, errorMessage);
|
|
684
|
-
|
|
702
|
+
|
|
685
703
|
// Emit batch_complete event with error (Requirement 11.3)
|
|
686
704
|
timing.totalMs = Date.now() - batchStartTime;
|
|
687
705
|
this.emitBatchEvent('batch_complete', {
|
|
@@ -690,7 +708,7 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
|
|
|
690
708
|
reason: `LLM call failed: ${errorMessage}`,
|
|
691
709
|
timing,
|
|
692
710
|
});
|
|
693
|
-
|
|
711
|
+
|
|
694
712
|
return {
|
|
695
713
|
message: '',
|
|
696
714
|
session: lastSuccessfulSession, // Preserve session state
|
|
@@ -703,50 +721,50 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
|
|
|
703
721
|
},
|
|
704
722
|
};
|
|
705
723
|
}
|
|
706
|
-
|
|
724
|
+
|
|
707
725
|
// Update last successful session after LLM call
|
|
708
726
|
lastSuccessfulSession = currentSession;
|
|
709
|
-
|
|
727
|
+
|
|
710
728
|
// PHASE 3: Collect and validate data (Requirement 9.2 - include validation errors)
|
|
711
729
|
const collectStartTime = Date.now();
|
|
712
730
|
logger.debug(`[BatchExecutor] Phase 3: Collecting and validating data`);
|
|
713
|
-
|
|
731
|
+
|
|
714
732
|
const collectResult = this.collectBatchData({
|
|
715
733
|
steps: batch.steps,
|
|
716
734
|
llmResponse,
|
|
717
735
|
session: currentSession,
|
|
718
736
|
schema,
|
|
719
737
|
});
|
|
720
|
-
|
|
738
|
+
|
|
721
739
|
timing.dataCollectionMs = Date.now() - collectStartTime;
|
|
722
740
|
logger.debug(`[BatchExecutor] Data collection completed in ${timing.dataCollectionMs}ms`);
|
|
723
|
-
|
|
741
|
+
|
|
724
742
|
// Update session with collected data (even if validation failed)
|
|
725
743
|
currentSession = collectResult.session;
|
|
726
|
-
|
|
744
|
+
|
|
727
745
|
// Track collected data for response
|
|
728
746
|
const collectedData = collectResult.collectedData;
|
|
729
|
-
|
|
747
|
+
|
|
730
748
|
// Check for validation errors
|
|
731
749
|
let validationError: BatchExecutionError | undefined;
|
|
732
750
|
if (!collectResult.success && collectResult.validationErrors && collectResult.validationErrors.length > 0) {
|
|
733
751
|
// Requirement 9.2: Include validation errors in response
|
|
734
752
|
logger.warn(`[BatchExecutor] Data validation failed:`, collectResult.validationErrors);
|
|
735
|
-
|
|
753
|
+
|
|
736
754
|
validationError = {
|
|
737
755
|
type: 'data_validation',
|
|
738
756
|
message: `Validation failed for ${collectResult.validationErrors.length} field(s): ${collectResult.validationErrors.map(e => e.field).join(', ')}`,
|
|
739
757
|
details: collectResult.validationErrors,
|
|
740
758
|
};
|
|
741
|
-
|
|
759
|
+
|
|
742
760
|
// Note: We continue execution despite validation errors
|
|
743
761
|
// The error is included in the response for the caller to handle
|
|
744
762
|
}
|
|
745
|
-
|
|
763
|
+
|
|
746
764
|
// Update last successful session after data collection
|
|
747
765
|
// (even with validation errors, we preserve the collected data)
|
|
748
766
|
lastSuccessfulSession = currentSession;
|
|
749
|
-
|
|
767
|
+
|
|
750
768
|
// Build executed steps list
|
|
751
769
|
for (const step of batch.steps) {
|
|
752
770
|
if (step.id) {
|
|
@@ -756,26 +774,26 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
|
|
|
756
774
|
});
|
|
757
775
|
}
|
|
758
776
|
}
|
|
759
|
-
|
|
777
|
+
|
|
760
778
|
// PHASE 4: Execute finalize hooks (Requirement 5.5 - continue on failure)
|
|
761
779
|
const finalizeStartTime = Date.now();
|
|
762
780
|
logger.debug(`[BatchExecutor] Phase 4: Executing finalize hooks`);
|
|
763
|
-
|
|
781
|
+
|
|
764
782
|
const finalizeResult = await this.executeFinalizeHooks({
|
|
765
783
|
steps: batch.steps,
|
|
766
784
|
context,
|
|
767
785
|
data: currentSession.data,
|
|
768
786
|
executeHook,
|
|
769
787
|
});
|
|
770
|
-
|
|
788
|
+
|
|
771
789
|
timing.finalizeHooksMs = Date.now() - finalizeStartTime;
|
|
772
790
|
logger.debug(`[BatchExecutor] Finalize hooks completed in ${timing.finalizeHooksMs}ms`);
|
|
773
|
-
|
|
791
|
+
|
|
774
792
|
// Log finalize errors but don't fail the batch
|
|
775
793
|
let finalizeError: BatchExecutionError | undefined;
|
|
776
794
|
if (finalizeResult.errors && finalizeResult.errors.length > 0) {
|
|
777
795
|
logger.warn(`[BatchExecutor] Some finalize hooks failed:`, finalizeResult.errors);
|
|
778
|
-
|
|
796
|
+
|
|
779
797
|
// Create a summary error for finalize failures
|
|
780
798
|
finalizeError = {
|
|
781
799
|
type: 'finalize_hook',
|
|
@@ -783,12 +801,12 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
|
|
|
783
801
|
details: finalizeResult.errors,
|
|
784
802
|
};
|
|
785
803
|
}
|
|
786
|
-
|
|
804
|
+
|
|
787
805
|
// Determine the final stopped reason
|
|
788
806
|
// Priority: validation_error > finalize_error > batch.stoppedReason
|
|
789
807
|
let finalStoppedReason: StoppedReason = batch.stoppedReason;
|
|
790
808
|
let finalError: BatchExecutionError | undefined;
|
|
791
|
-
|
|
809
|
+
|
|
792
810
|
if (validationError) {
|
|
793
811
|
finalStoppedReason = 'validation_error';
|
|
794
812
|
finalError = validationError;
|
|
@@ -797,11 +815,11 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
|
|
|
797
815
|
// but include the error in the response
|
|
798
816
|
finalError = finalizeError;
|
|
799
817
|
}
|
|
800
|
-
|
|
818
|
+
|
|
801
819
|
// Calculate total time and log completion (Requirement 11.1, 11.2)
|
|
802
820
|
timing.totalMs = Date.now() - batchStartTime;
|
|
803
821
|
logger.debug(`[BatchExecutor] Batch execution complete. Stopped reason: ${finalStoppedReason}, Executed steps: ${executedSteps.length}, Total time: ${timing.totalMs}ms`);
|
|
804
|
-
|
|
822
|
+
|
|
805
823
|
// Emit batch_complete event (Requirement 11.3)
|
|
806
824
|
this.emitBatchEvent('batch_complete', {
|
|
807
825
|
batchSize: executedSteps.length,
|
|
@@ -809,7 +827,7 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
|
|
|
809
827
|
reason: `Batch completed with ${executedSteps.length} steps`,
|
|
810
828
|
timing,
|
|
811
829
|
});
|
|
812
|
-
|
|
830
|
+
|
|
813
831
|
return {
|
|
814
832
|
message,
|
|
815
833
|
session: currentSession,
|
|
@@ -836,9 +854,9 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
|
|
|
836
854
|
*/
|
|
837
855
|
collectBatchData(params: CollectBatchDataParams<TData>): CollectBatchDataResult<TData> {
|
|
838
856
|
const { steps, llmResponse, session, schema } = params;
|
|
839
|
-
|
|
857
|
+
|
|
840
858
|
logger.debug(`[BatchExecutor] Collecting batch data from ${steps.length} steps`);
|
|
841
|
-
|
|
859
|
+
|
|
842
860
|
// Requirement 6.1: Gather all collect fields from all steps in the batch
|
|
843
861
|
const allCollectFields = new Set<string>();
|
|
844
862
|
for (const step of steps) {
|
|
@@ -848,9 +866,9 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
|
|
|
848
866
|
}
|
|
849
867
|
}
|
|
850
868
|
}
|
|
851
|
-
|
|
869
|
+
|
|
852
870
|
logger.debug(`[BatchExecutor] Collect fields to extract: ${Array.from(allCollectFields).join(', ')}`);
|
|
853
|
-
|
|
871
|
+
|
|
854
872
|
// If no fields to collect, return early with unchanged session
|
|
855
873
|
if (allCollectFields.size === 0) {
|
|
856
874
|
logger.debug(`[BatchExecutor] No collect fields defined, skipping data collection`);
|
|
@@ -861,12 +879,12 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
|
|
|
861
879
|
fieldsCollected: [],
|
|
862
880
|
};
|
|
863
881
|
}
|
|
864
|
-
|
|
882
|
+
|
|
865
883
|
// Extract data from LLM response for all collect fields
|
|
866
884
|
const collectedData: Partial<TData> = {};
|
|
867
885
|
const fieldsCollected: string[] = [];
|
|
868
886
|
const fieldsMissing: string[] = [];
|
|
869
|
-
|
|
887
|
+
|
|
870
888
|
for (const field of allCollectFields) {
|
|
871
889
|
// Check if the field exists in the LLM response
|
|
872
890
|
if (field in llmResponse && llmResponse[field] !== undefined) {
|
|
@@ -878,20 +896,20 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
|
|
|
878
896
|
logger.debug(`[BatchExecutor] Field '${field}' not found in LLM response`);
|
|
879
897
|
}
|
|
880
898
|
}
|
|
881
|
-
|
|
899
|
+
|
|
882
900
|
// Requirement 6.2: Validate collected data against the agent schema
|
|
883
901
|
const validationErrors: ValidationError[] = [];
|
|
884
|
-
|
|
902
|
+
|
|
885
903
|
if (schema && Object.keys(collectedData).length > 0) {
|
|
886
904
|
logger.debug(`[BatchExecutor] Validating collected data against schema`);
|
|
887
|
-
|
|
905
|
+
|
|
888
906
|
const validationResult = this.validateAgainstSchema(collectedData, schema);
|
|
889
907
|
if (!validationResult.valid) {
|
|
890
908
|
validationErrors.push(...validationResult.errors);
|
|
891
909
|
logger.warn(`[BatchExecutor] Schema validation found ${validationErrors.length} error(s)`);
|
|
892
910
|
}
|
|
893
911
|
}
|
|
894
|
-
|
|
912
|
+
|
|
895
913
|
// Requirement 6.3: Update session data with all collected values
|
|
896
914
|
// Only merge valid data (data that passed validation or had no schema to validate against)
|
|
897
915
|
let updatedSession = session;
|
|
@@ -901,11 +919,11 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
|
|
|
901
919
|
updatedSession = mergeCollected(session, collectedData);
|
|
902
920
|
logger.debug(`[BatchExecutor] Updated session with collected data`);
|
|
903
921
|
}
|
|
904
|
-
|
|
922
|
+
|
|
905
923
|
const success = validationErrors.length === 0;
|
|
906
|
-
|
|
924
|
+
|
|
907
925
|
logger.debug(`[BatchExecutor] Data collection complete. Success: ${success}, Fields collected: ${fieldsCollected.length}, Fields missing: ${fieldsMissing.length}`);
|
|
908
|
-
|
|
926
|
+
|
|
909
927
|
return {
|
|
910
928
|
success,
|
|
911
929
|
collectedData,
|
|
@@ -935,7 +953,7 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
|
|
|
935
953
|
schema: StructuredSchema
|
|
936
954
|
): { valid: boolean; errors: ValidationError[] } {
|
|
937
955
|
const errors: ValidationError[] = [];
|
|
938
|
-
|
|
956
|
+
|
|
939
957
|
// Check if provided fields exist in schema
|
|
940
958
|
if (schema.properties) {
|
|
941
959
|
for (const [key, value] of Object.entries(data)) {
|
|
@@ -956,7 +974,7 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
|
|
|
956
974
|
}
|
|
957
975
|
}
|
|
958
976
|
}
|
|
959
|
-
|
|
977
|
+
|
|
960
978
|
return {
|
|
961
979
|
valid: errors.length === 0,
|
|
962
980
|
errors,
|
|
@@ -976,18 +994,18 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
|
|
|
976
994
|
// Null/undefined values are handled separately (required field check)
|
|
977
995
|
return null;
|
|
978
996
|
}
|
|
979
|
-
|
|
997
|
+
|
|
980
998
|
const expectedType = fieldSchema.type;
|
|
981
999
|
if (!expectedType) {
|
|
982
1000
|
// No type specified, consider valid
|
|
983
1001
|
return null;
|
|
984
1002
|
}
|
|
985
|
-
|
|
1003
|
+
|
|
986
1004
|
const actualType = Array.isArray(value) ? 'array' : typeof value;
|
|
987
|
-
|
|
1005
|
+
|
|
988
1006
|
// Handle array of types
|
|
989
1007
|
const allowedTypes = Array.isArray(expectedType) ? expectedType : [expectedType];
|
|
990
|
-
|
|
1008
|
+
|
|
991
1009
|
// Map JavaScript types to JSON Schema types
|
|
992
1010
|
const typeMapping: Record<string, string> = {
|
|
993
1011
|
'string': 'string',
|
|
@@ -996,9 +1014,9 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
|
|
|
996
1014
|
'object': 'object',
|
|
997
1015
|
'array': 'array',
|
|
998
1016
|
};
|
|
999
|
-
|
|
1017
|
+
|
|
1000
1018
|
const mappedActualType = typeMapping[actualType] || actualType;
|
|
1001
|
-
|
|
1019
|
+
|
|
1002
1020
|
// Check if actual type matches any allowed type
|
|
1003
1021
|
// Also handle 'integer' as a valid number type
|
|
1004
1022
|
const isValidType = allowedTypes.some(t => {
|
|
@@ -1007,7 +1025,7 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
|
|
|
1007
1025
|
}
|
|
1008
1026
|
return t === mappedActualType;
|
|
1009
1027
|
});
|
|
1010
|
-
|
|
1028
|
+
|
|
1011
1029
|
if (!isValidType) {
|
|
1012
1030
|
return {
|
|
1013
1031
|
field,
|
|
@@ -1016,7 +1034,7 @@ export class BatchExecutor<TContext = unknown, TData = unknown> {
|
|
|
1016
1034
|
schemaPath: `properties.${field}.type`,
|
|
1017
1035
|
};
|
|
1018
1036
|
}
|
|
1019
|
-
|
|
1037
|
+
|
|
1020
1038
|
return null;
|
|
1021
1039
|
}
|
|
1022
1040
|
}
|
|
@@ -1047,7 +1065,7 @@ export interface ExecuteHooksParams<TContext, TData> {
|
|
|
1047
1065
|
/**
|
|
1048
1066
|
* Type for the hook execution function
|
|
1049
1067
|
*/
|
|
1050
|
-
export type HookFunction<TContext, TData> =
|
|
1068
|
+
export type HookFunction<TContext, TData> =
|
|
1051
1069
|
| string
|
|
1052
1070
|
| Tool<TContext, TData>
|
|
1053
1071
|
| ((context: TContext, data?: Partial<TData>) => void | Promise<void>);
|