@falai/agent 2.2.3 → 2.4.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/README.md +2 -2
- package/dist/adapters/MemoryAdapter.d.ts.map +1 -1
- package/dist/adapters/MemoryAdapter.js +10 -1
- package/dist/adapters/MemoryAdapter.js.map +1 -1
- package/dist/adapters/MongoAdapter.d.ts.map +1 -1
- package/dist/adapters/MongoAdapter.js +33 -2
- package/dist/adapters/MongoAdapter.js.map +1 -1
- package/dist/adapters/OpenSearchAdapter.d.ts.map +1 -1
- package/dist/adapters/OpenSearchAdapter.js +15 -1
- package/dist/adapters/OpenSearchAdapter.js.map +1 -1
- package/dist/adapters/PostgreSQLAdapter.d.ts.map +1 -1
- package/dist/adapters/PostgreSQLAdapter.js +35 -5
- package/dist/adapters/PostgreSQLAdapter.js.map +1 -1
- package/dist/adapters/PrismaAdapter.d.ts.map +1 -1
- package/dist/adapters/PrismaAdapter.js +58 -15
- package/dist/adapters/PrismaAdapter.js.map +1 -1
- package/dist/adapters/RedisAdapter.d.ts.map +1 -1
- package/dist/adapters/RedisAdapter.js +10 -1
- package/dist/adapters/RedisAdapter.js.map +1 -1
- package/dist/adapters/SQLiteAdapter.d.ts.map +1 -1
- package/dist/adapters/SQLiteAdapter.js +38 -6
- package/dist/adapters/SQLiteAdapter.js.map +1 -1
- package/dist/cjs/adapters/MemoryAdapter.d.ts.map +1 -1
- package/dist/cjs/adapters/MemoryAdapter.js +10 -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 +33 -2
- 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 +15 -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 +35 -5
- 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 +58 -15
- 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 +10 -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 +38 -6
- package/dist/cjs/adapters/SQLiteAdapter.js.map +1 -1
- package/dist/cjs/core/Agent.d.ts +28 -18
- package/dist/cjs/core/Agent.d.ts.map +1 -1
- package/dist/cjs/core/Agent.js +58 -68
- package/dist/cjs/core/Agent.js.map +1 -1
- package/dist/cjs/core/AutoChainExecutor.d.ts.map +1 -1
- package/dist/cjs/core/AutoChainExecutor.js +14 -20
- package/dist/cjs/core/AutoChainExecutor.js.map +1 -1
- package/dist/cjs/core/BranchEvaluator.d.ts +4 -3
- package/dist/cjs/core/BranchEvaluator.d.ts.map +1 -1
- package/dist/cjs/core/BranchEvaluator.js +18 -23
- package/dist/cjs/core/BranchEvaluator.js.map +1 -1
- package/dist/cjs/core/Flow.d.ts +2 -1
- package/dist/cjs/core/Flow.d.ts.map +1 -1
- package/dist/cjs/core/Flow.js +10 -3
- package/dist/cjs/core/Flow.js.map +1 -1
- package/dist/cjs/core/FlowRouter.d.ts +1 -0
- package/dist/cjs/core/FlowRouter.d.ts.map +1 -1
- package/dist/cjs/core/FlowRouter.js +28 -5
- package/dist/cjs/core/FlowRouter.js.map +1 -1
- package/dist/cjs/core/PersistenceManager.d.ts +3 -0
- package/dist/cjs/core/PersistenceManager.d.ts.map +1 -1
- package/dist/cjs/core/PersistenceManager.js +57 -5
- package/dist/cjs/core/PersistenceManager.js.map +1 -1
- package/dist/cjs/core/PromptComposer.d.ts.map +1 -1
- package/dist/cjs/core/PromptComposer.js +24 -10
- package/dist/cjs/core/PromptComposer.js.map +1 -1
- package/dist/cjs/core/ResponseGenerationError.d.ts +30 -0
- package/dist/cjs/core/ResponseGenerationError.d.ts.map +1 -0
- package/dist/cjs/core/ResponseGenerationError.js +37 -0
- package/dist/cjs/core/ResponseGenerationError.js.map +1 -0
- package/dist/cjs/core/ResponseModal.d.ts +43 -96
- package/dist/cjs/core/ResponseModal.d.ts.map +1 -1
- package/dist/cjs/core/ResponseModal.js +184 -1183
- package/dist/cjs/core/ResponseModal.js.map +1 -1
- package/dist/cjs/core/ResponsePipeline.d.ts +58 -152
- package/dist/cjs/core/ResponsePipeline.d.ts.map +1 -1
- package/dist/cjs/core/ResponsePipeline.js +408 -457
- package/dist/cjs/core/ResponsePipeline.js.map +1 -1
- package/dist/cjs/core/SessionFinalizer.d.ts +34 -0
- package/dist/cjs/core/SessionFinalizer.d.ts.map +1 -0
- package/dist/cjs/core/SessionFinalizer.js +61 -0
- package/dist/cjs/core/SessionFinalizer.js.map +1 -0
- package/dist/cjs/core/SessionManager.d.ts +1 -1
- package/dist/cjs/core/SessionManager.d.ts.map +1 -1
- package/dist/cjs/core/SessionManager.js +18 -7
- package/dist/cjs/core/SessionManager.js.map +1 -1
- package/dist/cjs/core/SignalCoordinator.d.ts +103 -0
- package/dist/cjs/core/SignalCoordinator.d.ts.map +1 -0
- package/dist/cjs/core/SignalCoordinator.js +207 -0
- package/dist/cjs/core/SignalCoordinator.js.map +1 -0
- package/dist/cjs/core/SignalEvaluator.d.ts +2 -2
- package/dist/cjs/core/SignalEvaluator.d.ts.map +1 -1
- package/dist/cjs/core/SignalEvaluator.js +13 -27
- package/dist/cjs/core/SignalEvaluator.js.map +1 -1
- package/dist/cjs/core/SignalProcessor.d.ts.map +1 -1
- package/dist/cjs/core/SignalProcessor.js +1 -3
- package/dist/cjs/core/SignalProcessor.js.map +1 -1
- package/dist/cjs/core/Step.d.ts +3 -1
- package/dist/cjs/core/Step.d.ts.map +1 -1
- package/dist/cjs/core/Step.js +10 -3
- package/dist/cjs/core/Step.js.map +1 -1
- package/dist/cjs/core/StepLifecycle.d.ts +33 -0
- package/dist/cjs/core/StepLifecycle.d.ts.map +1 -0
- package/dist/cjs/core/StepLifecycle.js +97 -0
- package/dist/cjs/core/StepLifecycle.js.map +1 -0
- package/dist/cjs/core/ToolLoopExecutor.d.ts +104 -0
- package/dist/cjs/core/ToolLoopExecutor.d.ts.map +1 -0
- package/dist/cjs/core/ToolLoopExecutor.js +391 -0
- package/dist/cjs/core/ToolLoopExecutor.js.map +1 -0
- package/dist/cjs/core/ToolManager.d.ts +1 -1
- package/dist/cjs/core/ToolManager.d.ts.map +1 -1
- package/dist/cjs/core/ToolManager.js.map +1 -1
- package/dist/cjs/index.d.ts +4 -5
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +7 -6
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/providers/AnthropicProvider.d.ts +2 -0
- package/dist/cjs/providers/AnthropicProvider.d.ts.map +1 -1
- package/dist/cjs/providers/AnthropicProvider.js +28 -56
- package/dist/cjs/providers/AnthropicProvider.js.map +1 -1
- package/dist/cjs/providers/DeepSeekProvider.d.ts +25 -21
- package/dist/cjs/providers/DeepSeekProvider.d.ts.map +1 -1
- package/dist/cjs/providers/DeepSeekProvider.js +48 -407
- package/dist/cjs/providers/DeepSeekProvider.js.map +1 -1
- package/dist/cjs/providers/GeminiProvider.d.ts +2 -0
- package/dist/cjs/providers/GeminiProvider.d.ts.map +1 -1
- package/dist/cjs/providers/GeminiProvider.js +27 -56
- package/dist/cjs/providers/GeminiProvider.js.map +1 -1
- package/dist/cjs/providers/OpenAICompatibleProvider.d.ts +129 -0
- package/dist/cjs/providers/OpenAICompatibleProvider.d.ts.map +1 -0
- package/dist/cjs/providers/OpenAICompatibleProvider.js +485 -0
- package/dist/cjs/providers/OpenAICompatibleProvider.js.map +1 -0
- package/dist/cjs/providers/OpenAIProvider.d.ts +9 -28
- package/dist/cjs/providers/OpenAIProvider.d.ts.map +1 -1
- package/dist/cjs/providers/OpenAIProvider.js +23 -417
- package/dist/cjs/providers/OpenAIProvider.js.map +1 -1
- package/dist/cjs/providers/OpenRouterProvider.d.ts +10 -27
- package/dist/cjs/providers/OpenRouterProvider.d.ts.map +1 -1
- package/dist/cjs/providers/OpenRouterProvider.js +28 -417
- package/dist/cjs/providers/OpenRouterProvider.js.map +1 -1
- package/dist/cjs/providers/errorClassification.d.ts +61 -0
- package/dist/cjs/providers/errorClassification.d.ts.map +1 -0
- package/dist/cjs/providers/errorClassification.js +123 -0
- package/dist/cjs/providers/errorClassification.js.map +1 -0
- package/dist/cjs/providers/index.d.ts +4 -0
- package/dist/cjs/providers/index.d.ts.map +1 -1
- package/dist/cjs/providers/index.js +7 -1
- package/dist/cjs/providers/index.js.map +1 -1
- package/dist/cjs/types/agent.d.ts +2 -1
- package/dist/cjs/types/agent.d.ts.map +1 -1
- package/dist/cjs/types/ai.d.ts +20 -0
- package/dist/cjs/types/ai.d.ts.map +1 -1
- package/dist/cjs/types/errors.d.ts +33 -0
- package/dist/cjs/types/errors.d.ts.map +1 -1
- package/dist/cjs/types/errors.js +37 -1
- package/dist/cjs/types/errors.js.map +1 -1
- package/dist/cjs/types/flow.d.ts +10 -5
- package/dist/cjs/types/flow.d.ts.map +1 -1
- package/dist/cjs/types/history.d.ts +1 -1
- package/dist/cjs/types/history.d.ts.map +1 -1
- package/dist/cjs/types/index.d.ts +5 -4
- package/dist/cjs/types/index.d.ts.map +1 -1
- package/dist/cjs/types/index.js +3 -1
- package/dist/cjs/types/index.js.map +1 -1
- package/dist/cjs/types/persistence.d.ts +43 -2
- package/dist/cjs/types/persistence.d.ts.map +1 -1
- package/dist/cjs/types/session.d.ts +8 -0
- package/dist/cjs/types/session.d.ts.map +1 -1
- package/dist/cjs/types/signals.d.ts +21 -11
- package/dist/cjs/types/signals.d.ts.map +1 -1
- package/dist/cjs/types/template.d.ts +3 -1
- package/dist/cjs/types/template.d.ts.map +1 -1
- package/dist/cjs/types/tool.d.ts +4 -4
- package/dist/cjs/types/tool.d.ts.map +1 -1
- package/dist/cjs/utils/condition.d.ts +19 -0
- package/dist/cjs/utils/condition.d.ts.map +1 -1
- package/dist/cjs/utils/condition.js +86 -15
- package/dist/cjs/utils/condition.js.map +1 -1
- package/dist/cjs/utils/session.d.ts +1 -0
- package/dist/cjs/utils/session.d.ts.map +1 -1
- package/dist/cjs/utils/session.js +1 -0
- package/dist/cjs/utils/session.js.map +1 -1
- package/dist/core/Agent.d.ts +28 -18
- package/dist/core/Agent.d.ts.map +1 -1
- package/dist/core/Agent.js +58 -68
- package/dist/core/Agent.js.map +1 -1
- package/dist/core/AutoChainExecutor.d.ts.map +1 -1
- package/dist/core/AutoChainExecutor.js +14 -20
- package/dist/core/AutoChainExecutor.js.map +1 -1
- package/dist/core/BranchEvaluator.d.ts +4 -3
- package/dist/core/BranchEvaluator.d.ts.map +1 -1
- package/dist/core/BranchEvaluator.js +18 -23
- package/dist/core/BranchEvaluator.js.map +1 -1
- package/dist/core/Flow.d.ts +2 -1
- package/dist/core/Flow.d.ts.map +1 -1
- package/dist/core/Flow.js +10 -3
- package/dist/core/Flow.js.map +1 -1
- package/dist/core/FlowRouter.d.ts +1 -0
- package/dist/core/FlowRouter.d.ts.map +1 -1
- package/dist/core/FlowRouter.js +28 -5
- package/dist/core/FlowRouter.js.map +1 -1
- package/dist/core/PersistenceManager.d.ts +3 -0
- package/dist/core/PersistenceManager.d.ts.map +1 -1
- package/dist/core/PersistenceManager.js +58 -6
- package/dist/core/PersistenceManager.js.map +1 -1
- package/dist/core/PromptComposer.d.ts.map +1 -1
- package/dist/core/PromptComposer.js +24 -10
- package/dist/core/PromptComposer.js.map +1 -1
- package/dist/core/ResponseGenerationError.d.ts +30 -0
- package/dist/core/ResponseGenerationError.d.ts.map +1 -0
- package/dist/core/ResponseGenerationError.js +33 -0
- package/dist/core/ResponseGenerationError.js.map +1 -0
- package/dist/core/ResponseModal.d.ts +43 -96
- package/dist/core/ResponseModal.d.ts.map +1 -1
- package/dist/core/ResponseModal.js +177 -1175
- package/dist/core/ResponseModal.js.map +1 -1
- package/dist/core/ResponsePipeline.d.ts +58 -152
- package/dist/core/ResponsePipeline.d.ts.map +1 -1
- package/dist/core/ResponsePipeline.js +409 -458
- package/dist/core/ResponsePipeline.js.map +1 -1
- package/dist/core/SessionFinalizer.d.ts +34 -0
- package/dist/core/SessionFinalizer.d.ts.map +1 -0
- package/dist/core/SessionFinalizer.js +57 -0
- package/dist/core/SessionFinalizer.js.map +1 -0
- package/dist/core/SessionManager.d.ts +1 -1
- package/dist/core/SessionManager.d.ts.map +1 -1
- package/dist/core/SessionManager.js +18 -7
- package/dist/core/SessionManager.js.map +1 -1
- package/dist/core/SignalCoordinator.d.ts +103 -0
- package/dist/core/SignalCoordinator.d.ts.map +1 -0
- package/dist/core/SignalCoordinator.js +203 -0
- package/dist/core/SignalCoordinator.js.map +1 -0
- package/dist/core/SignalEvaluator.d.ts +2 -2
- package/dist/core/SignalEvaluator.d.ts.map +1 -1
- package/dist/core/SignalEvaluator.js +13 -27
- package/dist/core/SignalEvaluator.js.map +1 -1
- package/dist/core/SignalProcessor.d.ts.map +1 -1
- package/dist/core/SignalProcessor.js +1 -3
- package/dist/core/SignalProcessor.js.map +1 -1
- package/dist/core/Step.d.ts +3 -1
- package/dist/core/Step.d.ts.map +1 -1
- package/dist/core/Step.js +10 -3
- package/dist/core/Step.js.map +1 -1
- package/dist/core/StepLifecycle.d.ts +33 -0
- package/dist/core/StepLifecycle.d.ts.map +1 -0
- package/dist/core/StepLifecycle.js +93 -0
- package/dist/core/StepLifecycle.js.map +1 -0
- package/dist/core/ToolLoopExecutor.d.ts +104 -0
- package/dist/core/ToolLoopExecutor.d.ts.map +1 -0
- package/dist/core/ToolLoopExecutor.js +387 -0
- package/dist/core/ToolLoopExecutor.js.map +1 -0
- package/dist/core/ToolManager.d.ts +1 -1
- package/dist/core/ToolManager.d.ts.map +1 -1
- package/dist/core/ToolManager.js.map +1 -1
- package/dist/index.d.ts +4 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/providers/AnthropicProvider.d.ts +2 -0
- package/dist/providers/AnthropicProvider.d.ts.map +1 -1
- package/dist/providers/AnthropicProvider.js +22 -50
- package/dist/providers/AnthropicProvider.js.map +1 -1
- package/dist/providers/DeepSeekProvider.d.ts +25 -21
- package/dist/providers/DeepSeekProvider.d.ts.map +1 -1
- package/dist/providers/DeepSeekProvider.js +49 -408
- package/dist/providers/DeepSeekProvider.js.map +1 -1
- package/dist/providers/GeminiProvider.d.ts +2 -0
- package/dist/providers/GeminiProvider.d.ts.map +1 -1
- package/dist/providers/GeminiProvider.js +21 -50
- package/dist/providers/GeminiProvider.js.map +1 -1
- package/dist/providers/OpenAICompatibleProvider.d.ts +129 -0
- package/dist/providers/OpenAICompatibleProvider.d.ts.map +1 -0
- package/dist/providers/OpenAICompatibleProvider.js +481 -0
- package/dist/providers/OpenAICompatibleProvider.js.map +1 -0
- package/dist/providers/OpenAIProvider.d.ts +9 -28
- package/dist/providers/OpenAIProvider.d.ts.map +1 -1
- package/dist/providers/OpenAIProvider.js +23 -417
- package/dist/providers/OpenAIProvider.js.map +1 -1
- package/dist/providers/OpenRouterProvider.d.ts +10 -27
- package/dist/providers/OpenRouterProvider.d.ts.map +1 -1
- package/dist/providers/OpenRouterProvider.js +28 -417
- package/dist/providers/OpenRouterProvider.js.map +1 -1
- package/dist/providers/errorClassification.d.ts +61 -0
- package/dist/providers/errorClassification.d.ts.map +1 -0
- package/dist/providers/errorClassification.js +116 -0
- package/dist/providers/errorClassification.js.map +1 -0
- package/dist/providers/index.d.ts +4 -0
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/index.js +2 -0
- package/dist/providers/index.js.map +1 -1
- package/dist/types/agent.d.ts +2 -1
- package/dist/types/agent.d.ts.map +1 -1
- package/dist/types/ai.d.ts +20 -0
- package/dist/types/ai.d.ts.map +1 -1
- package/dist/types/errors.d.ts +33 -0
- package/dist/types/errors.d.ts.map +1 -1
- package/dist/types/errors.js +34 -0
- package/dist/types/errors.js.map +1 -1
- package/dist/types/flow.d.ts +10 -5
- package/dist/types/flow.d.ts.map +1 -1
- package/dist/types/history.d.ts +1 -1
- package/dist/types/history.d.ts.map +1 -1
- package/dist/types/index.d.ts +5 -4
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +1 -1
- package/dist/types/index.js.map +1 -1
- package/dist/types/persistence.d.ts +43 -2
- package/dist/types/persistence.d.ts.map +1 -1
- package/dist/types/session.d.ts +8 -0
- package/dist/types/session.d.ts.map +1 -1
- package/dist/types/signals.d.ts +21 -11
- package/dist/types/signals.d.ts.map +1 -1
- package/dist/types/template.d.ts +3 -1
- package/dist/types/template.d.ts.map +1 -1
- package/dist/types/tool.d.ts +4 -4
- package/dist/types/tool.d.ts.map +1 -1
- package/dist/utils/condition.d.ts +19 -0
- package/dist/utils/condition.d.ts.map +1 -1
- package/dist/utils/condition.js +84 -15
- package/dist/utils/condition.js.map +1 -1
- package/dist/utils/session.d.ts +1 -0
- package/dist/utils/session.d.ts.map +1 -1
- package/dist/utils/session.js +1 -0
- package/dist/utils/session.js.map +1 -1
- package/docs/README.md +1 -1
- package/docs/guides/branching.md +1 -1
- package/docs/guides/compaction.md +12 -5
- package/docs/guides/conditions.md +15 -2
- package/docs/guides/error-handling.md +59 -9
- package/docs/guides/instructions.md +2 -2
- package/docs/guides/persistence.md +69 -2
- package/docs/guides/streaming.md +3 -1
- package/docs/migration/README.md +5 -1
- package/docs/migration/v2-3-to-v2-4.md +316 -0
- package/docs/reference/adapters.md +55 -7
- package/docs/reference/branches.md +2 -2
- package/docs/reference/create-agent.md +3 -3
- package/docs/reference/errors.md +66 -1
- package/docs/reference/flow.md +1 -1
- package/docs/reference/instruction.md +4 -4
- package/docs/reference/providers.md +100 -3
- package/docs/reference/signals.md +18 -8
- package/docs/reference/step.md +2 -2
- package/docs/reference/tool.md +3 -1
- package/docs/start/05-go-to-production.md +3 -0
- package/package.json +1 -1
- package/src/adapters/MemoryAdapter.ts +15 -1
- package/src/adapters/MongoAdapter.ts +41 -2
- package/src/adapters/OpenSearchAdapter.ts +24 -1
- package/src/adapters/PostgreSQLAdapter.ts +45 -5
- package/src/adapters/PrismaAdapter.ts +82 -16
- package/src/adapters/RedisAdapter.ts +19 -1
- package/src/adapters/SQLiteAdapter.ts +47 -5
- package/src/core/Agent.ts +70 -85
- package/src/core/AutoChainExecutor.ts +27 -57
- package/src/core/BranchEvaluator.ts +24 -30
- package/src/core/Flow.ts +10 -3
- package/src/core/FlowRouter.ts +36 -4
- package/src/core/PersistenceManager.ts +79 -10
- package/src/core/PromptComposer.ts +25 -12
- package/src/core/ResponseGenerationError.ts +56 -0
- package/src/core/ResponseModal.ts +241 -1465
- package/src/core/ResponsePipeline.ts +492 -662
- package/src/core/SessionFinalizer.ts +78 -0
- package/src/core/SessionManager.ts +21 -9
- package/src/core/SignalCoordinator.ts +263 -0
- package/src/core/SignalEvaluator.ts +14 -30
- package/src/core/SignalProcessor.ts +1 -4
- package/src/core/Step.ts +11 -3
- package/src/core/StepLifecycle.ts +139 -0
- package/src/core/ToolLoopExecutor.ts +492 -0
- package/src/core/ToolManager.ts +2 -1
- package/src/index.ts +7 -5
- package/src/providers/AnthropicProvider.ts +30 -72
- package/src/providers/DeepSeekProvider.ts +74 -586
- package/src/providers/GeminiProvider.ts +29 -70
- package/src/providers/OpenAICompatibleProvider.ts +738 -0
- package/src/providers/OpenAIProvider.ts +29 -602
- package/src/providers/OpenRouterProvider.ts +38 -596
- package/src/providers/errorClassification.ts +172 -0
- package/src/providers/index.ts +13 -0
- package/src/types/agent.ts +2 -1
- package/src/types/ai.ts +22 -0
- package/src/types/errors.ts +57 -0
- package/src/types/flow.ts +10 -5
- package/src/types/history.ts +1 -2
- package/src/types/index.ts +5 -1
- package/src/types/persistence.ts +50 -2
- package/src/types/session.ts +9 -0
- package/src/types/signals.ts +23 -11
- package/src/types/template.ts +3 -1
- package/src/types/tool.ts +10 -10
- package/src/utils/condition.ts +115 -18
- package/src/utils/session.ts +2 -0
|
@@ -6,31 +6,28 @@ import type {
|
|
|
6
6
|
AgentOptions,
|
|
7
7
|
Event,
|
|
8
8
|
SessionState,
|
|
9
|
-
AgentStructuredResponse,
|
|
10
|
-
Tool,
|
|
11
9
|
Directive,
|
|
10
|
+
StructuredSchema,
|
|
12
11
|
} from "../types";
|
|
13
12
|
import type { SignalFiring } from "../types/signals";
|
|
14
|
-
import type { SignalProcessor } from "./SignalProcessor";
|
|
15
|
-
import type { HistoryItem } from "../types/history";
|
|
16
13
|
import {
|
|
17
14
|
createSession,
|
|
18
15
|
enterStep,
|
|
19
16
|
mergeCollected,
|
|
20
|
-
completeCurrentFlow,
|
|
21
17
|
logger,
|
|
22
18
|
historyToEvents,
|
|
23
|
-
|
|
19
|
+
eventsToHistory,
|
|
20
|
+
getLastMessageFromHistory,
|
|
24
21
|
} from "../utils";
|
|
25
22
|
import { enterFlow } from "../utils/session";
|
|
26
23
|
import { createTemplateContext } from "../utils/template";
|
|
27
24
|
import { Flow } from "./Flow";
|
|
28
25
|
import { Step, FlowConfigurationError } from "../core/Step";
|
|
29
26
|
import { FlowRouter } from "./FlowRouter";
|
|
30
|
-
import type { ToolManager } from "../core/ToolManager";
|
|
31
27
|
import { evaluateBranches, createAiConditionEvaluator } from "./BranchEvaluator";
|
|
32
28
|
import { DirectiveChainTracker } from "./DirectiveChainTracker";
|
|
33
|
-
import {
|
|
29
|
+
import { ResponseGenerationError } from "./ResponseGenerationError";
|
|
30
|
+
import type { SignalCoordinator } from "./SignalCoordinator";
|
|
34
31
|
|
|
35
32
|
/**
|
|
36
33
|
* Position fields on a Directive that represent a navigation decision.
|
|
@@ -57,6 +54,8 @@ export function hasDirectivePositionField<TContext = unknown, TData = unknown>(
|
|
|
57
54
|
export interface ResponsePreparationResult<TContext, TData = unknown> {
|
|
58
55
|
effectiveContext: TContext;
|
|
59
56
|
session: SessionState<TData>;
|
|
57
|
+
/** Context returned by the beforeRespond hook, for the caller to sync back to the agent. */
|
|
58
|
+
contextAfterHook?: TContext;
|
|
60
59
|
}
|
|
61
60
|
|
|
62
61
|
export interface RoutingResult<TContext, TData = unknown> {
|
|
@@ -68,20 +67,6 @@ export interface RoutingResult<TContext, TData = unknown> {
|
|
|
68
67
|
completedFlows?: Flow<TContext, TData>[];
|
|
69
68
|
}
|
|
70
69
|
|
|
71
|
-
export interface ToolExecutionResult<TData = unknown> {
|
|
72
|
-
session: SessionState<TData>;
|
|
73
|
-
toolCalls:
|
|
74
|
-
| Array<{ toolName: string; arguments: Record<string, unknown> }>
|
|
75
|
-
| undefined;
|
|
76
|
-
/** Map of tool name to serialized result data for inclusion in conversation history */
|
|
77
|
-
toolResults?: Map<string, string>;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export interface DataCollectionResult<TData = unknown> {
|
|
81
|
-
session: SessionState<TData>;
|
|
82
|
-
collectedData?: Partial<TData>;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
70
|
/**
|
|
86
71
|
* Shared response processing logic between respond() and respondStream() methods
|
|
87
72
|
*/
|
|
@@ -96,20 +81,10 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
|
|
|
96
81
|
constructor(
|
|
97
82
|
private readonly options: AgentOptions<TContext, TData>,
|
|
98
83
|
private readonly getFlows: () => Flow<TContext, TData>[],
|
|
99
|
-
private readonly tools: Tool<TContext, TData>[],
|
|
100
84
|
private readonly flowRouter: FlowRouter<TContext, TData>,
|
|
101
|
-
private readonly
|
|
102
|
-
|
|
103
|
-
) =>
|
|
104
|
-
private readonly updateData: (
|
|
105
|
-
session: SessionState<TData>,
|
|
106
|
-
dataUpdate: Partial<TData>
|
|
107
|
-
) => Promise<SessionState<TData>>,
|
|
108
|
-
private readonly updateCollectedData?: (
|
|
109
|
-
updates: Partial<TData>
|
|
110
|
-
) => Promise<void>,
|
|
111
|
-
private readonly toolManager?: ToolManager<TContext, TData>,
|
|
112
|
-
private readonly signalProcessor?: SignalProcessor<TContext, TData>
|
|
85
|
+
private readonly signalCoordinator: SignalCoordinator<TContext, TData>,
|
|
86
|
+
private readonly updateCollectedData: (updates: Partial<TData>) => Promise<void>,
|
|
87
|
+
private readonly getSchema: () => StructuredSchema | undefined
|
|
113
88
|
) { }
|
|
114
89
|
|
|
115
90
|
/**
|
|
@@ -132,101 +107,27 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
|
|
|
132
107
|
return this._chainTracker;
|
|
133
108
|
}
|
|
134
109
|
|
|
135
|
-
/**
|
|
136
|
-
* Create a fresh DirectiveBus for a new turn.
|
|
137
|
-
* The bus collects directives from hooks, tools, and branches during the turn
|
|
138
|
-
* and merges them at phase boundaries via Algorithm 4.
|
|
139
|
-
*/
|
|
140
|
-
createDirectiveBus(): DirectiveBus<TContext, TData> {
|
|
141
|
-
return new DirectiveBus<TContext, TData>();
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// ──────────────────────────────────────────────────────────────────────────
|
|
145
|
-
// Signal pipeline phases
|
|
146
|
-
// ──────────────────────────────────────────────────────────────────────────
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* PRE-SIGNAL PHASE — Evaluates pre/both signals in parallel with routing.
|
|
150
|
-
*
|
|
151
|
-
* Delegates to `signalProcessor.runPreSignalPhase(...)` when configured.
|
|
152
|
-
* When no signal processor is present (zero-cost path), returns a stable
|
|
153
|
-
* empty shape so callers don't need to branch.
|
|
154
|
-
*
|
|
155
|
-
* @requirements 2.1, 2.3, 8.6, 13.3
|
|
156
|
-
*/
|
|
157
|
-
async runPreSignalPhase(
|
|
158
|
-
session: SessionState<TData>,
|
|
159
|
-
context: TContext,
|
|
160
|
-
history: Event[],
|
|
161
|
-
): Promise<{
|
|
162
|
-
firings: SignalFiring<TContext, TData>[];
|
|
163
|
-
updatedSession: SessionState<TData>;
|
|
164
|
-
mergedDirective: Directive<TContext, TData> | undefined;
|
|
165
|
-
}> {
|
|
166
|
-
if (!this.signalProcessor) {
|
|
167
|
-
return { firings: [], updatedSession: session, mergedDirective: undefined };
|
|
168
|
-
}
|
|
169
|
-
const result = await this.signalProcessor.runPreSignalPhase({ session, history, context });
|
|
170
|
-
return {
|
|
171
|
-
firings: result.firings,
|
|
172
|
-
updatedSession: result.updatedSession,
|
|
173
|
-
mergedDirective: result.mergedDirective,
|
|
174
|
-
};
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* POST-SIGNAL PHASE — Evaluates post/both signals after finalize/onComplete.
|
|
179
|
-
*
|
|
180
|
-
* Delegates to `signalProcessor.runPostSignalPhase(...)` when configured.
|
|
181
|
-
* When no signal processor is present, returns a stable empty shape.
|
|
182
|
-
*
|
|
183
|
-
* Post-phase signals see the complete turn result: assistant message in history,
|
|
184
|
-
* collected data, tool results. Position directives from this phase set
|
|
185
|
-
* `session.pendingDirective` (no mid-turn re-entry per D6 decision).
|
|
186
|
-
*
|
|
187
|
-
* Pre-LLM-only fields (`appendPrompt`, `injectTools`, `halt`) are already
|
|
188
|
-
* dropped inside `runPostSignalPhase` per Phase 4.5 — this seam does NOT
|
|
189
|
-
* re-introduce them.
|
|
190
|
-
*
|
|
191
|
-
* @requirements 9.1, 9.2, 9.3, 9.4
|
|
192
|
-
*/
|
|
193
|
-
async runPostSignalPhase(
|
|
194
|
-
session: SessionState<TData>,
|
|
195
|
-
context: TContext,
|
|
196
|
-
history: Event[],
|
|
197
|
-
): Promise<{
|
|
198
|
-
firings: SignalFiring<TContext, TData>[];
|
|
199
|
-
updatedSession: SessionState<TData>;
|
|
200
|
-
mergedDirective: Directive<TContext, TData> | undefined;
|
|
201
|
-
}> {
|
|
202
|
-
if (!this.signalProcessor) {
|
|
203
|
-
return { firings: [], updatedSession: session, mergedDirective: undefined };
|
|
204
|
-
}
|
|
205
|
-
const result = await this.signalProcessor.runPostSignalPhase({ session, history, context });
|
|
206
|
-
return {
|
|
207
|
-
firings: result.firings,
|
|
208
|
-
updatedSession: result.updatedSession,
|
|
209
|
-
mergedDirective: result.mergedDirective,
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
|
-
|
|
213
110
|
/**
|
|
214
111
|
* Prepare context and session for response generation
|
|
215
112
|
*/
|
|
216
113
|
async prepareResponseContext(params: {
|
|
217
114
|
contextOverride?: Partial<TContext>;
|
|
218
115
|
session?: SessionState<TData>;
|
|
116
|
+
/** The agent's current context, resolved by the caller (contextProvider already applied). */
|
|
117
|
+
currentContext?: TContext;
|
|
118
|
+
/** The agent's live session, used when no explicit session is passed. */
|
|
119
|
+
currentSession?: SessionState<TData>;
|
|
219
120
|
}): Promise<ResponsePreparationResult<TContext, TData>> {
|
|
220
121
|
const { contextOverride, session } = params;
|
|
221
122
|
|
|
222
|
-
|
|
223
|
-
let
|
|
123
|
+
let currentContext = params.currentContext;
|
|
124
|
+
let contextAfterHook: TContext | undefined;
|
|
224
125
|
|
|
225
126
|
// Call beforeRespond hook if configured
|
|
226
127
|
if (this.options.hooks?.beforeRespond && currentContext !== undefined) {
|
|
227
128
|
currentContext = await this.options.hooks.beforeRespond(currentContext);
|
|
228
|
-
//
|
|
229
|
-
|
|
129
|
+
// Surface the hook result so the caller can sync it back to the agent
|
|
130
|
+
contextAfterHook = currentContext;
|
|
230
131
|
}
|
|
231
132
|
|
|
232
133
|
// Merge context with override
|
|
@@ -235,12 +136,13 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
|
|
|
235
136
|
...(contextOverride as Record<string, unknown>),
|
|
236
137
|
} as TContext;
|
|
237
138
|
|
|
238
|
-
// Initialize or get session (use
|
|
239
|
-
const targetSession = session ||
|
|
139
|
+
// Initialize or get session (use the live session if available)
|
|
140
|
+
const targetSession = session || params.currentSession || createSession<TData>();
|
|
240
141
|
|
|
241
142
|
return {
|
|
242
143
|
effectiveContext,
|
|
243
144
|
session: targetSession,
|
|
145
|
+
contextAfterHook,
|
|
244
146
|
};
|
|
245
147
|
}
|
|
246
148
|
|
|
@@ -270,7 +172,7 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
|
|
|
270
172
|
if (targetSession.pendingDirective) {
|
|
271
173
|
const directive = targetSession.pendingDirective;
|
|
272
174
|
logger.debug(
|
|
273
|
-
`[
|
|
175
|
+
`[ResponsePipeline] Applying pending directive at start of turn`
|
|
274
176
|
);
|
|
275
177
|
|
|
276
178
|
// Track directive chain depth (Requirement 22.1)
|
|
@@ -306,7 +208,7 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
|
|
|
306
208
|
|
|
307
209
|
if (targetFlow) {
|
|
308
210
|
logger.debug(
|
|
309
|
-
`[
|
|
211
|
+
`[ResponsePipeline] Pending directive goTo → flow: ${targetFlow.title}`
|
|
310
212
|
);
|
|
311
213
|
targetSession = enterFlow(
|
|
312
214
|
targetSession,
|
|
@@ -436,7 +338,7 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
|
|
|
436
338
|
// Log if flow is complete
|
|
437
339
|
if (isFlowComplete) {
|
|
438
340
|
logger.debug(
|
|
439
|
-
`[
|
|
341
|
+
`[ResponsePipeline] Flow complete: all required data collected or last step reached`
|
|
440
342
|
);
|
|
441
343
|
}
|
|
442
344
|
}
|
|
@@ -459,10 +361,12 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
|
|
|
459
361
|
selectedStep: Step<TContext, TData> | undefined;
|
|
460
362
|
session: SessionState<TData>;
|
|
461
363
|
isFlowComplete: boolean;
|
|
364
|
+
/** The turn's effective context, passed explicitly (no stored pipeline state). */
|
|
365
|
+
context: TContext;
|
|
462
366
|
/** Merged directive from the directive bus (pre-LLM + post-LLM phases). */
|
|
463
367
|
busDirective?: Directive<TContext, TData>;
|
|
464
368
|
}): Promise<{ nextStep: Step<TContext, TData> | undefined; session: SessionState<TData>; flowChanged?: Flow<TContext, TData> }> {
|
|
465
|
-
const { selectedFlow, selectedStep, session, isFlowComplete, busDirective } = params;
|
|
369
|
+
const { selectedFlow, selectedStep, session, isFlowComplete, context, busDirective } = params;
|
|
466
370
|
|
|
467
371
|
if (!selectedFlow) {
|
|
468
372
|
return { nextStep: undefined, session };
|
|
@@ -475,7 +379,7 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
|
|
|
475
379
|
// branches or linear/AI selection — the caller applies the bus directive.
|
|
476
380
|
if (hasDirectivePositionField(busDirective)) {
|
|
477
381
|
logger.debug(
|
|
478
|
-
`[
|
|
382
|
+
`[ResponsePipeline] Directive bus winner has position field — skipping branch evaluation and linear/AI selection`,
|
|
479
383
|
);
|
|
480
384
|
return { nextStep: undefined, session };
|
|
481
385
|
}
|
|
@@ -489,9 +393,8 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
|
|
|
489
393
|
: undefined;
|
|
490
394
|
|
|
491
395
|
if (currentStep?.branches && currentStep.branches.length > 0) {
|
|
492
|
-
const contextToUse = this.getStoredContext();
|
|
493
396
|
const branchResult = await this.evaluateStepBranches(
|
|
494
|
-
currentStep, selectedFlow, session,
|
|
397
|
+
currentStep, selectedFlow, session, context
|
|
495
398
|
);
|
|
496
399
|
if (branchResult) {
|
|
497
400
|
return branchResult;
|
|
@@ -515,19 +418,17 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
|
|
|
515
418
|
? selectedFlow.getStep(session.currentStep.id)
|
|
516
419
|
: undefined;
|
|
517
420
|
|
|
518
|
-
const contextToUse = this.getStoredContext();
|
|
519
|
-
|
|
520
421
|
// Get candidate steps based on current position in the flow
|
|
521
422
|
const candidates = await this.flowRouter.getCandidateStepsWithConditions(
|
|
522
423
|
selectedFlow,
|
|
523
424
|
currentStep, // Pass current step instead of undefined to maintain progression
|
|
524
|
-
createTemplateContext({ data: session.data, session, context
|
|
425
|
+
createTemplateContext({ data: session.data, session, context })
|
|
525
426
|
);
|
|
526
427
|
|
|
527
428
|
if (candidates.length > 0) {
|
|
528
429
|
nextStep = candidates[0].step;
|
|
529
430
|
logger.debug(
|
|
530
|
-
`[
|
|
431
|
+
`[ResponsePipeline] Using first valid step: ${nextStep.id}${currentStep ? ' (progressing from ' + currentStep.id + ')' : ' for new flow'}`
|
|
531
432
|
);
|
|
532
433
|
} else {
|
|
533
434
|
// Fallback to initial step even if it should be skipped
|
|
@@ -546,7 +447,7 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
|
|
|
546
447
|
);
|
|
547
448
|
if (missingRequires.length > 0) {
|
|
548
449
|
logger.debug(
|
|
549
|
-
`[
|
|
450
|
+
`[ResponsePipeline] Cannot enter step "${nextStep.id}": missing required fields [${missingRequires.join(', ')}]. Staying at current step.`
|
|
550
451
|
);
|
|
551
452
|
// Stay at current step - don't enter the next one
|
|
552
453
|
const currentStepId = session.currentStep?.id;
|
|
@@ -566,7 +467,7 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
|
|
|
566
467
|
nextStep.id,
|
|
567
468
|
nextStep.description
|
|
568
469
|
);
|
|
569
|
-
logger.debug(`[
|
|
470
|
+
logger.debug(`[ResponsePipeline] Entered step: ${nextStep.id}`);
|
|
570
471
|
|
|
571
472
|
return { nextStep, session: updatedSession };
|
|
572
473
|
}
|
|
@@ -626,7 +527,7 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
|
|
|
626
527
|
const localStep = selectedFlow.getStep(result);
|
|
627
528
|
if (localStep) {
|
|
628
529
|
const updatedSession = enterStep(session, localStep.id, localStep.description);
|
|
629
|
-
logger.debug(`[
|
|
530
|
+
logger.debug(`[ResponsePipeline] Branch resolved to local step: ${localStep.id}`);
|
|
630
531
|
return { nextStep: localStep, session: updatedSession };
|
|
631
532
|
}
|
|
632
533
|
|
|
@@ -635,7 +536,7 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
|
|
|
635
536
|
const targetFlow = flows.find(f => f.id === result || f.title === result);
|
|
636
537
|
if (targetFlow) {
|
|
637
538
|
// Treat as applyDirective({ goTo: result }) — enter the target flow
|
|
638
|
-
logger.debug(`[
|
|
539
|
+
logger.debug(`[ResponsePipeline] Branch resolved to flow: ${targetFlow.title}`);
|
|
639
540
|
const updatedSession = enterFlow(session, targetFlow.id, targetFlow.title);
|
|
640
541
|
return { nextStep: undefined, session: updatedSession, flowChanged: targetFlow };
|
|
641
542
|
}
|
|
@@ -685,7 +586,7 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
|
|
|
685
586
|
updatedSession = enterStep(updatedSession, stepTarget);
|
|
686
587
|
// Try to resolve the target step instance for the caller
|
|
687
588
|
const targetStepInstance = targetFlow.getStep(stepTarget);
|
|
688
|
-
logger.debug(`[
|
|
589
|
+
logger.debug(`[ResponsePipeline] Branch directive goToStep → ${flowTarget}.${stepTarget}`);
|
|
689
590
|
return { nextStep: targetStepInstance || undefined, session: updatedSession, flowChanged: targetFlow };
|
|
690
591
|
}
|
|
691
592
|
throw new FlowConfigurationError(
|
|
@@ -698,7 +599,7 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
|
|
|
698
599
|
const targetStep = selectedFlow.getStep(stepTarget);
|
|
699
600
|
if (targetStep) {
|
|
700
601
|
updatedSession = enterStep(updatedSession, targetStep.id, targetStep.description);
|
|
701
|
-
logger.debug(`[
|
|
602
|
+
logger.debug(`[ResponsePipeline] Branch directive goToStep → ${targetStep.id}`);
|
|
702
603
|
return { nextStep: targetStep, session: updatedSession };
|
|
703
604
|
}
|
|
704
605
|
throw new FlowConfigurationError(
|
|
@@ -723,7 +624,7 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
|
|
|
723
624
|
updatedSession = enterStep(updatedSession, directive.goTo.step);
|
|
724
625
|
}
|
|
725
626
|
|
|
726
|
-
logger.debug(`[
|
|
627
|
+
logger.debug(`[ResponsePipeline] Branch directive goTo → ${targetFlow.title}`);
|
|
727
628
|
return { nextStep: undefined, session: updatedSession, flowChanged: targetFlow };
|
|
728
629
|
}
|
|
729
630
|
throw new FlowConfigurationError(
|
|
@@ -734,12 +635,12 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
|
|
|
734
635
|
}
|
|
735
636
|
|
|
736
637
|
if (directive.complete) {
|
|
737
|
-
logger.debug(`[
|
|
638
|
+
logger.debug(`[ResponsePipeline] Branch directive complete`);
|
|
738
639
|
return { nextStep: undefined, session: updatedSession };
|
|
739
640
|
}
|
|
740
641
|
|
|
741
642
|
if (directive.abort) {
|
|
742
|
-
logger.debug(`[
|
|
643
|
+
logger.debug(`[ResponsePipeline] Branch directive abort`);
|
|
743
644
|
return { nextStep: undefined, session: updatedSession };
|
|
744
645
|
}
|
|
745
646
|
|
|
@@ -757,7 +658,7 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
|
|
|
757
658
|
// Reset to initial step
|
|
758
659
|
const initialStep = selectedFlow.initialStep;
|
|
759
660
|
updatedSession = enterStep(updatedSession, initialStep.id, initialStep.description);
|
|
760
|
-
logger.debug(`[
|
|
661
|
+
logger.debug(`[ResponsePipeline] Branch directive reset → ${initialStep.id}`);
|
|
761
662
|
return { nextStep: initialStep, session: updatedSession };
|
|
762
663
|
}
|
|
763
664
|
|
|
@@ -766,612 +667,541 @@ export class ResponsePipeline<TContext = unknown, TData = unknown> {
|
|
|
766
667
|
return { nextStep: undefined, session: updatedSession };
|
|
767
668
|
}
|
|
768
669
|
|
|
670
|
+
|
|
769
671
|
/**
|
|
770
|
-
*
|
|
672
|
+
* TURN ROUTING + STEP SELECTION — the single entry point for deciding which
|
|
673
|
+
* flow and step a turn renders. Runs the routing-skip optimization, the
|
|
674
|
+
* pre-signal phase (parallel with routing when a processor is configured),
|
|
675
|
+
* pre-extraction, and next-step determination.
|
|
771
676
|
*/
|
|
772
|
-
async
|
|
773
|
-
|
|
774
|
-
|
|
677
|
+
async routeAndSelectStep(params: {
|
|
678
|
+
session: SessionState<TData>;
|
|
679
|
+
history: Event[]; // Use Event[] for internal processing
|
|
775
680
|
context: TContext;
|
|
681
|
+
signal?: AbortSignal;
|
|
682
|
+
}): Promise<{
|
|
683
|
+
selectedFlow?: Flow<TContext, TData>;
|
|
684
|
+
selectedStep?: Step<TContext, TData>;
|
|
685
|
+
responseDirectives?: string[];
|
|
776
686
|
session: SessionState<TData>;
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
}
|
|
823
|
-
|
|
824
|
-
// Fallback: execute tool directly if ToolManager not available
|
|
825
|
-
throw new Error(`ToolManager not available for tool execution: ${toolCall.toolName}`);
|
|
826
|
-
}
|
|
827
|
-
|
|
828
|
-
executedToolCalls.push(toolCall);
|
|
829
|
-
|
|
830
|
-
// Store the actual tool result data for history
|
|
831
|
-
toolResults.set(toolCall.toolName, serializeToolResult(result));
|
|
832
|
-
|
|
833
|
-
// Update context with tool results
|
|
834
|
-
if (result.contextUpdate) {
|
|
835
|
-
await this.updateContext(result.contextUpdate as Partial<TContext>);
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
// Update collected data with tool results
|
|
839
|
-
if (result.dataUpdate) {
|
|
840
|
-
updatedSession = await this.updateData(
|
|
841
|
-
updatedSession,
|
|
842
|
-
result.dataUpdate as Partial<TData>
|
|
843
|
-
);
|
|
844
|
-
logger.debug(
|
|
845
|
-
`[ResponseHandler] ${isStreaming ? "Streaming " : ""
|
|
846
|
-
}Tool updated collected data:`,
|
|
847
|
-
result.dataUpdate
|
|
848
|
-
);
|
|
687
|
+
isFlowComplete: boolean;
|
|
688
|
+
/** Signal firings from the pre-phase (threaded through for response surface). */
|
|
689
|
+
signalFirings?: SignalFiring<TContext, TData>[];
|
|
690
|
+
/** Non-position signal directive for pre-LLM augmentation (appendPrompt, injectTools, etc). */
|
|
691
|
+
signalPreDirective?: Directive<TContext, TData>;
|
|
692
|
+
/** Pre-signal phase halted the turn. */
|
|
693
|
+
signalHalted?: boolean;
|
|
694
|
+
/** Reply text from the halt directive. */
|
|
695
|
+
signalHaltReply?: string;
|
|
696
|
+
}> {
|
|
697
|
+
try {
|
|
698
|
+
// Create a fresh chain tracker for this turn (Requirement 22.1)
|
|
699
|
+
this.createChainTracker();
|
|
700
|
+
|
|
701
|
+
// ROUTING SKIP OPTIMIZATION (Requirements 20.1, 20.2, 20.3):
|
|
702
|
+
// When the current step has collect fields AND pre-extraction populates at least
|
|
703
|
+
// one of those fields, skip FlowRouter.decideFlowAndStep for this turn.
|
|
704
|
+
const routingSkipResult = await this.attemptRoutingSkipForCollect(params);
|
|
705
|
+
if (routingSkipResult) {
|
|
706
|
+
// Even when routing is skipped, run pre-signal phase if processor is present
|
|
707
|
+
if (this.signalCoordinator.enabled) {
|
|
708
|
+
const signalResult = await this.signalCoordinator.runPrePhase(
|
|
709
|
+
params.session, params.context, params.history,
|
|
710
|
+
);
|
|
711
|
+
// If signal halts, override the routing skip result
|
|
712
|
+
if (signalResult.mergedDirective?.halt) {
|
|
713
|
+
return {
|
|
714
|
+
...routingSkipResult,
|
|
715
|
+
session: signalResult.updatedSession,
|
|
716
|
+
signalFirings: signalResult.firings,
|
|
717
|
+
signalHalted: true,
|
|
718
|
+
signalHaltReply: signalResult.mergedDirective.reply,
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
// If signal has position fields, override routing skip result
|
|
722
|
+
if (hasDirectivePositionField(signalResult.mergedDirective)) {
|
|
723
|
+
return this.signalCoordinator.applyPositionDirective(signalResult);
|
|
724
|
+
}
|
|
725
|
+
// Non-position directive: propagate for pre-LLM augmentation
|
|
726
|
+
return {
|
|
727
|
+
...routingSkipResult,
|
|
728
|
+
session: signalResult.updatedSession,
|
|
729
|
+
signalFirings: signalResult.firings,
|
|
730
|
+
signalPreDirective: signalResult.mergedDirective || undefined,
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
return routingSkipResult;
|
|
849
734
|
}
|
|
850
735
|
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
)
|
|
855
|
-
|
|
736
|
+
// ── PARALLEL PRE-SIGNAL PHASE + ROUTING (Algorithm 5) ────────────────
|
|
737
|
+
// When signalProcessor is present, run pre-signals in parallel with routing.
|
|
738
|
+
// When absent, call the router directly (zero overhead, preserve current behavior).
|
|
739
|
+
if (this.signalCoordinator.enabled) {
|
|
740
|
+
// Run pre-signal phase in parallel with routing (Requirement 8.1)
|
|
741
|
+
const [signalResult, routingResult] = await Promise.all([
|
|
742
|
+
this.signalCoordinator.runPrePhase(
|
|
743
|
+
params.session, params.context, params.history,
|
|
744
|
+
),
|
|
745
|
+
this.handleRoutingAndStepSelection({
|
|
746
|
+
session: params.session,
|
|
747
|
+
history: params.history,
|
|
748
|
+
context: params.context,
|
|
749
|
+
signal: params.signal,
|
|
750
|
+
}),
|
|
751
|
+
]);
|
|
752
|
+
|
|
753
|
+
// ── Requirement 8.2: halt → discard routing, skip LLM ────────────
|
|
754
|
+
if (signalResult.mergedDirective?.halt) {
|
|
755
|
+
return {
|
|
756
|
+
selectedFlow: undefined,
|
|
757
|
+
selectedStep: undefined,
|
|
758
|
+
session: signalResult.updatedSession,
|
|
759
|
+
isFlowComplete: false,
|
|
760
|
+
signalFirings: signalResult.firings,
|
|
761
|
+
signalHalted: true,
|
|
762
|
+
signalHaltReply: signalResult.mergedDirective.reply,
|
|
763
|
+
};
|
|
764
|
+
}
|
|
856
765
|
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
};
|
|
862
|
-
}
|
|
766
|
+
// ── Requirement 8.3: position directive → discard routing, apply signal position ──
|
|
767
|
+
if (hasDirectivePositionField(signalResult.mergedDirective)) {
|
|
768
|
+
return this.signalCoordinator.applyPositionDirective(signalResult);
|
|
769
|
+
}
|
|
863
770
|
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
async executeToolLoop(params: {
|
|
868
|
-
initialToolCalls:
|
|
869
|
-
| Array<{ toolName: string; arguments: Record<string, unknown> }>
|
|
870
|
-
| undefined;
|
|
871
|
-
selectedFlow?: Flow<TContext, TData>;
|
|
872
|
-
nextStep: Step<TContext, TData>;
|
|
873
|
-
responsePrompt: string;
|
|
874
|
-
history: HistoryItem[];
|
|
875
|
-
context: TContext;
|
|
876
|
-
session: SessionState<TData>;
|
|
877
|
-
responseSchema: Record<string, unknown>;
|
|
878
|
-
isStreaming?: boolean;
|
|
879
|
-
}): Promise<ToolExecutionResult<TData>> {
|
|
880
|
-
const {
|
|
881
|
-
initialToolCalls,
|
|
882
|
-
selectedFlow,
|
|
883
|
-
nextStep,
|
|
884
|
-
responsePrompt,
|
|
885
|
-
history,
|
|
886
|
-
context,
|
|
887
|
-
session,
|
|
888
|
-
responseSchema,
|
|
889
|
-
isStreaming = false,
|
|
890
|
-
} = params;
|
|
891
|
-
|
|
892
|
-
const MAX_TOOL_LOOPS = 5;
|
|
893
|
-
let toolLoopCount = 0;
|
|
894
|
-
let currentToolCalls = initialToolCalls;
|
|
895
|
-
let hasToolCalls = currentToolCalls && currentToolCalls.length > 0;
|
|
896
|
-
let currentSession = session;
|
|
897
|
-
// Track tool results across loop iterations for history
|
|
898
|
-
let currentToolResults = new Map<string, string>();
|
|
899
|
-
|
|
900
|
-
while (hasToolCalls && toolLoopCount < MAX_TOOL_LOOPS) {
|
|
901
|
-
toolLoopCount++;
|
|
902
|
-
logger.debug(
|
|
903
|
-
`[ResponseHandler] Starting ${isStreaming ? "streaming " : ""
|
|
904
|
-
}tool loop ${toolLoopCount}/${MAX_TOOL_LOOPS}`
|
|
905
|
-
);
|
|
771
|
+
// ── Requirement 8.4: non-position directive → use routing, propagate augmentation ──
|
|
772
|
+
// ── Requirement 8.5: no directive → use routing as-is ─────────────
|
|
773
|
+
let updatedSession = signalResult.updatedSession;
|
|
906
774
|
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
const tool = this.findAvailableTool(toolCall.toolName, selectedFlow);
|
|
911
|
-
if (tool) {
|
|
912
|
-
toolResultItems.push({
|
|
913
|
-
role: "assistant" as const,
|
|
914
|
-
content: null,
|
|
915
|
-
tool_calls: [{
|
|
916
|
-
id: toolCall.toolName,
|
|
917
|
-
name: toolCall.toolName,
|
|
918
|
-
arguments: toolCall.arguments,
|
|
919
|
-
}],
|
|
920
|
-
});
|
|
921
|
-
toolResultItems.push({
|
|
922
|
-
role: "tool" as const,
|
|
923
|
-
tool_call_id: toolCall.toolName,
|
|
924
|
-
name: toolCall.toolName,
|
|
925
|
-
content: currentToolResults.get(toolCall.toolName) || "Tool executed successfully",
|
|
926
|
-
});
|
|
775
|
+
// Apply data/context updates from signal to the routed session
|
|
776
|
+
if (signalResult.mergedDirective?.dataUpdate) {
|
|
777
|
+
updatedSession = mergeCollected(updatedSession, signalResult.mergedDirective.dataUpdate);
|
|
927
778
|
}
|
|
928
|
-
}
|
|
929
|
-
|
|
930
|
-
// Create updated history with tool results
|
|
931
|
-
const updatedHistory = [...history, ...toolResultItems];
|
|
932
779
|
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
}
|
|
943
|
-
});
|
|
780
|
+
// Use routing result for flow/step, but carry signal session state
|
|
781
|
+
// Merge routing session changes on top of signal session
|
|
782
|
+
const routingSession = routingResult.session;
|
|
783
|
+
updatedSession = {
|
|
784
|
+
...updatedSession,
|
|
785
|
+
currentFlow: routingSession.currentFlow,
|
|
786
|
+
currentStep: routingSession.currentStep,
|
|
787
|
+
flowHistory: routingSession.flowHistory,
|
|
788
|
+
pendingDirective: routingSession.pendingDirective,
|
|
789
|
+
};
|
|
944
790
|
|
|
945
|
-
|
|
946
|
-
const followUpToolCalls = followUpResult.structured?.toolCalls;
|
|
947
|
-
hasToolCalls = followUpToolCalls && followUpToolCalls.length > 0;
|
|
791
|
+
const isFlowComplete = routingResult.isFlowComplete;
|
|
948
792
|
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
793
|
+
// PRE-EXTRACTION: same logic as below — extract data from user message
|
|
794
|
+
if (routingResult.selectedFlow && !isFlowComplete) {
|
|
795
|
+
if (this.shouldPreExtractData(routingResult.selectedFlow)) {
|
|
796
|
+
logger.debug(
|
|
797
|
+
`[ResponsePipeline] Pre-extracting data for flow: ${routingResult.selectedFlow.title}`
|
|
798
|
+
);
|
|
799
|
+
const extractedData = await this.preExtractFlowData({
|
|
800
|
+
route: routingResult.selectedFlow,
|
|
801
|
+
history: params.history,
|
|
802
|
+
context: params.context,
|
|
803
|
+
session: updatedSession,
|
|
804
|
+
signal: params.signal,
|
|
805
|
+
});
|
|
806
|
+
if (extractedData && Object.keys(extractedData).length > 0) {
|
|
807
|
+
logger.debug(`[ResponsePipeline] Pre-extracted data:`, extractedData);
|
|
808
|
+
updatedSession = mergeCollected(updatedSession, extractedData);
|
|
809
|
+
await this.updateCollectedData(extractedData);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
}
|
|
954
813
|
|
|
955
|
-
//
|
|
956
|
-
const
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
isStreaming,
|
|
814
|
+
// Determine next step
|
|
815
|
+
const stepResult = await this.determineNextStep({
|
|
816
|
+
selectedFlow: routingResult.selectedFlow,
|
|
817
|
+
selectedStep: routingResult.selectedStep,
|
|
818
|
+
session: updatedSession,
|
|
819
|
+
isFlowComplete,
|
|
820
|
+
context: params.context,
|
|
963
821
|
});
|
|
964
822
|
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
currentToolCalls = followUpToolCalls || [];
|
|
975
|
-
break;
|
|
823
|
+
return {
|
|
824
|
+
selectedFlow: stepResult.flowChanged || routingResult.selectedFlow,
|
|
825
|
+
selectedStep: stepResult.nextStep,
|
|
826
|
+
responseDirectives: routingResult.responseDirectives,
|
|
827
|
+
session: stepResult.session,
|
|
828
|
+
isFlowComplete: stepResult.flowChanged ? false : isFlowComplete,
|
|
829
|
+
signalFirings: signalResult.firings,
|
|
830
|
+
signalPreDirective: signalResult.mergedDirective || undefined,
|
|
831
|
+
};
|
|
976
832
|
}
|
|
977
|
-
}
|
|
978
|
-
|
|
979
|
-
if (toolLoopCount >= MAX_TOOL_LOOPS) {
|
|
980
|
-
logger.warn(
|
|
981
|
-
`[ResponseGenerationError] ${isStreaming ? "Streaming t" : "T"}ool loop limit reached: ${toolLoopCount} iterations hit the cap (${MAX_TOOL_LOOPS}). Stopping tool execution. Increase MAX_TOOL_LOOPS or reduce recursive tool calls.`
|
|
982
|
-
);
|
|
983
|
-
}
|
|
984
|
-
|
|
985
|
-
return {
|
|
986
|
-
session: currentSession,
|
|
987
|
-
toolCalls: currentToolCalls,
|
|
988
|
-
};
|
|
989
|
-
}
|
|
990
833
|
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
}): Promise<DataCollectionResult<TData>> {
|
|
999
|
-
const { structured, nextStep, session } = params;
|
|
1000
|
-
|
|
1001
|
-
if (!structured || !nextStep.collect) {
|
|
1002
|
-
return { session };
|
|
1003
|
-
}
|
|
834
|
+
// ── No signal processor: existing behavior (zero overhead) ────────────
|
|
835
|
+
const routingResult = await this.handleRoutingAndStepSelection({
|
|
836
|
+
session: params.session,
|
|
837
|
+
history: params.history,
|
|
838
|
+
context: params.context,
|
|
839
|
+
signal: params.signal,
|
|
840
|
+
});
|
|
1004
841
|
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
842
|
+
let updatedSession = routingResult.session;
|
|
843
|
+
const isFlowComplete = routingResult.isFlowComplete;
|
|
844
|
+
|
|
845
|
+
// PRE-EXTRACTION: If entering a flow that collects data, extract data from user message first
|
|
846
|
+
// This allows us to skip steps whose data is already provided
|
|
847
|
+
if (routingResult.selectedFlow && !isFlowComplete) {
|
|
848
|
+
// Always pre-extract when flow collects data (not just on new flow entry)
|
|
849
|
+
// This ensures step selection has the most up-to-date data
|
|
850
|
+
if (this.shouldPreExtractData(routingResult.selectedFlow)) {
|
|
851
|
+
logger.debug(
|
|
852
|
+
`[ResponsePipeline] Pre-extracting data for flow: ${routingResult.selectedFlow.title}`
|
|
853
|
+
);
|
|
1009
854
|
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
855
|
+
const extractedData = await this.preExtractFlowData({
|
|
856
|
+
route: routingResult.selectedFlow,
|
|
857
|
+
history: params.history,
|
|
858
|
+
context: params.context,
|
|
859
|
+
session: updatedSession,
|
|
860
|
+
signal: params.signal,
|
|
861
|
+
});
|
|
1016
862
|
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
863
|
+
if (extractedData && Object.keys(extractedData).length > 0) {
|
|
864
|
+
logger.debug(
|
|
865
|
+
`[ResponsePipeline] Pre-extracted data:`,
|
|
866
|
+
extractedData
|
|
867
|
+
);
|
|
868
|
+
// Merge pre-extracted data into session before step selection
|
|
869
|
+
updatedSession = mergeCollected(updatedSession, extractedData);
|
|
870
|
+
// Also update agent's collected data
|
|
871
|
+
await this.updateCollectedData(extractedData);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
1022
874
|
}
|
|
1023
875
|
|
|
1024
|
-
//
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
};
|
|
1033
|
-
}
|
|
876
|
+
// Determine next step using pipeline method for consistency
|
|
877
|
+
const stepResult = await this.determineNextStep({
|
|
878
|
+
selectedFlow: routingResult.selectedFlow,
|
|
879
|
+
selectedStep: routingResult.selectedStep,
|
|
880
|
+
session: updatedSession, // Use updated session with pre-extracted data
|
|
881
|
+
isFlowComplete, // Use updated completion status
|
|
882
|
+
context: params.context,
|
|
883
|
+
});
|
|
1034
884
|
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
) {
|
|
1046
|
-
await this.updateContext(
|
|
1047
|
-
(structured as { contextUpdate?: Partial<TContext> })
|
|
1048
|
-
.contextUpdate as Partial<TContext>
|
|
1049
|
-
);
|
|
885
|
+
return {
|
|
886
|
+
selectedFlow: stepResult.flowChanged || routingResult.selectedFlow,
|
|
887
|
+
selectedStep: stepResult.nextStep, // Use the determined next step
|
|
888
|
+
responseDirectives: routingResult.responseDirectives,
|
|
889
|
+
session: stepResult.session,
|
|
890
|
+
// If a branch changed the flow, the original isFlowComplete no longer applies
|
|
891
|
+
isFlowComplete: stepResult.flowChanged ? false : isFlowComplete,
|
|
892
|
+
};
|
|
893
|
+
} catch (error) {
|
|
894
|
+
throw ResponseGenerationError.fromError(error, 'routing_optimization', params);
|
|
1050
895
|
}
|
|
1051
896
|
}
|
|
1052
897
|
|
|
1053
898
|
/**
|
|
1054
|
-
*
|
|
899
|
+
* RENDER-STEP RESOLUTION — resolve the step a flow response will render,
|
|
900
|
+
* shared by the streaming and non-streaming paths. When no step was
|
|
901
|
+
* pre-selected: branches win over the linear chain, then candidate steps,
|
|
902
|
+
* then the initial-step fallback. Enforces `requires` (stays at the current
|
|
903
|
+
* step when required fields are missing) and enters the resolved step.
|
|
1055
904
|
*
|
|
1056
|
-
*
|
|
1057
|
-
*
|
|
1058
|
-
*
|
|
1059
|
-
* emits no message of its own at the completion boundary; callers that
|
|
1060
|
-
* need closing copy should add a final interactive step.
|
|
905
|
+
* Returns `flowTransition: true` when a branch resolved to a flow
|
|
906
|
+
* transition or completion — there is no local step to render and the
|
|
907
|
+
* caller handles the transition.
|
|
1061
908
|
*/
|
|
1062
|
-
async
|
|
909
|
+
async resolveRenderStep(params: {
|
|
1063
910
|
selectedFlow: Flow<TContext, TData>;
|
|
911
|
+
selectedStep?: Step<TContext, TData>;
|
|
1064
912
|
session: SessionState<TData>;
|
|
1065
913
|
context: TContext;
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
const
|
|
1072
|
-
|
|
1073
|
-
context
|
|
1074
|
-
);
|
|
1075
|
-
|
|
1076
|
-
// Release to idle. When the flow is reentrant, scrub its owned
|
|
1077
|
-
// fields so a future re-selection doesn't short-circuit on stale data.
|
|
1078
|
-
const ownedFields = selectedFlow.reentrant
|
|
1079
|
-
? [
|
|
1080
|
-
...(selectedFlow.requiredFields ?? []),
|
|
1081
|
-
...(selectedFlow.optionalFields ?? []),
|
|
1082
|
-
]
|
|
1083
|
-
: undefined;
|
|
1084
|
-
let updatedSession = completeCurrentFlow(session, {
|
|
1085
|
-
clearOwnedFields: ownedFields,
|
|
1086
|
-
});
|
|
914
|
+
}): Promise<{
|
|
915
|
+
nextStep?: Step<TContext, TData>;
|
|
916
|
+
session: SessionState<TData>;
|
|
917
|
+
flowTransition: boolean;
|
|
918
|
+
}> {
|
|
919
|
+
const { selectedFlow, selectedStep, context } = params;
|
|
920
|
+
let session = params.session;
|
|
1087
921
|
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
922
|
+
// Determine next step
|
|
923
|
+
let nextStep: Step<TContext, TData>;
|
|
924
|
+
if (selectedStep) {
|
|
925
|
+
nextStep = selectedStep;
|
|
926
|
+
} else {
|
|
927
|
+
// Determine current step from session if we're already in this flow
|
|
928
|
+
const isInSameFlow = session.currentFlow?.id === selectedFlow.id;
|
|
929
|
+
const currentStep = isInSameFlow && session.currentStep
|
|
930
|
+
? selectedFlow.getStep(session.currentStep.id)
|
|
931
|
+
: undefined;
|
|
1093
932
|
|
|
1094
|
-
|
|
1095
|
-
const targetFlow = goToTarget ? this.getFlows().find(
|
|
1096
|
-
(r) =>
|
|
1097
|
-
r.id === goToTarget ||
|
|
1098
|
-
r.title === goToTarget
|
|
1099
|
-
) : undefined;
|
|
933
|
+
logger.debug(`[ResponsePipeline] Step determination: flow match=${isInSameFlow}, currentFlow=${session.currentFlow?.id}, selectedFlow=${selectedFlow.id}, currentStep=${currentStep?.id || 'none'}`);
|
|
1100
934
|
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
goTo: targetFlow.id,
|
|
1106
|
-
},
|
|
1107
|
-
};
|
|
1108
|
-
logger.debug(
|
|
1109
|
-
`[ResponseHandler] Flow ${selectedFlow.title} completed with pending directive to: ${targetFlow.title}`
|
|
935
|
+
// STEP 1 (Algorithm 1): branches win over linear chain
|
|
936
|
+
if (currentStep?.branches && currentStep.branches.length > 0) {
|
|
937
|
+
const branchResult = await this.evaluateStepBranches(
|
|
938
|
+
currentStep, selectedFlow, session, context
|
|
1110
939
|
);
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
940
|
+
if (branchResult) {
|
|
941
|
+
if (branchResult.nextStep) {
|
|
942
|
+
nextStep = branchResult.nextStep;
|
|
943
|
+
session = branchResult.session;
|
|
944
|
+
} else {
|
|
945
|
+
// Flow transition or completion — no local step to render
|
|
946
|
+
return { nextStep: undefined, session: branchResult.session, flowTransition: true };
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
if (!nextStep!) {
|
|
952
|
+
// Get candidate steps based on current position in the flow
|
|
953
|
+
const candidates = await this.flowRouter.getCandidateStepsWithConditions(
|
|
954
|
+
selectedFlow,
|
|
955
|
+
currentStep, // Pass current step instead of undefined to maintain progression
|
|
956
|
+
createTemplateContext({ data: session.data, session, context })
|
|
1116
957
|
);
|
|
958
|
+
|
|
959
|
+
logger.debug(`[ResponsePipeline] Found ${candidates.length} candidate steps${currentStep ? ' from current step ' + currentStep.id : ' (new flow entry)'}`);
|
|
960
|
+
|
|
961
|
+
if (candidates.length > 0) {
|
|
962
|
+
nextStep = candidates[0].step;
|
|
963
|
+
logger.debug(`[ResponsePipeline] Using first valid step: ${nextStep.id}${currentStep ? ' (progressing from ' + currentStep.id + ')' : ' for new flow'}`);
|
|
964
|
+
} else {
|
|
965
|
+
// Fallback to initial step even if it should be skipped
|
|
966
|
+
nextStep = selectedFlow.initialStep;
|
|
967
|
+
logger.warn(`[FlowConfigurationError] No valid steps found: all candidates were skipped in flow. Falling back to initial step "${nextStep.id}". Review step skip conditions.`);
|
|
968
|
+
}
|
|
1117
969
|
}
|
|
1118
970
|
}
|
|
1119
971
|
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
)
|
|
972
|
+
// Update session with next step
|
|
973
|
+
// If the next step has requires fields that are missing, stay at the previous step
|
|
974
|
+
if (nextStep.requires && nextStep.requires.length > 0) {
|
|
975
|
+
const sessionData = session.data || {};
|
|
976
|
+
const missingRequires = nextStep.requires.filter(
|
|
977
|
+
field => (sessionData as Record<string, unknown>)[String(field)] === undefined
|
|
978
|
+
);
|
|
979
|
+
if (missingRequires.length > 0) {
|
|
980
|
+
const warning = `[FlowConfigurationError] Cannot advance to step "${nextStep.description || nextStep.id}": ` +
|
|
981
|
+
`missing required fields [${missingRequires.join(', ')}]. Staying at current step. Ensure preceding steps collect these fields.`;
|
|
982
|
+
logger.warn(warning);
|
|
983
|
+
console.warn(warning);
|
|
984
|
+
// Stay at the current step - don't enter the next one
|
|
985
|
+
const currentStepId = session.currentStep?.id;
|
|
986
|
+
if (currentStepId) {
|
|
987
|
+
const currentStepInstance = selectedFlow.getStep(currentStepId);
|
|
988
|
+
if (currentStepInstance) {
|
|
989
|
+
nextStep = currentStepInstance;
|
|
990
|
+
logger.debug(`[ResponsePipeline] Staying at current step: ${nextStep.id} due to missing requires`);
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
} else {
|
|
994
|
+
session = enterStep(session, nextStep.id, nextStep.description);
|
|
995
|
+
logger.debug(`[ResponsePipeline] Entered step: ${nextStep.id}`);
|
|
996
|
+
}
|
|
997
|
+
} else {
|
|
998
|
+
session = enterStep(session, nextStep.id, nextStep.description);
|
|
999
|
+
logger.debug(`[ResponsePipeline] Entered step: ${nextStep.id}`);
|
|
1000
|
+
}
|
|
1123
1001
|
|
|
1124
|
-
return { session
|
|
1002
|
+
return { nextStep, session, flowTransition: false };
|
|
1125
1003
|
}
|
|
1126
1004
|
|
|
1127
|
-
|
|
1128
1005
|
/**
|
|
1129
|
-
*
|
|
1130
|
-
*
|
|
1006
|
+
* Routing skip optimization (Requirements 20.1, 20.2, 20.3):
|
|
1007
|
+
* When the current step declares `collect` fields AND pre-extraction populates
|
|
1008
|
+
* at least one of those fields from the user's message, skip routing for this turn.
|
|
1009
|
+
*
|
|
1010
|
+
* Returns the routing result if the skip applies, or undefined to fall through
|
|
1011
|
+
* to normal routing.
|
|
1131
1012
|
*/
|
|
1132
|
-
private
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1013
|
+
private async attemptRoutingSkipForCollect(params: {
|
|
1014
|
+
session: SessionState<TData>;
|
|
1015
|
+
history: Event[];
|
|
1016
|
+
context: TContext;
|
|
1017
|
+
signal?: AbortSignal;
|
|
1018
|
+
}): Promise<{
|
|
1019
|
+
selectedFlow?: Flow<TContext, TData>;
|
|
1020
|
+
selectedStep?: Step<TContext, TData>;
|
|
1021
|
+
responseDirectives?: string[];
|
|
1022
|
+
session: SessionState<TData>;
|
|
1023
|
+
isFlowComplete: boolean;
|
|
1024
|
+
} | undefined> {
|
|
1025
|
+
const { session } = params;
|
|
1140
1026
|
|
|
1141
|
-
//
|
|
1142
|
-
|
|
1027
|
+
// Only applies when we already have a current flow and step
|
|
1028
|
+
if (!session.currentFlow || !session.currentStep) {
|
|
1029
|
+
return undefined;
|
|
1030
|
+
}
|
|
1143
1031
|
|
|
1144
|
-
//
|
|
1145
|
-
if (
|
|
1146
|
-
|
|
1147
|
-
.getTools()
|
|
1148
|
-
.find((tool) => tool.id === toolName || tool.id === toolName);
|
|
1149
|
-
if (flowTool) return flowTool;
|
|
1032
|
+
// Also skip this optimization if there's a pending directive (it takes priority)
|
|
1033
|
+
if (session.pendingDirective) {
|
|
1034
|
+
return undefined;
|
|
1150
1035
|
}
|
|
1151
1036
|
|
|
1152
|
-
//
|
|
1153
|
-
|
|
1154
|
-
(
|
|
1037
|
+
// Look up the actual Flow and Step objects to access `collect`
|
|
1038
|
+
const currentFlow = this.getFlows().find(
|
|
1039
|
+
(f) => f.id === session.currentFlow?.id
|
|
1155
1040
|
);
|
|
1156
|
-
|
|
1041
|
+
if (!currentFlow) {
|
|
1042
|
+
return undefined;
|
|
1043
|
+
}
|
|
1157
1044
|
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
*/
|
|
1162
|
-
private collectAvailableTools(
|
|
1163
|
-
flow?: Flow<TContext, TData>,
|
|
1164
|
-
step?: Step<TContext, TData>
|
|
1165
|
-
): Array<{ id: string; description?: string; parameters?: unknown }> {
|
|
1166
|
-
// Use ToolManager for unified tool collection if available
|
|
1167
|
-
if (this.toolManager) {
|
|
1168
|
-
const availableTools = this.toolManager.getAvailable(undefined, step, flow);
|
|
1169
|
-
return availableTools.map((tool) => ({
|
|
1170
|
-
id: tool.id,
|
|
1171
|
-
description: tool.description,
|
|
1172
|
-
parameters: tool.parameters,
|
|
1173
|
-
}));
|
|
1045
|
+
const currentStep = currentFlow.getStep(session.currentStep.id);
|
|
1046
|
+
if (!currentStep || !currentStep.collect || currentStep.collect.length === 0) {
|
|
1047
|
+
return undefined;
|
|
1174
1048
|
}
|
|
1175
1049
|
|
|
1176
|
-
//
|
|
1177
|
-
|
|
1050
|
+
// We have a step with collect fields. Run pre-extraction to see if the
|
|
1051
|
+
// user's message populates any of them.
|
|
1052
|
+
const collectFields = currentStep.collect;
|
|
1178
1053
|
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
Tool<TContext, TData>
|
|
1182
|
-
>();
|
|
1054
|
+
// Snapshot current data for comparison
|
|
1055
|
+
const dataBefore = { ...session.data };
|
|
1183
1056
|
|
|
1184
|
-
//
|
|
1185
|
-
this.
|
|
1186
|
-
|
|
1057
|
+
// Run pre-extraction against the current flow
|
|
1058
|
+
const extractedData = await this.preExtractFlowData({
|
|
1059
|
+
route: currentFlow,
|
|
1060
|
+
history: params.history,
|
|
1061
|
+
context: params.context,
|
|
1062
|
+
session,
|
|
1063
|
+
signal: params.signal,
|
|
1187
1064
|
});
|
|
1188
1065
|
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
flow.getTools().forEach((tool) => {
|
|
1192
|
-
availableTools.set(tool.id, tool);
|
|
1193
|
-
});
|
|
1066
|
+
if (!extractedData || Object.keys(extractedData).length === 0) {
|
|
1067
|
+
return undefined;
|
|
1194
1068
|
}
|
|
1195
1069
|
|
|
1196
|
-
//
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
const
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
allowedToolIds.add(toolRef);
|
|
1205
|
-
} else {
|
|
1206
|
-
// Inline tool definition
|
|
1207
|
-
if (toolRef.id) {
|
|
1208
|
-
allowedToolIds.add(toolRef.id);
|
|
1209
|
-
stepTools.push(toolRef);
|
|
1210
|
-
}
|
|
1211
|
-
}
|
|
1212
|
-
}
|
|
1213
|
-
|
|
1214
|
-
// If step specifies tools, only include those
|
|
1215
|
-
if (allowedToolIds.size > 0) {
|
|
1216
|
-
const filteredTools = new Map<
|
|
1217
|
-
string,
|
|
1218
|
-
Tool<TContext, TData>
|
|
1219
|
-
>();
|
|
1220
|
-
for (const toolId of Array.from(allowedToolIds)) {
|
|
1221
|
-
const tool = availableTools.get(toolId);
|
|
1222
|
-
if (tool) {
|
|
1223
|
-
filteredTools.set(toolId, tool);
|
|
1224
|
-
}
|
|
1225
|
-
}
|
|
1226
|
-
// Add inline tools
|
|
1227
|
-
stepTools.forEach((tool) => {
|
|
1228
|
-
if (tool.id) {
|
|
1229
|
-
filteredTools.set(tool.id, tool);
|
|
1230
|
-
}
|
|
1231
|
-
});
|
|
1232
|
-
availableTools.clear();
|
|
1233
|
-
filteredTools.forEach((tool, id) => availableTools.set(id, tool));
|
|
1070
|
+
// Determine which collect fields were newly populated by pre-extraction
|
|
1071
|
+
const populatedCollectFields: string[] = [];
|
|
1072
|
+
for (const field of collectFields) {
|
|
1073
|
+
const key = field as string;
|
|
1074
|
+
const hadValue = dataBefore[field] !== undefined && dataBefore[field] !== null;
|
|
1075
|
+
const hasNewValue = extractedData[field] !== undefined && extractedData[field] !== null;
|
|
1076
|
+
if (hasNewValue && !hadValue) {
|
|
1077
|
+
populatedCollectFields.push(key);
|
|
1234
1078
|
}
|
|
1235
1079
|
}
|
|
1236
1080
|
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
description: tool.description,
|
|
1241
|
-
parameters: tool.parameters,
|
|
1242
|
-
}));
|
|
1243
|
-
}
|
|
1244
|
-
|
|
1245
|
-
/**
|
|
1246
|
-
* Get current context (fetches from provider if configured)
|
|
1247
|
-
*/
|
|
1248
|
-
private async getContext(): Promise<TContext | undefined> {
|
|
1249
|
-
// If context provider is configured, use it to fetch fresh context
|
|
1250
|
-
if (this.options.contextProvider) {
|
|
1251
|
-
return await this.options.contextProvider();
|
|
1081
|
+
if (populatedCollectFields.length === 0) {
|
|
1082
|
+
// Pre-extraction didn't populate any declared collect field — no skip
|
|
1083
|
+
return undefined;
|
|
1252
1084
|
}
|
|
1253
1085
|
|
|
1254
|
-
//
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
// These need to be passed in or accessed differently since ResponseHandler is not part of Agent
|
|
1259
|
-
private context?: TContext;
|
|
1260
|
-
private currentSession?: SessionState<TData>;
|
|
1261
|
-
|
|
1262
|
-
// Setters for context and current session (needed for beforeRespond hook)
|
|
1263
|
-
setContext(context: TContext | undefined): void {
|
|
1264
|
-
this.context = context;
|
|
1265
|
-
}
|
|
1086
|
+
// ROUTING SKIP: pre-extraction populated collect fields → retain current flow/step
|
|
1087
|
+
logger.debug(
|
|
1088
|
+
`[ResponsePipeline] Routing skip: pre-extraction populated collect fields [${populatedCollectFields.join(', ')}] for step "${currentStep.id}" — skipping FlowRouter`
|
|
1089
|
+
);
|
|
1266
1090
|
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1091
|
+
// Merge extracted data into session
|
|
1092
|
+
const updatedSession = mergeCollected(session, extractedData);
|
|
1093
|
+
await this.updateCollectedData(extractedData);
|
|
1270
1094
|
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1095
|
+
// Determine next step using pipeline method for consistency
|
|
1096
|
+
// Pass the current flow/step as the routing result (retained)
|
|
1097
|
+
const stepResult = await this.determineNextStep({
|
|
1098
|
+
selectedFlow: currentFlow,
|
|
1099
|
+
selectedStep: currentStep,
|
|
1100
|
+
session: updatedSession,
|
|
1101
|
+
isFlowComplete: false,
|
|
1102
|
+
context: params.context,
|
|
1103
|
+
});
|
|
1274
1104
|
|
|
1275
|
-
|
|
1276
|
-
|
|
1105
|
+
return {
|
|
1106
|
+
selectedFlow: stepResult.flowChanged || currentFlow,
|
|
1107
|
+
selectedStep: stepResult.nextStep,
|
|
1108
|
+
responseDirectives: undefined,
|
|
1109
|
+
session: stepResult.session,
|
|
1110
|
+
isFlowComplete: false,
|
|
1111
|
+
};
|
|
1277
1112
|
}
|
|
1278
1113
|
|
|
1279
1114
|
/**
|
|
1280
|
-
*
|
|
1281
|
-
* This method evaluates all flows for completion and can trigger completion handlers
|
|
1115
|
+
* Check if a flow should pre-extract data before determining the initial step
|
|
1282
1116
|
*/
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
history: Event[];
|
|
1288
|
-
}): Promise<{
|
|
1289
|
-
session: SessionState<TData>;
|
|
1290
|
-
completedFlows: Flow<TContext, TData>[];
|
|
1291
|
-
pendingTransitions: Array<{
|
|
1292
|
-
flow: Flow<TContext, TData>;
|
|
1293
|
-
transitionConfig: Directive<TContext, TData>;
|
|
1294
|
-
}>;
|
|
1295
|
-
}> {
|
|
1296
|
-
const { flows, session, context } = params;
|
|
1297
|
-
|
|
1298
|
-
// Evaluate all flows for completion
|
|
1299
|
-
const completedFlows: Flow<TContext, TData>[] = [];
|
|
1300
|
-
const pendingTransitions: Array<{
|
|
1301
|
-
flow: Flow<TContext, TData>;
|
|
1302
|
-
transitionConfig: Directive<TContext, TData>;
|
|
1303
|
-
}> = [];
|
|
1304
|
-
|
|
1305
|
-
for (const flow of flows) {
|
|
1306
|
-
if (flow.isComplete(session.data || {})) {
|
|
1307
|
-
completedFlows.push(flow);
|
|
1308
|
-
|
|
1309
|
-
// Check for onComplete transitions
|
|
1310
|
-
const transitionConfig = await flow.evaluateOnComplete(
|
|
1311
|
-
{ data: session.data },
|
|
1312
|
-
context
|
|
1313
|
-
);
|
|
1314
|
-
|
|
1315
|
-
if (transitionConfig) {
|
|
1316
|
-
pendingTransitions.push({ flow, transitionConfig });
|
|
1317
|
-
}
|
|
1318
|
-
|
|
1319
|
-
logger.debug(
|
|
1320
|
-
`[ResponsePipeline] Flow completed: ${flow.title} ` +
|
|
1321
|
-
`(${Math.round(flow.getCompletionProgress(session.data || {}) * 100)}%)`
|
|
1322
|
-
);
|
|
1323
|
-
}
|
|
1117
|
+
private shouldPreExtractData(flow: Flow<TContext, TData>): boolean {
|
|
1118
|
+
// Pre-extract if flow has declared required or optional fields
|
|
1119
|
+
if (flow.requiredFields && flow.requiredFields.length > 0) {
|
|
1120
|
+
return true;
|
|
1324
1121
|
}
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
if (completedFlows.length > 0) {
|
|
1328
|
-
logger.debug(
|
|
1329
|
-
`[ResponsePipeline] Cross-flow completion evaluation: ` +
|
|
1330
|
-
`${completedFlows.length}/${flows.length} flows complete`
|
|
1331
|
-
);
|
|
1122
|
+
if (flow.optionalFields && flow.optionalFields.length > 0) {
|
|
1123
|
+
return true;
|
|
1332
1124
|
}
|
|
1333
1125
|
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1126
|
+
// Pre-extract if any step in the flow collects data
|
|
1127
|
+
const steps = flow.getAllSteps();
|
|
1128
|
+
const hasDataCollectionSteps = steps.some(
|
|
1129
|
+
step => step.collect && step.collect.length > 0
|
|
1130
|
+
);
|
|
1131
|
+
|
|
1132
|
+
return hasDataCollectionSteps;
|
|
1339
1133
|
}
|
|
1340
1134
|
|
|
1341
1135
|
/**
|
|
1342
|
-
*
|
|
1343
|
-
* This
|
|
1136
|
+
* Pre-extract data from user message when entering a flow
|
|
1137
|
+
* This allows skipping steps whose data is already provided
|
|
1344
1138
|
*/
|
|
1345
|
-
async
|
|
1139
|
+
private async preExtractFlowData(params: {
|
|
1140
|
+
route: Flow<TContext, TData>;
|
|
1141
|
+
history: Event[];
|
|
1142
|
+
context: TContext;
|
|
1346
1143
|
session: SessionState<TData>;
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1144
|
+
signal?: AbortSignal;
|
|
1145
|
+
}): Promise<Partial<TData>> {
|
|
1146
|
+
const { route: flow, history, signal } = params;
|
|
1147
|
+
|
|
1148
|
+
// Build a schema for data extraction based on flow's fields
|
|
1149
|
+
const extractionSchema = this.getSchema();
|
|
1150
|
+
if (!extractionSchema) {
|
|
1151
|
+
logger.warn(`[ResponsePipeline] No schema available for pre-extraction`);
|
|
1152
|
+
return {};
|
|
1153
|
+
}
|
|
1354
1154
|
|
|
1355
|
-
//
|
|
1356
|
-
|
|
1357
|
-
|
|
1155
|
+
// Get last user message
|
|
1156
|
+
const lastMessage = getLastMessageFromHistory(history);
|
|
1157
|
+
|
|
1158
|
+
// Build extraction prompt
|
|
1159
|
+
const extractionPrompt = [
|
|
1160
|
+
`Extract any relevant information from the user's message that matches the following data fields.`,
|
|
1161
|
+
`Only extract information that is explicitly stated or clearly implied.`,
|
|
1162
|
+
``,
|
|
1163
|
+
`User's message: "${lastMessage}"`,
|
|
1164
|
+
``,
|
|
1165
|
+
`Extract data for these fields if present:`,
|
|
1166
|
+
];
|
|
1167
|
+
|
|
1168
|
+
// Add field descriptions
|
|
1169
|
+
if (flow.requiredFields) {
|
|
1170
|
+
extractionPrompt.push(`Required fields: ${flow.requiredFields.join(', ')}`);
|
|
1171
|
+
}
|
|
1172
|
+
if (flow.optionalFields) {
|
|
1173
|
+
extractionPrompt.push(`Optional fields: ${flow.optionalFields.join(', ')}`);
|
|
1358
1174
|
}
|
|
1359
1175
|
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
context: this.context!,
|
|
1365
|
-
history: [],
|
|
1366
|
-
});
|
|
1176
|
+
extractionPrompt.push(
|
|
1177
|
+
``,
|
|
1178
|
+
`Return ONLY the extracted data as JSON. If no data can be extracted, return an empty object {}.`
|
|
1179
|
+
);
|
|
1367
1180
|
|
|
1368
|
-
//
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1181
|
+
// Convert Event[] to HistoryItem[] for provider call
|
|
1182
|
+
const historyItems = eventsToHistory(history);
|
|
1183
|
+
|
|
1184
|
+
// Call AI to extract data
|
|
1185
|
+
try {
|
|
1186
|
+
const result = await this.options.provider.generateMessage<TContext, Partial<TData>>({
|
|
1187
|
+
prompt: extractionPrompt.join('\n'),
|
|
1188
|
+
history: historyItems,
|
|
1189
|
+
context: {} as TContext, // Passed as empty object so AI doesn't "extract" from context
|
|
1190
|
+
// NOTE: context is intentionally NOT passed here.
|
|
1191
|
+
// Passing context caused the AI to "extract" data from the lead's context
|
|
1192
|
+
// (e.g., name, sector, city) instead of from what the user actually said.
|
|
1193
|
+
signal,
|
|
1194
|
+
parameters: {
|
|
1195
|
+
jsonSchema: extractionSchema,
|
|
1196
|
+
schemaName: 'data_extraction',
|
|
1197
|
+
},
|
|
1198
|
+
});
|
|
1374
1199
|
|
|
1375
|
-
|
|
1200
|
+
return result.structured || {};
|
|
1201
|
+
} catch (error) {
|
|
1202
|
+
logger.error(`[ResponsePipeline] Pre-extraction failed:`, error);
|
|
1203
|
+
return {};
|
|
1204
|
+
}
|
|
1376
1205
|
}
|
|
1206
|
+
|
|
1377
1207
|
}
|