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