@falai/agent 0.1.0-alpha2 → 0.1.0-alpha3
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/README.md +106 -0
- package/dist/cjs/src/core/BatchExecutor.d.ts +353 -0
- package/dist/cjs/src/core/BatchExecutor.d.ts.map +1 -0
- package/dist/cjs/src/core/BatchExecutor.js +842 -0
- package/dist/cjs/src/core/BatchExecutor.js.map +1 -0
- package/dist/cjs/src/core/BatchPromptBuilder.d.ts +86 -0
- package/dist/cjs/src/core/BatchPromptBuilder.d.ts.map +1 -0
- package/dist/cjs/src/core/BatchPromptBuilder.js +201 -0
- package/dist/cjs/src/core/BatchPromptBuilder.js.map +1 -0
- package/dist/cjs/src/core/ResponseModal.d.ts +34 -0
- package/dist/cjs/src/core/ResponseModal.d.ts.map +1 -1
- package/dist/cjs/src/core/ResponseModal.js +460 -34
- package/dist/cjs/src/core/ResponseModal.js.map +1 -1
- package/dist/cjs/src/index.d.ts +2 -0
- package/dist/cjs/src/index.d.ts.map +1 -1
- package/dist/cjs/src/index.js +7 -1
- package/dist/cjs/src/index.js.map +1 -1
- package/dist/cjs/src/types/agent.d.ts +9 -1
- package/dist/cjs/src/types/agent.d.ts.map +1 -1
- package/dist/cjs/src/types/route.d.ts +98 -0
- package/dist/cjs/src/types/route.d.ts.map +1 -1
- package/dist/src/core/BatchExecutor.d.ts +353 -0
- package/dist/src/core/BatchExecutor.d.ts.map +1 -0
- package/dist/src/core/BatchExecutor.js +837 -0
- package/dist/src/core/BatchExecutor.js.map +1 -0
- package/dist/src/core/BatchPromptBuilder.d.ts +86 -0
- package/dist/src/core/BatchPromptBuilder.d.ts.map +1 -0
- package/dist/src/core/BatchPromptBuilder.js +197 -0
- package/dist/src/core/BatchPromptBuilder.js.map +1 -0
- package/dist/src/core/ResponseModal.d.ts +34 -0
- package/dist/src/core/ResponseModal.d.ts.map +1 -1
- package/dist/src/core/ResponseModal.js +460 -34
- package/dist/src/core/ResponseModal.js.map +1 -1
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/types/agent.d.ts +9 -1
- package/dist/src/types/agent.d.ts.map +1 -1
- package/dist/src/types/route.d.ts +98 -0
- package/dist/src/types/route.d.ts.map +1 -1
- package/docs/api/overview.md +124 -0
- package/docs/architecture/multi-step-execution.md +243 -0
- package/docs/core/ai-integration/prompt-composition.md +135 -0
- package/docs/core/ai-integration/response-processing.md +146 -0
- package/docs/core/conversation-flows/data-collection.md +143 -0
- package/docs/core/conversation-flows/step-transitions.md +132 -0
- package/docs/core/conversation-flows/steps.md +112 -0
- package/docs/core/error-handling.md +193 -0
- package/docs/guides/getting-started/README.md +102 -0
- package/docs/guides/migration/README.md +23 -0
- package/docs/guides/migration/multi-step-execution.md +303 -0
- package/package.json +4 -2
- package/src/core/BatchExecutor.ts +1156 -0
- package/src/core/BatchPromptBuilder.ts +275 -0
- package/src/core/ResponseModal.ts +605 -35
- package/src/index.ts +2 -0
- package/src/types/agent.ts +9 -1
- package/src/types/route.ts +119 -0
|
@@ -15,6 +15,7 @@ import type {
|
|
|
15
15
|
ToolEventData,
|
|
16
16
|
AgentStructuredResponse,
|
|
17
17
|
Term,
|
|
18
|
+
StoppedReason,
|
|
18
19
|
} from "../types";
|
|
19
20
|
import { EventKind, MessageRole } from "../types";
|
|
20
21
|
import type { Agent } from "./Agent";
|
|
@@ -22,10 +23,13 @@ import type { Route } from "./Route";
|
|
|
22
23
|
import { Step } from "./Step";
|
|
23
24
|
import { ResponseEngine } from "./ResponseEngine";
|
|
24
25
|
import { ResponsePipeline } from "./ResponsePipeline";
|
|
26
|
+
import { BatchExecutor, type HookFunction } from "./BatchExecutor";
|
|
27
|
+
import { BatchPromptBuilder } from "./BatchPromptBuilder";
|
|
25
28
|
import { cloneDeep, mergeCollected, enterStep, getLastMessageFromHistory, render, logger, historyToEvents } from "../utils";
|
|
26
29
|
import { createTemplateContext } from "../utils/template";
|
|
27
30
|
import type { ToolManager } from "./ToolManager";
|
|
28
31
|
import { END_ROUTE_ID } from "../constants";
|
|
32
|
+
import type { StepOptions } from "../types/route";
|
|
29
33
|
|
|
30
34
|
/**
|
|
31
35
|
* Configuration options for ResponseModal
|
|
@@ -130,6 +134,12 @@ interface ResponseContext<TContext = unknown, TData = unknown> {
|
|
|
130
134
|
selectedStep?: Step<TContext, TData>;
|
|
131
135
|
responseDirectives?: string[];
|
|
132
136
|
isRouteComplete: boolean;
|
|
137
|
+
/** Batch of steps to execute (for multi-step execution) */
|
|
138
|
+
batchSteps?: StepOptions<TContext, TData>[];
|
|
139
|
+
/** Reason why batch determination stopped */
|
|
140
|
+
batchStoppedReason?: StoppedReason;
|
|
141
|
+
/** Step that caused batch to stop (if applicable) */
|
|
142
|
+
batchStoppedAtStep?: StepOptions<TContext, TData>;
|
|
133
143
|
}
|
|
134
144
|
|
|
135
145
|
/**
|
|
@@ -139,6 +149,8 @@ interface ResponseContext<TContext = unknown, TData = unknown> {
|
|
|
139
149
|
export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
140
150
|
private readonly responseEngine: ResponseEngine<TContext, TData>;
|
|
141
151
|
private readonly responsePipeline: ResponsePipeline<TContext, TData>;
|
|
152
|
+
private readonly batchExecutor: BatchExecutor<TContext, TData>;
|
|
153
|
+
private readonly batchPromptBuilder: BatchPromptBuilder<TContext, TData>;
|
|
142
154
|
|
|
143
155
|
constructor(
|
|
144
156
|
private readonly agent: Agent<TContext, TData>,
|
|
@@ -158,6 +170,12 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
158
170
|
this.agent.updateCollectedData.bind(this.agent),
|
|
159
171
|
this.getToolManager()
|
|
160
172
|
);
|
|
173
|
+
|
|
174
|
+
// Initialize batch executor for multi-step execution
|
|
175
|
+
this.batchExecutor = new BatchExecutor<TContext, TData>();
|
|
176
|
+
|
|
177
|
+
// Initialize batch prompt builder for combined prompts
|
|
178
|
+
this.batchPromptBuilder = new BatchPromptBuilder<TContext, TData>();
|
|
161
179
|
}
|
|
162
180
|
|
|
163
181
|
/**
|
|
@@ -419,12 +437,16 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
419
437
|
}
|
|
420
438
|
|
|
421
439
|
// PHASE 2: ROUTING + STEP SELECTION - Determine which route and step to use
|
|
440
|
+
// Also performs pre-extraction and batch determination
|
|
422
441
|
let routingResult: {
|
|
423
442
|
selectedRoute?: Route<TContext, TData>;
|
|
424
443
|
selectedStep?: Step<TContext, TData>;
|
|
425
444
|
responseDirectives?: string[];
|
|
426
445
|
session: SessionState<TData>;
|
|
427
446
|
isRouteComplete: boolean;
|
|
447
|
+
batchSteps?: StepOptions<TContext, TData>[];
|
|
448
|
+
batchStoppedReason?: StoppedReason;
|
|
449
|
+
batchStoppedAtStep?: StepOptions<TContext, TData>;
|
|
428
450
|
};
|
|
429
451
|
try {
|
|
430
452
|
routingResult = await this.handleUnifiedRoutingAndStepSelection({
|
|
@@ -445,6 +467,9 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
445
467
|
selectedStep: routingResult.selectedStep,
|
|
446
468
|
responseDirectives: routingResult.responseDirectives,
|
|
447
469
|
isRouteComplete: routingResult.isRouteComplete,
|
|
470
|
+
batchSteps: routingResult.batchSteps,
|
|
471
|
+
batchStoppedReason: routingResult.batchStoppedReason,
|
|
472
|
+
batchStoppedAtStep: routingResult.batchStoppedAtStep,
|
|
448
473
|
};
|
|
449
474
|
} catch (error) {
|
|
450
475
|
// Re-throw ResponseGenerationError as-is, wrap others
|
|
@@ -470,6 +495,12 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
470
495
|
responseDirectives?: string[];
|
|
471
496
|
session: SessionState<TData>;
|
|
472
497
|
isRouteComplete: boolean;
|
|
498
|
+
/** Batch of steps to execute (for multi-step execution) */
|
|
499
|
+
batchSteps?: StepOptions<TContext, TData>[];
|
|
500
|
+
/** Reason why batch determination stopped */
|
|
501
|
+
batchStoppedReason?: StoppedReason;
|
|
502
|
+
/** Step that caused batch to stop (if applicable) */
|
|
503
|
+
batchStoppedAtStep?: StepOptions<TContext, TData>;
|
|
473
504
|
}> {
|
|
474
505
|
try {
|
|
475
506
|
// Use the ResponsePipeline for optimized routing and step selection
|
|
@@ -485,13 +516,13 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
485
516
|
let updatedSession = routingResult.session;
|
|
486
517
|
let isRouteComplete = routingResult.isRouteComplete;
|
|
487
518
|
|
|
488
|
-
// PRE-EXTRACTION: If entering a
|
|
519
|
+
// PRE-EXTRACTION: If entering a route that collects data, extract data from user message first
|
|
489
520
|
// This allows us to skip steps whose data is already provided
|
|
521
|
+
// Requirement 3.1: Perform Pre_Extraction before determining the Batch
|
|
490
522
|
if (routingResult.selectedRoute && !isRouteComplete) {
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
if (isEnteringNewRoute && this.shouldPreExtractData(routingResult.selectedRoute)) {
|
|
523
|
+
// Always pre-extract when route collects data (not just on new route entry)
|
|
524
|
+
// This ensures batch determination has the most up-to-date data
|
|
525
|
+
if (this.shouldPreExtractData(routingResult.selectedRoute)) {
|
|
495
526
|
logger.debug(
|
|
496
527
|
`[ResponseModal] Pre-extracting data for route: ${routingResult.selectedRoute.title}`
|
|
497
528
|
);
|
|
@@ -509,7 +540,7 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
509
540
|
`[ResponseModal] Pre-extracted data:`,
|
|
510
541
|
extractedData
|
|
511
542
|
);
|
|
512
|
-
//
|
|
543
|
+
// Requirement 3.3: Merge pre-extracted data into session before batch determination
|
|
513
544
|
updatedSession = mergeCollected(updatedSession, extractedData);
|
|
514
545
|
// Also update agent's collected data
|
|
515
546
|
await this.agent.updateCollectedData(extractedData);
|
|
@@ -526,6 +557,33 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
526
557
|
}
|
|
527
558
|
}
|
|
528
559
|
|
|
560
|
+
// BATCH DETERMINATION: Use BatchExecutor to determine which steps can execute together
|
|
561
|
+
// Requirement 3.4: Pre-extraction results affect batch determination
|
|
562
|
+
let batchSteps: StepOptions<TContext, TData>[] | undefined;
|
|
563
|
+
let batchStoppedReason: StoppedReason | undefined;
|
|
564
|
+
let batchStoppedAtStep: StepOptions<TContext, TData> | undefined;
|
|
565
|
+
|
|
566
|
+
if (routingResult.selectedRoute && !isRouteComplete) {
|
|
567
|
+
// Determine current step position for batch determination
|
|
568
|
+
const currentStep = routingResult.selectedStep ||
|
|
569
|
+
(updatedSession.currentStep ? routingResult.selectedRoute.getStep(updatedSession.currentStep.id) : undefined);
|
|
570
|
+
|
|
571
|
+
logger.debug(`[ResponseModal] Determining batch starting from step: ${currentStep?.id || 'initial'}`);
|
|
572
|
+
|
|
573
|
+
const batchResult = await this.batchExecutor.determineBatch({
|
|
574
|
+
route: routingResult.selectedRoute,
|
|
575
|
+
currentStep,
|
|
576
|
+
sessionData: updatedSession.data || {},
|
|
577
|
+
context: params.context,
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
batchSteps = batchResult.steps;
|
|
581
|
+
batchStoppedReason = batchResult.stoppedReason;
|
|
582
|
+
batchStoppedAtStep = batchResult.stoppedAtStep;
|
|
583
|
+
|
|
584
|
+
logger.debug(`[ResponseModal] Batch determined: ${batchSteps.length} steps, stopped reason: ${batchStoppedReason}`);
|
|
585
|
+
}
|
|
586
|
+
|
|
529
587
|
// Determine next step using pipeline method for consistency
|
|
530
588
|
const stepResult = await this.responsePipeline.determineNextStep({
|
|
531
589
|
selectedRoute: routingResult.selectedRoute,
|
|
@@ -540,6 +598,9 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
540
598
|
responseDirectives: routingResult.responseDirectives,
|
|
541
599
|
session: stepResult.session,
|
|
542
600
|
isRouteComplete, // Use updated completion status
|
|
601
|
+
batchSteps,
|
|
602
|
+
batchStoppedReason,
|
|
603
|
+
batchStoppedAtStep,
|
|
543
604
|
};
|
|
544
605
|
} catch (error) {
|
|
545
606
|
throw ResponseGenerationError.fromError(error, 'routing_optimization', params);
|
|
@@ -643,7 +704,17 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
643
704
|
private async generateUnifiedResponse(
|
|
644
705
|
responseContext: ResponseContext<TContext, TData>
|
|
645
706
|
): Promise<AgentResponse<TData>> {
|
|
646
|
-
const {
|
|
707
|
+
const {
|
|
708
|
+
effectiveContext,
|
|
709
|
+
session: initialSession,
|
|
710
|
+
history,
|
|
711
|
+
selectedRoute,
|
|
712
|
+
selectedStep,
|
|
713
|
+
responseDirectives,
|
|
714
|
+
isRouteComplete,
|
|
715
|
+
batchSteps,
|
|
716
|
+
batchStoppedReason,
|
|
717
|
+
} = responseContext;
|
|
647
718
|
let session = initialSession;
|
|
648
719
|
|
|
649
720
|
// Get last user message (needed for both route and completion handling)
|
|
@@ -653,27 +724,61 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
653
724
|
|
|
654
725
|
let message: string;
|
|
655
726
|
let toolCalls: Array<{ toolName: string; arguments: Record<string, unknown> }> | undefined = undefined;
|
|
727
|
+
let executedSteps: StepRef[] | undefined;
|
|
728
|
+
let stoppedReason: StoppedReason | undefined;
|
|
656
729
|
|
|
657
730
|
|
|
658
731
|
|
|
659
732
|
if (selectedRoute && !isRouteComplete) {
|
|
660
|
-
//
|
|
733
|
+
// Check if we have batch steps to execute
|
|
734
|
+
if (batchSteps && batchSteps.length > 0) {
|
|
735
|
+
// BATCH EXECUTION: Execute multiple steps in a single LLM call
|
|
736
|
+
logger.debug(`[ResponseModal] Executing batch of ${batchSteps.length} steps`);
|
|
661
737
|
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
738
|
+
const batchResult = await this.executeBatchResponse({
|
|
739
|
+
selectedRoute,
|
|
740
|
+
batchSteps,
|
|
741
|
+
responseDirectives,
|
|
742
|
+
session,
|
|
743
|
+
history,
|
|
744
|
+
context: effectiveContext,
|
|
745
|
+
historyEvents,
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
message = batchResult.message;
|
|
749
|
+
toolCalls = batchResult.toolCalls;
|
|
750
|
+
session = batchResult.session;
|
|
751
|
+
executedSteps = batchResult.executedSteps;
|
|
752
|
+
stoppedReason = batchStoppedReason;
|
|
753
|
+
|
|
754
|
+
} else {
|
|
755
|
+
// SINGLE STEP EXECUTION: Fall back to single-step processing
|
|
756
|
+
// This happens when batch determination returns empty (first step needs input)
|
|
757
|
+
const result = await this.processRouteResponse({
|
|
758
|
+
selectedRoute,
|
|
759
|
+
selectedStep,
|
|
760
|
+
responseDirectives,
|
|
761
|
+
session,
|
|
762
|
+
history,
|
|
763
|
+
context: effectiveContext,
|
|
764
|
+
lastMessageText,
|
|
765
|
+
historyEvents,
|
|
766
|
+
signal: undefined,
|
|
767
|
+
});
|
|
673
768
|
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
769
|
+
message = result.message;
|
|
770
|
+
toolCalls = result.toolCalls;
|
|
771
|
+
session = result.session;
|
|
772
|
+
|
|
773
|
+
// Track executed step for single-step execution
|
|
774
|
+
if (selectedStep) {
|
|
775
|
+
executedSteps = [{
|
|
776
|
+
id: selectedStep.id,
|
|
777
|
+
routeId: selectedRoute.id,
|
|
778
|
+
}];
|
|
779
|
+
}
|
|
780
|
+
stoppedReason = batchStoppedReason || 'needs_input';
|
|
781
|
+
}
|
|
677
782
|
|
|
678
783
|
} else if (isRouteComplete && selectedRoute) {
|
|
679
784
|
// Handle route completion
|
|
@@ -686,17 +791,19 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
686
791
|
context: effectiveContext,
|
|
687
792
|
lastMessageText,
|
|
688
793
|
historyEvents,
|
|
689
|
-
signal: undefined,
|
|
794
|
+
signal: undefined,
|
|
690
795
|
});
|
|
691
796
|
|
|
692
797
|
// Set step to END_ROUTE marker
|
|
693
798
|
session = enterStep(session, END_ROUTE_ID, "Route completed");
|
|
799
|
+
stoppedReason = 'route_complete';
|
|
694
800
|
logger.debug(`[ResponseModal] Route ${selectedRoute.title} completed. Entered END_ROUTE step.`);
|
|
695
801
|
} catch (error) {
|
|
696
802
|
logger.error(`[ResponseModal] Error generating completion message:`, error);
|
|
697
803
|
// Fallback to simple completion message
|
|
698
804
|
message = `Thank you! I've recorded all the information for your ${selectedRoute.title.toLowerCase()}.`;
|
|
699
805
|
session = enterStep(session, END_ROUTE_ID, "Route completed");
|
|
806
|
+
stoppedReason = 'route_complete';
|
|
700
807
|
}
|
|
701
808
|
|
|
702
809
|
} else {
|
|
@@ -707,16 +814,291 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
707
814
|
context: effectiveContext,
|
|
708
815
|
session,
|
|
709
816
|
});
|
|
817
|
+
|
|
818
|
+
// For fallback responses, set empty executedSteps and no stoppedReason
|
|
819
|
+
// since there's no route/step execution happening
|
|
820
|
+
executedSteps = [];
|
|
821
|
+
stoppedReason = undefined;
|
|
710
822
|
}
|
|
711
823
|
|
|
824
|
+
// Ensure response structure completeness (Requirement 8.1, 8.2, 8.3)
|
|
825
|
+
// - executedSteps: array of steps executed (empty array if none)
|
|
826
|
+
// - stoppedReason: why execution stopped (undefined for fallback)
|
|
827
|
+
// - session.currentStep: reflects final step position
|
|
712
828
|
return {
|
|
713
829
|
message,
|
|
714
830
|
session,
|
|
715
831
|
toolCalls,
|
|
716
832
|
isRouteComplete,
|
|
833
|
+
executedSteps: executedSteps || [],
|
|
834
|
+
stoppedReason,
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
/**
|
|
839
|
+
* Execute a batch of steps with a single LLM call
|
|
840
|
+
*
|
|
841
|
+
* This method:
|
|
842
|
+
* 1. Executes all prepare hooks for steps in the batch (in order)
|
|
843
|
+
* 2. Builds a combined prompt using BatchPromptBuilder
|
|
844
|
+
* 3. Makes a single LLM call
|
|
845
|
+
* 4. Collects data from the response for all steps
|
|
846
|
+
* 5. Executes all finalize hooks for steps in the batch (in order)
|
|
847
|
+
*
|
|
848
|
+
* @private
|
|
849
|
+
* **Validates: Requirements 1.1, 4.4, 5.1, 5.2**
|
|
850
|
+
*/
|
|
851
|
+
private async executeBatchResponse(params: {
|
|
852
|
+
selectedRoute: Route<TContext, TData>;
|
|
853
|
+
batchSteps: StepOptions<TContext, TData>[];
|
|
854
|
+
responseDirectives?: string[];
|
|
855
|
+
session: SessionState<TData>;
|
|
856
|
+
history: HistoryItem[];
|
|
857
|
+
context: TContext;
|
|
858
|
+
historyEvents: Event[];
|
|
859
|
+
signal?: AbortSignal;
|
|
860
|
+
}): Promise<{
|
|
861
|
+
message: string;
|
|
862
|
+
toolCalls?: Array<{ toolName: string; arguments: Record<string, unknown> }>;
|
|
863
|
+
session: SessionState<TData>;
|
|
864
|
+
executedSteps: StepRef[];
|
|
865
|
+
}> {
|
|
866
|
+
const { selectedRoute, batchSteps, history, context, historyEvents, signal } = params;
|
|
867
|
+
let session = params.session;
|
|
868
|
+
|
|
869
|
+
logger.debug(`[ResponseModal] Starting batch execution for ${batchSteps.length} steps`);
|
|
870
|
+
|
|
871
|
+
// Create hook executor function
|
|
872
|
+
const executeHook = async (
|
|
873
|
+
hook: HookFunction<TContext, TData>,
|
|
874
|
+
hookContext: TContext,
|
|
875
|
+
data?: Partial<TData>,
|
|
876
|
+
step?: StepOptions<TContext, TData>
|
|
877
|
+
): Promise<void> => {
|
|
878
|
+
// Find the route for this step
|
|
879
|
+
const route = selectedRoute;
|
|
880
|
+
// Convert StepOptions to Step if needed for executePrepareFinalize
|
|
881
|
+
const stepInstance = step?.id ? route.getStep(step.id) : undefined;
|
|
882
|
+
await this.executePrepareFinalize(hook, hookContext, data, route, stepInstance);
|
|
883
|
+
};
|
|
884
|
+
|
|
885
|
+
// PHASE 1: Execute all prepare hooks (Requirement 5.1)
|
|
886
|
+
logger.debug(`[ResponseModal] Executing prepare hooks for batch`);
|
|
887
|
+
const prepareResult = await this.batchExecutor.executePrepareHooks({
|
|
888
|
+
steps: batchSteps,
|
|
889
|
+
context,
|
|
890
|
+
data: session.data,
|
|
891
|
+
executeHook,
|
|
892
|
+
});
|
|
893
|
+
|
|
894
|
+
if (!prepareResult.success) {
|
|
895
|
+
// Prepare hook failed - return error response
|
|
896
|
+
logger.error(`[ResponseModal] Prepare hook failed:`, prepareResult.error);
|
|
897
|
+
throw new ResponseGenerationError(
|
|
898
|
+
`Prepare hook failed: ${prepareResult.error?.message}`,
|
|
899
|
+
{
|
|
900
|
+
phase: 'prepare_hooks',
|
|
901
|
+
context: {
|
|
902
|
+
stepId: prepareResult.error?.stepId,
|
|
903
|
+
executedSteps: prepareResult.executedSteps,
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
);
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
// PHASE 2: Build combined prompt using BatchPromptBuilder (Requirement 4.4)
|
|
910
|
+
logger.debug(`[ResponseModal] Building batch prompt`);
|
|
911
|
+
const batchPromptResult = await this.batchPromptBuilder.buildBatchPrompt({
|
|
912
|
+
steps: batchSteps,
|
|
913
|
+
route: selectedRoute,
|
|
914
|
+
history: historyEvents,
|
|
915
|
+
context,
|
|
916
|
+
session,
|
|
917
|
+
agentOptions: this.agent.getAgentOptions(),
|
|
918
|
+
});
|
|
919
|
+
|
|
920
|
+
logger.debug(`[ResponseModal] Batch prompt built with ${batchPromptResult.stepCount} steps, collecting: ${batchPromptResult.collectFields.join(', ')}`);
|
|
921
|
+
|
|
922
|
+
// Build response schema for batch (includes all collect fields)
|
|
923
|
+
const responseSchema = this.buildBatchResponseSchema(batchPromptResult.collectFields);
|
|
924
|
+
|
|
925
|
+
// Collect available tools for AI (from all steps in batch)
|
|
926
|
+
const availableTools = this.collectBatchAvailableTools(selectedRoute, batchSteps);
|
|
927
|
+
|
|
928
|
+
// PHASE 3: Make single LLM call (Requirement 4.4)
|
|
929
|
+
logger.debug(`[ResponseModal] Making LLM call for batch`);
|
|
930
|
+
const agentOptions = this.agent.getAgentOptions();
|
|
931
|
+
const result = await agentOptions.provider.generateMessage({
|
|
932
|
+
prompt: batchPromptResult.prompt,
|
|
933
|
+
history: historyEvents,
|
|
934
|
+
context,
|
|
935
|
+
tools: availableTools,
|
|
936
|
+
signal,
|
|
937
|
+
parameters: responseSchema ? { jsonSchema: responseSchema, schemaName: "batch_response" } : undefined,
|
|
938
|
+
});
|
|
939
|
+
|
|
940
|
+
let message = result.structured?.message || result.message;
|
|
941
|
+
let toolCalls = result.structured?.toolCalls;
|
|
942
|
+
|
|
943
|
+
logger.debug(`[ResponseModal] LLM response received for batch`);
|
|
944
|
+
|
|
945
|
+
// Execute tools if any
|
|
946
|
+
if (toolCalls && toolCalls.length > 0) {
|
|
947
|
+
const toolResult = await this.executeUnifiedToolLoop({
|
|
948
|
+
toolCalls,
|
|
949
|
+
context,
|
|
950
|
+
session,
|
|
951
|
+
history,
|
|
952
|
+
selectedRoute,
|
|
953
|
+
responsePrompt: batchPromptResult.prompt,
|
|
954
|
+
availableTools,
|
|
955
|
+
responseSchema,
|
|
956
|
+
signal,
|
|
957
|
+
});
|
|
958
|
+
|
|
959
|
+
session = toolResult.session;
|
|
960
|
+
toolCalls = toolResult.finalToolCalls;
|
|
961
|
+
if (toolResult.finalMessage) {
|
|
962
|
+
message = toolResult.finalMessage;
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
// PHASE 4: Collect data from response for all steps (Requirement 6.1, 6.2, 6.3)
|
|
967
|
+
logger.debug(`[ResponseModal] Collecting batch data`);
|
|
968
|
+
const collectResult = this.batchExecutor.collectBatchData({
|
|
969
|
+
steps: batchSteps,
|
|
970
|
+
llmResponse: result.structured || {},
|
|
971
|
+
session,
|
|
972
|
+
schema: this.agent.getSchema(),
|
|
973
|
+
});
|
|
974
|
+
|
|
975
|
+
session = collectResult.session;
|
|
976
|
+
|
|
977
|
+
if (collectResult.collectedData && Object.keys(collectResult.collectedData).length > 0) {
|
|
978
|
+
// Update agent's collected data
|
|
979
|
+
await this.agent.updateCollectedData(collectResult.collectedData);
|
|
980
|
+
logger.debug(`[ResponseModal] Batch collected data:`, collectResult.collectedData);
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
if (collectResult.validationErrors && collectResult.validationErrors.length > 0) {
|
|
984
|
+
logger.warn(`[ResponseModal] Batch data validation errors:`, collectResult.validationErrors);
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
// Update session to final step position
|
|
988
|
+
const lastStep = batchSteps[batchSteps.length - 1];
|
|
989
|
+
if (lastStep?.id) {
|
|
990
|
+
session = enterStep(session, lastStep.id, lastStep.description);
|
|
991
|
+
logger.debug(`[ResponseModal] Updated session to final batch step: ${lastStep.id}`);
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
// PHASE 5: Execute all finalize hooks (Requirement 5.2)
|
|
995
|
+
logger.debug(`[ResponseModal] Executing finalize hooks for batch`);
|
|
996
|
+
const finalizeResult = await this.batchExecutor.executeFinalizeHooks({
|
|
997
|
+
steps: batchSteps,
|
|
998
|
+
context,
|
|
999
|
+
data: session.data,
|
|
1000
|
+
executeHook,
|
|
1001
|
+
});
|
|
1002
|
+
|
|
1003
|
+
if (finalizeResult.errors && finalizeResult.errors.length > 0) {
|
|
1004
|
+
// Log finalize errors but don't fail (Requirement 5.5)
|
|
1005
|
+
logger.warn(`[ResponseModal] Some finalize hooks failed:`, finalizeResult.errors);
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
// Build executed steps list
|
|
1009
|
+
const executedSteps: StepRef[] = batchSteps
|
|
1010
|
+
.filter(step => step.id)
|
|
1011
|
+
.map(step => ({
|
|
1012
|
+
id: step.id!,
|
|
1013
|
+
routeId: selectedRoute.id,
|
|
1014
|
+
}));
|
|
1015
|
+
|
|
1016
|
+
logger.debug(`[ResponseModal] Batch execution complete. Executed ${executedSteps.length} steps`);
|
|
1017
|
+
|
|
1018
|
+
return {
|
|
1019
|
+
message,
|
|
1020
|
+
toolCalls,
|
|
1021
|
+
session,
|
|
1022
|
+
executedSteps,
|
|
1023
|
+
};
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
/**
|
|
1027
|
+
* Build response schema for batch execution
|
|
1028
|
+
* @private
|
|
1029
|
+
*/
|
|
1030
|
+
private buildBatchResponseSchema(collectFields: string[]): Record<string, unknown> {
|
|
1031
|
+
const properties: Record<string, unknown> = {
|
|
1032
|
+
message: {
|
|
1033
|
+
type: "string",
|
|
1034
|
+
description: "Your response to the user",
|
|
1035
|
+
},
|
|
1036
|
+
};
|
|
1037
|
+
|
|
1038
|
+
// Add collect fields to schema
|
|
1039
|
+
for (const field of collectFields) {
|
|
1040
|
+
properties[field] = {
|
|
1041
|
+
type: "string",
|
|
1042
|
+
description: `Collected value for ${field}`,
|
|
1043
|
+
};
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
return {
|
|
1047
|
+
type: "object",
|
|
1048
|
+
properties,
|
|
1049
|
+
required: ["message"],
|
|
1050
|
+
additionalProperties: true,
|
|
717
1051
|
};
|
|
718
1052
|
}
|
|
719
1053
|
|
|
1054
|
+
/**
|
|
1055
|
+
* Collect available tools from all steps in the batch
|
|
1056
|
+
* @private
|
|
1057
|
+
*/
|
|
1058
|
+
private collectBatchAvailableTools(
|
|
1059
|
+
route: Route<TContext, TData>,
|
|
1060
|
+
batchSteps: StepOptions<TContext, TData>[]
|
|
1061
|
+
): Array<{
|
|
1062
|
+
id: string;
|
|
1063
|
+
name: string;
|
|
1064
|
+
description?: string;
|
|
1065
|
+
parameters?: unknown;
|
|
1066
|
+
}> {
|
|
1067
|
+
const availableTools = new Map<string, Tool<TContext, TData>>();
|
|
1068
|
+
|
|
1069
|
+
// Add agent-level tools
|
|
1070
|
+
this.agent.getTools().forEach((tool) => {
|
|
1071
|
+
availableTools.set(tool.id, tool);
|
|
1072
|
+
});
|
|
1073
|
+
|
|
1074
|
+
// Add route-level tools
|
|
1075
|
+
route.getTools().forEach((tool: Tool<TContext, TData>) => {
|
|
1076
|
+
availableTools.set(tool.id, tool);
|
|
1077
|
+
});
|
|
1078
|
+
|
|
1079
|
+
// Add step-level tools from all batch steps
|
|
1080
|
+
for (const step of batchSteps) {
|
|
1081
|
+
if (step.tools) {
|
|
1082
|
+
for (const toolRef of step.tools) {
|
|
1083
|
+
if (typeof toolRef === "string") {
|
|
1084
|
+
// Reference to registered tool - already in availableTools
|
|
1085
|
+
} else if (typeof toolRef === 'object' && 'id' in toolRef && toolRef.id) {
|
|
1086
|
+
// Inline tool definition
|
|
1087
|
+
availableTools.set(toolRef.id, toolRef);
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
// Convert to the format expected by AI providers
|
|
1094
|
+
return Array.from(availableTools.values()).map((tool) => ({
|
|
1095
|
+
id: tool.id,
|
|
1096
|
+
name: tool.name || tool.id,
|
|
1097
|
+
description: tool.description,
|
|
1098
|
+
parameters: tool.parameters,
|
|
1099
|
+
}));
|
|
1100
|
+
}
|
|
1101
|
+
|
|
720
1102
|
/**
|
|
721
1103
|
* Unified streaming response generation
|
|
722
1104
|
* @private
|
|
@@ -724,7 +1106,17 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
724
1106
|
private async *generateUnifiedStreamingResponse(
|
|
725
1107
|
responseContext: ResponseContext<TContext, TData>
|
|
726
1108
|
): AsyncGenerator<AgentResponseStreamChunk<TData>> {
|
|
727
|
-
const {
|
|
1109
|
+
const {
|
|
1110
|
+
effectiveContext,
|
|
1111
|
+
session: initialSession,
|
|
1112
|
+
history,
|
|
1113
|
+
selectedRoute,
|
|
1114
|
+
selectedStep,
|
|
1115
|
+
responseDirectives,
|
|
1116
|
+
isRouteComplete,
|
|
1117
|
+
batchSteps,
|
|
1118
|
+
batchStoppedReason,
|
|
1119
|
+
} = responseContext;
|
|
728
1120
|
const session = initialSession;
|
|
729
1121
|
|
|
730
1122
|
// Get last user message (needed for both route and completion handling)
|
|
@@ -733,17 +1125,35 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
733
1125
|
const lastMessageText = getLastMessageFromHistory(historyEvents);
|
|
734
1126
|
|
|
735
1127
|
if (selectedRoute && !isRouteComplete) {
|
|
736
|
-
//
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
1128
|
+
// Check if we have batch steps to execute
|
|
1129
|
+
if (batchSteps && batchSteps.length > 0) {
|
|
1130
|
+
// BATCH EXECUTION: Execute multiple steps with streaming
|
|
1131
|
+
// Note: For streaming, we still use batch execution but stream the response
|
|
1132
|
+
logger.debug(`[ResponseModal] Streaming batch execution for ${batchSteps.length} steps`);
|
|
1133
|
+
|
|
1134
|
+
yield* this.streamBatchResponse({
|
|
1135
|
+
selectedRoute,
|
|
1136
|
+
batchSteps,
|
|
1137
|
+
responseDirectives,
|
|
1138
|
+
session,
|
|
1139
|
+
history,
|
|
1140
|
+
context: effectiveContext,
|
|
1141
|
+
historyEvents,
|
|
1142
|
+
batchStoppedReason,
|
|
1143
|
+
});
|
|
1144
|
+
} else {
|
|
1145
|
+
// SINGLE STEP EXECUTION: Fall back to single-step streaming
|
|
1146
|
+
yield* this.processRouteStreamingResponse({
|
|
1147
|
+
selectedRoute,
|
|
1148
|
+
selectedStep,
|
|
1149
|
+
responseDirectives,
|
|
1150
|
+
session,
|
|
1151
|
+
history,
|
|
1152
|
+
context: effectiveContext,
|
|
1153
|
+
lastMessageText,
|
|
1154
|
+
historyEvents,
|
|
1155
|
+
});
|
|
1156
|
+
}
|
|
747
1157
|
|
|
748
1158
|
} else if (isRouteComplete && selectedRoute) {
|
|
749
1159
|
// Handle route completion streaming
|
|
@@ -764,6 +1174,148 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
764
1174
|
});
|
|
765
1175
|
}
|
|
766
1176
|
}
|
|
1177
|
+
|
|
1178
|
+
/**
|
|
1179
|
+
* Stream a batch response with multiple steps
|
|
1180
|
+
*
|
|
1181
|
+
* Similar to executeBatchResponse but streams the LLM response.
|
|
1182
|
+
*
|
|
1183
|
+
* @private
|
|
1184
|
+
*/
|
|
1185
|
+
private async *streamBatchResponse(params: {
|
|
1186
|
+
selectedRoute: Route<TContext, TData>;
|
|
1187
|
+
batchSteps: StepOptions<TContext, TData>[];
|
|
1188
|
+
responseDirectives?: string[];
|
|
1189
|
+
session: SessionState<TData>;
|
|
1190
|
+
history: HistoryItem[];
|
|
1191
|
+
context: TContext;
|
|
1192
|
+
historyEvents: Event[];
|
|
1193
|
+
batchStoppedReason?: StoppedReason;
|
|
1194
|
+
signal?: AbortSignal;
|
|
1195
|
+
}): AsyncGenerator<AgentResponseStreamChunk<TData>> {
|
|
1196
|
+
const { selectedRoute, batchSteps, context, historyEvents, batchStoppedReason, signal } = params;
|
|
1197
|
+
let session = params.session;
|
|
1198
|
+
|
|
1199
|
+
// Create hook executor function
|
|
1200
|
+
const executeHook = async (
|
|
1201
|
+
hook: HookFunction<TContext, TData>,
|
|
1202
|
+
hookContext: TContext,
|
|
1203
|
+
data?: Partial<TData>,
|
|
1204
|
+
step?: StepOptions<TContext, TData>
|
|
1205
|
+
): Promise<void> => {
|
|
1206
|
+
const route = selectedRoute;
|
|
1207
|
+
const stepInstance = step?.id ? route.getStep(step.id) : undefined;
|
|
1208
|
+
await this.executePrepareFinalize(hook, hookContext, data, route, stepInstance);
|
|
1209
|
+
};
|
|
1210
|
+
|
|
1211
|
+
// PHASE 1: Execute all prepare hooks
|
|
1212
|
+
const prepareResult = await this.batchExecutor.executePrepareHooks({
|
|
1213
|
+
steps: batchSteps,
|
|
1214
|
+
context,
|
|
1215
|
+
data: session.data,
|
|
1216
|
+
executeHook,
|
|
1217
|
+
});
|
|
1218
|
+
|
|
1219
|
+
if (!prepareResult.success) {
|
|
1220
|
+
// Yield error chunk
|
|
1221
|
+
yield {
|
|
1222
|
+
delta: "",
|
|
1223
|
+
accumulated: "",
|
|
1224
|
+
done: true,
|
|
1225
|
+
session,
|
|
1226
|
+
error: new ResponseGenerationError(
|
|
1227
|
+
`Prepare hook failed: ${prepareResult.error?.message}`,
|
|
1228
|
+
{ phase: 'prepare_hooks' }
|
|
1229
|
+
),
|
|
1230
|
+
};
|
|
1231
|
+
return;
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
// PHASE 2: Build combined prompt
|
|
1235
|
+
const batchPromptResult = await this.batchPromptBuilder.buildBatchPrompt({
|
|
1236
|
+
steps: batchSteps,
|
|
1237
|
+
route: selectedRoute,
|
|
1238
|
+
history: historyEvents,
|
|
1239
|
+
context,
|
|
1240
|
+
session,
|
|
1241
|
+
agentOptions: this.agent.getAgentOptions(),
|
|
1242
|
+
});
|
|
1243
|
+
|
|
1244
|
+
const responseSchema = this.buildBatchResponseSchema(batchPromptResult.collectFields);
|
|
1245
|
+
const availableTools = this.collectBatchAvailableTools(selectedRoute, batchSteps);
|
|
1246
|
+
|
|
1247
|
+
// PHASE 3: Stream LLM response
|
|
1248
|
+
const agentOptions = this.agent.getAgentOptions();
|
|
1249
|
+
const stream = agentOptions.provider.generateMessageStream({
|
|
1250
|
+
prompt: batchPromptResult.prompt,
|
|
1251
|
+
history: historyEvents,
|
|
1252
|
+
context,
|
|
1253
|
+
tools: availableTools,
|
|
1254
|
+
signal,
|
|
1255
|
+
parameters: responseSchema ? { jsonSchema: responseSchema, schemaName: "batch_stream_response" } : undefined,
|
|
1256
|
+
});
|
|
1257
|
+
|
|
1258
|
+
// Build executed steps list
|
|
1259
|
+
const executedSteps: StepRef[] = batchSteps
|
|
1260
|
+
.filter(step => step.id)
|
|
1261
|
+
.map(step => ({
|
|
1262
|
+
id: step.id!,
|
|
1263
|
+
routeId: selectedRoute.id,
|
|
1264
|
+
}));
|
|
1265
|
+
|
|
1266
|
+
// Stream chunks
|
|
1267
|
+
for await (const chunk of stream) {
|
|
1268
|
+
// On final chunk, collect data and execute finalize hooks
|
|
1269
|
+
if (chunk.done) {
|
|
1270
|
+
// Collect data from response
|
|
1271
|
+
if (chunk.structured) {
|
|
1272
|
+
const collectResult = this.batchExecutor.collectBatchData({
|
|
1273
|
+
steps: batchSteps,
|
|
1274
|
+
llmResponse: chunk.structured,
|
|
1275
|
+
session,
|
|
1276
|
+
schema: this.agent.getSchema(),
|
|
1277
|
+
});
|
|
1278
|
+
|
|
1279
|
+
session = collectResult.session;
|
|
1280
|
+
|
|
1281
|
+
if (collectResult.collectedData && Object.keys(collectResult.collectedData).length > 0) {
|
|
1282
|
+
await this.agent.updateCollectedData(collectResult.collectedData);
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
// Update session to final step position
|
|
1287
|
+
const lastStep = batchSteps[batchSteps.length - 1];
|
|
1288
|
+
if (lastStep?.id) {
|
|
1289
|
+
session = enterStep(session, lastStep.id, lastStep.description);
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
// Execute finalize hooks
|
|
1293
|
+
await this.batchExecutor.executeFinalizeHooks({
|
|
1294
|
+
steps: batchSteps,
|
|
1295
|
+
context,
|
|
1296
|
+
data: session.data,
|
|
1297
|
+
executeHook,
|
|
1298
|
+
});
|
|
1299
|
+
|
|
1300
|
+
// Finalize session
|
|
1301
|
+
await this.finalizeSession(session, context);
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
yield {
|
|
1305
|
+
delta: chunk.delta,
|
|
1306
|
+
accumulated: chunk.accumulated,
|
|
1307
|
+
done: chunk.done,
|
|
1308
|
+
session,
|
|
1309
|
+
toolCalls: chunk.structured?.toolCalls,
|
|
1310
|
+
isRouteComplete: false,
|
|
1311
|
+
executedSteps: chunk.done ? executedSteps : undefined,
|
|
1312
|
+
stoppedReason: chunk.done ? batchStoppedReason : undefined,
|
|
1313
|
+
metadata: chunk.metadata,
|
|
1314
|
+
structured: chunk.structured,
|
|
1315
|
+
};
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
|
|
767
1319
|
/**
|
|
768
1320
|
* Execute prepare function for current step if available
|
|
769
1321
|
* @private
|
|
@@ -1067,6 +1619,10 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
1067
1619
|
await this.finalizeSession(session, context);
|
|
1068
1620
|
}
|
|
1069
1621
|
|
|
1622
|
+
// Response structure completeness (Requirement 8.1, 8.2, 8.3)
|
|
1623
|
+
// - executedSteps: single step executed in this response
|
|
1624
|
+
// - stoppedReason: 'needs_input' for single-step execution (waiting for user input)
|
|
1625
|
+
// - session.currentStep: reflects the executed step
|
|
1070
1626
|
yield {
|
|
1071
1627
|
delta: chunk.delta,
|
|
1072
1628
|
accumulated: chunk.accumulated,
|
|
@@ -1074,6 +1630,8 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
1074
1630
|
session,
|
|
1075
1631
|
toolCalls,
|
|
1076
1632
|
isRouteComplete: false,
|
|
1633
|
+
executedSteps: chunk.done ? [{ id: nextStep.id, routeId: selectedRoute.id }] : undefined,
|
|
1634
|
+
stoppedReason: chunk.done ? 'needs_input' : undefined,
|
|
1077
1635
|
metadata: chunk.metadata,
|
|
1078
1636
|
structured: chunk.structured,
|
|
1079
1637
|
};
|
|
@@ -1653,6 +2211,10 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
1653
2211
|
await this.finalizeSession(session, context);
|
|
1654
2212
|
}
|
|
1655
2213
|
|
|
2214
|
+
// Response structure completeness (Requirement 8.1, 8.2, 8.3)
|
|
2215
|
+
// - executedSteps: empty for route completion (no new steps executed)
|
|
2216
|
+
// - stoppedReason: 'route_complete' for completed routes
|
|
2217
|
+
// - session.currentStep: set to END_ROUTE
|
|
1656
2218
|
yield {
|
|
1657
2219
|
delta: chunk.delta,
|
|
1658
2220
|
accumulated: chunk.accumulated,
|
|
@@ -1660,6 +2222,8 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
1660
2222
|
session,
|
|
1661
2223
|
toolCalls: undefined,
|
|
1662
2224
|
isRouteComplete: true,
|
|
2225
|
+
executedSteps: chunk.done ? [] : undefined,
|
|
2226
|
+
stoppedReason: chunk.done ? 'route_complete' : undefined,
|
|
1663
2227
|
metadata: chunk.metadata,
|
|
1664
2228
|
structured: chunk.structured,
|
|
1665
2229
|
};
|
|
@@ -1754,6 +2318,10 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
1754
2318
|
await this.finalizeSession(session, context);
|
|
1755
2319
|
}
|
|
1756
2320
|
|
|
2321
|
+
// Response structure completeness (Requirement 8.1, 8.2, 8.3)
|
|
2322
|
+
// - executedSteps: empty for fallback (no route/step execution)
|
|
2323
|
+
// - stoppedReason: undefined for fallback (no route context)
|
|
2324
|
+
// - session.currentStep: unchanged (no step progression)
|
|
1757
2325
|
yield {
|
|
1758
2326
|
delta: chunk.delta,
|
|
1759
2327
|
accumulated: chunk.accumulated,
|
|
@@ -1761,6 +2329,8 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
1761
2329
|
session,
|
|
1762
2330
|
toolCalls: undefined,
|
|
1763
2331
|
isRouteComplete: false,
|
|
2332
|
+
executedSteps: chunk.done ? [] : undefined,
|
|
2333
|
+
stoppedReason: undefined,
|
|
1764
2334
|
metadata: chunk.metadata,
|
|
1765
2335
|
structured: chunk.structured,
|
|
1766
2336
|
};
|