@falai/agent 2.2.4 → 2.4.2
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 -102
- package/dist/cjs/core/ResponseModal.d.ts.map +1 -1
- package/dist/cjs/core/ResponseModal.js +178 -1194
- 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 +11 -26
- 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 +28 -11
- 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 +18 -2
- 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 -102
- package/dist/core/ResponseModal.d.ts.map +1 -1
- package/dist/core/ResponseModal.js +171 -1186
- 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 +11 -26
- package/dist/core/SignalEvaluator.js.map +1 -1
- package/dist/core/SignalProcessor.d.ts.map +1 -1
- package/dist/core/SignalProcessor.js +28 -11
- 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 +18 -2
- 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 +2 -0
- 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 +14 -3
- 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 +233 -1482
- 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 +12 -29
- package/src/core/SignalProcessor.ts +34 -11
- 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 +20 -2
- 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
|
@@ -11,7 +11,7 @@ const template_1 = require("../utils/template");
|
|
|
11
11
|
const Step_1 = require("../core/Step");
|
|
12
12
|
const BranchEvaluator_1 = require("./BranchEvaluator");
|
|
13
13
|
const DirectiveChainTracker_1 = require("./DirectiveChainTracker");
|
|
14
|
-
const
|
|
14
|
+
const ResponseGenerationError_1 = require("./ResponseGenerationError");
|
|
15
15
|
/**
|
|
16
16
|
* Position fields on a Directive that represent a navigation decision.
|
|
17
17
|
* When any of these is set, the directive "wins" the turn's position decision
|
|
@@ -33,16 +33,13 @@ function hasDirectivePositionField(directive) {
|
|
|
33
33
|
* Shared response processing logic between respond() and respondStream() methods
|
|
34
34
|
*/
|
|
35
35
|
class ResponsePipeline {
|
|
36
|
-
constructor(options, getFlows,
|
|
36
|
+
constructor(options, getFlows, flowRouter, signalCoordinator, updateCollectedData, getSchema) {
|
|
37
37
|
this.options = options;
|
|
38
38
|
this.getFlows = getFlows;
|
|
39
|
-
this.tools = tools;
|
|
40
39
|
this.flowRouter = flowRouter;
|
|
41
|
-
this.
|
|
42
|
-
this.updateData = updateData;
|
|
40
|
+
this.signalCoordinator = signalCoordinator;
|
|
43
41
|
this.updateCollectedData = updateCollectedData;
|
|
44
|
-
this.
|
|
45
|
-
this.signalProcessor = signalProcessor;
|
|
42
|
+
this.getSchema = getSchema;
|
|
46
43
|
}
|
|
47
44
|
/**
|
|
48
45
|
* Create a fresh chain tracker for the current turn.
|
|
@@ -62,87 +59,30 @@ class ResponsePipeline {
|
|
|
62
59
|
}
|
|
63
60
|
return this._chainTracker;
|
|
64
61
|
}
|
|
65
|
-
/**
|
|
66
|
-
* Create a fresh DirectiveBus for a new turn.
|
|
67
|
-
* The bus collects directives from hooks, tools, and branches during the turn
|
|
68
|
-
* and merges them at phase boundaries via Algorithm 4.
|
|
69
|
-
*/
|
|
70
|
-
createDirectiveBus() {
|
|
71
|
-
return new DirectiveBus_1.DirectiveBus();
|
|
72
|
-
}
|
|
73
|
-
// ──────────────────────────────────────────────────────────────────────────
|
|
74
|
-
// Signal pipeline phases
|
|
75
|
-
// ──────────────────────────────────────────────────────────────────────────
|
|
76
|
-
/**
|
|
77
|
-
* PRE-SIGNAL PHASE — Evaluates pre/both signals in parallel with routing.
|
|
78
|
-
*
|
|
79
|
-
* Delegates to `signalProcessor.runPreSignalPhase(...)` when configured.
|
|
80
|
-
* When no signal processor is present (zero-cost path), returns a stable
|
|
81
|
-
* empty shape so callers don't need to branch.
|
|
82
|
-
*
|
|
83
|
-
* @requirements 2.1, 2.3, 8.6, 13.3
|
|
84
|
-
*/
|
|
85
|
-
async runPreSignalPhase(session, context, history) {
|
|
86
|
-
if (!this.signalProcessor) {
|
|
87
|
-
return { firings: [], updatedSession: session, mergedDirective: undefined };
|
|
88
|
-
}
|
|
89
|
-
const result = await this.signalProcessor.runPreSignalPhase({ session, history, context });
|
|
90
|
-
return {
|
|
91
|
-
firings: result.firings,
|
|
92
|
-
updatedSession: result.updatedSession,
|
|
93
|
-
mergedDirective: result.mergedDirective,
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
/**
|
|
97
|
-
* POST-SIGNAL PHASE — Evaluates post/both signals after finalize/onComplete.
|
|
98
|
-
*
|
|
99
|
-
* Delegates to `signalProcessor.runPostSignalPhase(...)` when configured.
|
|
100
|
-
* When no signal processor is present, returns a stable empty shape.
|
|
101
|
-
*
|
|
102
|
-
* Post-phase signals see the complete turn result: assistant message in history,
|
|
103
|
-
* collected data, tool results. Position directives from this phase set
|
|
104
|
-
* `session.pendingDirective` (no mid-turn re-entry per D6 decision).
|
|
105
|
-
*
|
|
106
|
-
* Pre-LLM-only fields (`appendPrompt`, `injectTools`, `halt`) are already
|
|
107
|
-
* dropped inside `runPostSignalPhase` per Phase 4.5 — this seam does NOT
|
|
108
|
-
* re-introduce them.
|
|
109
|
-
*
|
|
110
|
-
* @requirements 9.1, 9.2, 9.3, 9.4
|
|
111
|
-
*/
|
|
112
|
-
async runPostSignalPhase(session, context, history) {
|
|
113
|
-
if (!this.signalProcessor) {
|
|
114
|
-
return { firings: [], updatedSession: session, mergedDirective: undefined };
|
|
115
|
-
}
|
|
116
|
-
const result = await this.signalProcessor.runPostSignalPhase({ session, history, context });
|
|
117
|
-
return {
|
|
118
|
-
firings: result.firings,
|
|
119
|
-
updatedSession: result.updatedSession,
|
|
120
|
-
mergedDirective: result.mergedDirective,
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
62
|
/**
|
|
124
63
|
* Prepare context and session for response generation
|
|
125
64
|
*/
|
|
126
65
|
async prepareResponseContext(params) {
|
|
127
66
|
const { contextOverride, session } = params;
|
|
128
|
-
|
|
129
|
-
let
|
|
67
|
+
let currentContext = params.currentContext;
|
|
68
|
+
let contextAfterHook;
|
|
130
69
|
// Call beforeRespond hook if configured
|
|
131
70
|
if (this.options.hooks?.beforeRespond && currentContext !== undefined) {
|
|
132
71
|
currentContext = await this.options.hooks.beforeRespond(currentContext);
|
|
133
|
-
//
|
|
134
|
-
|
|
72
|
+
// Surface the hook result so the caller can sync it back to the agent
|
|
73
|
+
contextAfterHook = currentContext;
|
|
135
74
|
}
|
|
136
75
|
// Merge context with override
|
|
137
76
|
const effectiveContext = {
|
|
138
77
|
...currentContext,
|
|
139
78
|
...contextOverride,
|
|
140
79
|
};
|
|
141
|
-
// Initialize or get session (use
|
|
142
|
-
const targetSession = session ||
|
|
80
|
+
// Initialize or get session (use the live session if available)
|
|
81
|
+
const targetSession = session || params.currentSession || (0, utils_1.createSession)();
|
|
143
82
|
return {
|
|
144
83
|
effectiveContext,
|
|
145
84
|
session: targetSession,
|
|
85
|
+
contextAfterHook,
|
|
146
86
|
};
|
|
147
87
|
}
|
|
148
88
|
/**
|
|
@@ -162,7 +102,7 @@ class ResponsePipeline {
|
|
|
162
102
|
// Check for pending directive from previous flow completion or external dispatch
|
|
163
103
|
if (targetSession.pendingDirective) {
|
|
164
104
|
const directive = targetSession.pendingDirective;
|
|
165
|
-
utils_1.logger.debug(`[
|
|
105
|
+
utils_1.logger.debug(`[ResponsePipeline] Applying pending directive at start of turn`);
|
|
166
106
|
// Track directive chain depth (Requirement 22.1)
|
|
167
107
|
const tracker = this.chainTracker;
|
|
168
108
|
tracker.record(directive, "pending");
|
|
@@ -186,7 +126,7 @@ class ResponsePipeline {
|
|
|
186
126
|
if (flowTarget) {
|
|
187
127
|
const targetFlow = flows.find((r) => r.id === flowTarget || r.title === flowTarget);
|
|
188
128
|
if (targetFlow) {
|
|
189
|
-
utils_1.logger.debug(`[
|
|
129
|
+
utils_1.logger.debug(`[ResponsePipeline] Pending directive goTo → flow: ${targetFlow.title}`);
|
|
190
130
|
targetSession = (0, session_1.enterFlow)(targetSession, targetFlow.id, targetFlow.title);
|
|
191
131
|
// Merge initial data if available
|
|
192
132
|
if (targetFlow.initialData) {
|
|
@@ -295,7 +235,7 @@ class ResponsePipeline {
|
|
|
295
235
|
completedFlows = orchestration.completedFlows || [];
|
|
296
236
|
// Log if flow is complete
|
|
297
237
|
if (isFlowComplete) {
|
|
298
|
-
utils_1.logger.debug(`[
|
|
238
|
+
utils_1.logger.debug(`[ResponsePipeline] Flow complete: all required data collected or last step reached`);
|
|
299
239
|
}
|
|
300
240
|
}
|
|
301
241
|
return {
|
|
@@ -311,7 +251,7 @@ class ResponsePipeline {
|
|
|
311
251
|
* Determine next step and update session
|
|
312
252
|
*/
|
|
313
253
|
async determineNextStep(params) {
|
|
314
|
-
const { selectedFlow, selectedStep, session, isFlowComplete, busDirective } = params;
|
|
254
|
+
const { selectedFlow, selectedStep, session, isFlowComplete, context, busDirective } = params;
|
|
315
255
|
if (!selectedFlow) {
|
|
316
256
|
return { nextStep: undefined, session };
|
|
317
257
|
}
|
|
@@ -321,7 +261,7 @@ class ResponsePipeline {
|
|
|
321
261
|
// complete, abort, reset), that decision wins the turn. Do NOT evaluate
|
|
322
262
|
// branches or linear/AI selection — the caller applies the bus directive.
|
|
323
263
|
if (hasDirectivePositionField(busDirective)) {
|
|
324
|
-
utils_1.logger.debug(`[
|
|
264
|
+
utils_1.logger.debug(`[ResponsePipeline] Directive bus winner has position field — skipping branch evaluation and linear/AI selection`);
|
|
325
265
|
return { nextStep: undefined, session };
|
|
326
266
|
}
|
|
327
267
|
// STEP 1 (Algorithm 1): branches win over linear chain AND flow completion.
|
|
@@ -332,8 +272,7 @@ class ResponsePipeline {
|
|
|
332
272
|
? selectedFlow.getStep(session.currentStep.id)
|
|
333
273
|
: undefined;
|
|
334
274
|
if (currentStep?.branches && currentStep.branches.length > 0) {
|
|
335
|
-
const
|
|
336
|
-
const branchResult = await this.evaluateStepBranches(currentStep, selectedFlow, session, contextToUse);
|
|
275
|
+
const branchResult = await this.evaluateStepBranches(currentStep, selectedFlow, session, context);
|
|
337
276
|
if (branchResult) {
|
|
338
277
|
return branchResult;
|
|
339
278
|
}
|
|
@@ -353,13 +292,12 @@ class ResponsePipeline {
|
|
|
353
292
|
const currentStep = session.currentFlow?.id === selectedFlow.id && session.currentStep
|
|
354
293
|
? selectedFlow.getStep(session.currentStep.id)
|
|
355
294
|
: undefined;
|
|
356
|
-
const contextToUse = this.getStoredContext();
|
|
357
295
|
// Get candidate steps based on current position in the flow
|
|
358
296
|
const candidates = await this.flowRouter.getCandidateStepsWithConditions(selectedFlow, currentStep, // Pass current step instead of undefined to maintain progression
|
|
359
|
-
(0, template_1.createTemplateContext)({ data: session.data, session, context
|
|
297
|
+
(0, template_1.createTemplateContext)({ data: session.data, session, context }));
|
|
360
298
|
if (candidates.length > 0) {
|
|
361
299
|
nextStep = candidates[0].step;
|
|
362
|
-
utils_1.logger.debug(`[
|
|
300
|
+
utils_1.logger.debug(`[ResponsePipeline] Using first valid step: ${nextStep.id}${currentStep ? ' (progressing from ' + currentStep.id + ')' : ' for new flow'}`);
|
|
363
301
|
}
|
|
364
302
|
else {
|
|
365
303
|
// Fallback to initial step even if it should be skipped
|
|
@@ -372,7 +310,7 @@ class ResponsePipeline {
|
|
|
372
310
|
const sessionData = session.data || {};
|
|
373
311
|
const missingRequires = nextStep.requires.filter(field => sessionData[String(field)] === undefined);
|
|
374
312
|
if (missingRequires.length > 0) {
|
|
375
|
-
utils_1.logger.debug(`[
|
|
313
|
+
utils_1.logger.debug(`[ResponsePipeline] Cannot enter step "${nextStep.id}": missing required fields [${missingRequires.join(', ')}]. Staying at current step.`);
|
|
376
314
|
// Stay at current step - don't enter the next one
|
|
377
315
|
const currentStepId = session.currentStep?.id;
|
|
378
316
|
if (currentStepId && selectedFlow) {
|
|
@@ -386,7 +324,7 @@ class ResponsePipeline {
|
|
|
386
324
|
}
|
|
387
325
|
// Update session with next step
|
|
388
326
|
const updatedSession = (0, utils_1.enterStep)(session, nextStep.id, nextStep.description);
|
|
389
|
-
utils_1.logger.debug(`[
|
|
327
|
+
utils_1.logger.debug(`[ResponsePipeline] Entered step: ${nextStep.id}`);
|
|
390
328
|
return { nextStep, session: updatedSession };
|
|
391
329
|
}
|
|
392
330
|
/**
|
|
@@ -425,7 +363,7 @@ class ResponsePipeline {
|
|
|
425
363
|
const localStep = selectedFlow.getStep(result);
|
|
426
364
|
if (localStep) {
|
|
427
365
|
const updatedSession = (0, utils_1.enterStep)(session, localStep.id, localStep.description);
|
|
428
|
-
utils_1.logger.debug(`[
|
|
366
|
+
utils_1.logger.debug(`[ResponsePipeline] Branch resolved to local step: ${localStep.id}`);
|
|
429
367
|
return { nextStep: localStep, session: updatedSession };
|
|
430
368
|
}
|
|
431
369
|
// 2. Look up in agent's flow registry
|
|
@@ -433,7 +371,7 @@ class ResponsePipeline {
|
|
|
433
371
|
const targetFlow = flows.find(f => f.id === result || f.title === result);
|
|
434
372
|
if (targetFlow) {
|
|
435
373
|
// Treat as applyDirective({ goTo: result }) — enter the target flow
|
|
436
|
-
utils_1.logger.debug(`[
|
|
374
|
+
utils_1.logger.debug(`[ResponsePipeline] Branch resolved to flow: ${targetFlow.title}`);
|
|
437
375
|
const updatedSession = (0, session_1.enterFlow)(session, targetFlow.id, targetFlow.title);
|
|
438
376
|
return { nextStep: undefined, session: updatedSession, flowChanged: targetFlow };
|
|
439
377
|
}
|
|
@@ -471,7 +409,7 @@ class ResponsePipeline {
|
|
|
471
409
|
updatedSession = (0, utils_1.enterStep)(updatedSession, stepTarget);
|
|
472
410
|
// Try to resolve the target step instance for the caller
|
|
473
411
|
const targetStepInstance = targetFlow.getStep(stepTarget);
|
|
474
|
-
utils_1.logger.debug(`[
|
|
412
|
+
utils_1.logger.debug(`[ResponsePipeline] Branch directive goToStep → ${flowTarget}.${stepTarget}`);
|
|
475
413
|
return { nextStep: targetStepInstance || undefined, session: updatedSession, flowChanged: targetFlow };
|
|
476
414
|
}
|
|
477
415
|
throw new Step_1.FlowConfigurationError(`[FlowConfigurationError] Branch directive goToStep targets unknown flow: "${flowTarget}" does not match any flow id or title. ` +
|
|
@@ -481,7 +419,7 @@ class ResponsePipeline {
|
|
|
481
419
|
const targetStep = selectedFlow.getStep(stepTarget);
|
|
482
420
|
if (targetStep) {
|
|
483
421
|
updatedSession = (0, utils_1.enterStep)(updatedSession, targetStep.id, targetStep.description);
|
|
484
|
-
utils_1.logger.debug(`[
|
|
422
|
+
utils_1.logger.debug(`[ResponsePipeline] Branch directive goToStep → ${targetStep.id}`);
|
|
485
423
|
return { nextStep: targetStep, session: updatedSession };
|
|
486
424
|
}
|
|
487
425
|
throw new Step_1.FlowConfigurationError(`[FlowConfigurationError] Branch directive goToStep targets unknown step: "${stepTarget}" does not exist in flow "${selectedFlow.id}". ` +
|
|
@@ -500,7 +438,7 @@ class ResponsePipeline {
|
|
|
500
438
|
if (typeof directive.goTo === 'object' && directive.goTo.step) {
|
|
501
439
|
updatedSession = (0, utils_1.enterStep)(updatedSession, directive.goTo.step);
|
|
502
440
|
}
|
|
503
|
-
utils_1.logger.debug(`[
|
|
441
|
+
utils_1.logger.debug(`[ResponsePipeline] Branch directive goTo → ${targetFlow.title}`);
|
|
504
442
|
return { nextStep: undefined, session: updatedSession, flowChanged: targetFlow };
|
|
505
443
|
}
|
|
506
444
|
throw new Step_1.FlowConfigurationError(`[FlowConfigurationError] Branch directive goTo targets unknown flow: "${flowTarget}" does not match any flow id or title. ` +
|
|
@@ -508,11 +446,11 @@ class ResponsePipeline {
|
|
|
508
446
|
}
|
|
509
447
|
}
|
|
510
448
|
if (directive.complete) {
|
|
511
|
-
utils_1.logger.debug(`[
|
|
449
|
+
utils_1.logger.debug(`[ResponsePipeline] Branch directive complete`);
|
|
512
450
|
return { nextStep: undefined, session: updatedSession };
|
|
513
451
|
}
|
|
514
452
|
if (directive.abort) {
|
|
515
|
-
utils_1.logger.debug(`[
|
|
453
|
+
utils_1.logger.debug(`[ResponsePipeline] Branch directive abort`);
|
|
516
454
|
return { nextStep: undefined, session: updatedSession };
|
|
517
455
|
}
|
|
518
456
|
if (directive.reset) {
|
|
@@ -529,7 +467,7 @@ class ResponsePipeline {
|
|
|
529
467
|
// Reset to initial step
|
|
530
468
|
const initialStep = selectedFlow.initialStep;
|
|
531
469
|
updatedSession = (0, utils_1.enterStep)(updatedSession, initialStep.id, initialStep.description);
|
|
532
|
-
utils_1.logger.debug(`[
|
|
470
|
+
utils_1.logger.debug(`[ResponsePipeline] Branch directive reset → ${initialStep.id}`);
|
|
533
471
|
return { nextStep: initialStep, session: updatedSession };
|
|
534
472
|
}
|
|
535
473
|
// Directive with only non-position fields (e.g., just dataUpdate/contextUpdate/reply)
|
|
@@ -537,407 +475,420 @@ class ResponsePipeline {
|
|
|
537
475
|
return { nextStep: undefined, session: updatedSession };
|
|
538
476
|
}
|
|
539
477
|
/**
|
|
540
|
-
*
|
|
478
|
+
* TURN ROUTING + STEP SELECTION — the single entry point for deciding which
|
|
479
|
+
* flow and step a turn renders. Runs the routing-skip optimization, the
|
|
480
|
+
* pre-signal phase (parallel with routing when a processor is configured),
|
|
481
|
+
* pre-extraction, and next-step determination.
|
|
541
482
|
*/
|
|
542
|
-
async
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
483
|
+
async routeAndSelectStep(params) {
|
|
484
|
+
try {
|
|
485
|
+
// Create a fresh chain tracker for this turn (Requirement 22.1)
|
|
486
|
+
this.createChainTracker();
|
|
487
|
+
// ROUTING SKIP OPTIMIZATION (Requirements 20.1, 20.2, 20.3):
|
|
488
|
+
// When the current step has collect fields AND pre-extraction populates at least
|
|
489
|
+
// one of those fields, skip FlowRouter.decideFlowAndStep for this turn.
|
|
490
|
+
const routingSkipResult = await this.attemptRoutingSkipForCollect(params);
|
|
491
|
+
if (routingSkipResult) {
|
|
492
|
+
// Even when routing is skipped, run pre-signal phase if processor is present
|
|
493
|
+
if (this.signalCoordinator.enabled) {
|
|
494
|
+
const signalResult = await this.signalCoordinator.runPrePhase(params.session, params.context, params.history);
|
|
495
|
+
// If signal halts, override the routing skip result
|
|
496
|
+
if (signalResult.mergedDirective?.halt) {
|
|
497
|
+
return {
|
|
498
|
+
...routingSkipResult,
|
|
499
|
+
session: signalResult.updatedSession,
|
|
500
|
+
signalFirings: signalResult.firings,
|
|
501
|
+
signalHalted: true,
|
|
502
|
+
signalHaltReply: signalResult.mergedDirective.reply,
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
// If signal has position fields, override routing skip result
|
|
506
|
+
if (hasDirectivePositionField(signalResult.mergedDirective)) {
|
|
507
|
+
return this.signalCoordinator.applyPositionDirective(signalResult);
|
|
508
|
+
}
|
|
509
|
+
// Non-position directive: propagate for pre-LLM augmentation
|
|
510
|
+
return {
|
|
511
|
+
...routingSkipResult,
|
|
512
|
+
session: signalResult.updatedSession,
|
|
513
|
+
signalFirings: signalResult.firings,
|
|
514
|
+
signalPreDirective: signalResult.mergedDirective || undefined,
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
return routingSkipResult;
|
|
556
518
|
}
|
|
557
|
-
//
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
519
|
+
// ── PARALLEL PRE-SIGNAL PHASE + ROUTING (Algorithm 5) ────────────────
|
|
520
|
+
// When signalProcessor is present, run pre-signals in parallel with routing.
|
|
521
|
+
// When absent, call the router directly (zero overhead, preserve current behavior).
|
|
522
|
+
if (this.signalCoordinator.enabled) {
|
|
523
|
+
// Run pre-signal phase in parallel with routing (Requirement 8.1)
|
|
524
|
+
const [signalResult, routingResult] = await Promise.all([
|
|
525
|
+
this.signalCoordinator.runPrePhase(params.session, params.context, params.history),
|
|
526
|
+
this.handleRoutingAndStepSelection({
|
|
527
|
+
session: params.session,
|
|
528
|
+
history: params.history,
|
|
529
|
+
context: params.context,
|
|
530
|
+
signal: params.signal,
|
|
531
|
+
}),
|
|
532
|
+
]);
|
|
533
|
+
// ── Requirement 8.2: halt → discard routing, skip LLM ────────────
|
|
534
|
+
if (signalResult.mergedDirective?.halt) {
|
|
535
|
+
return {
|
|
536
|
+
selectedFlow: undefined,
|
|
537
|
+
selectedStep: undefined,
|
|
538
|
+
session: signalResult.updatedSession,
|
|
539
|
+
isFlowComplete: false,
|
|
540
|
+
signalFirings: signalResult.firings,
|
|
541
|
+
signalHalted: true,
|
|
542
|
+
signalHaltReply: signalResult.mergedDirective.reply,
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
// ── Requirement 8.3: position directive → discard routing, apply signal position ──
|
|
546
|
+
if (hasDirectivePositionField(signalResult.mergedDirective)) {
|
|
547
|
+
return this.signalCoordinator.applyPositionDirective(signalResult);
|
|
548
|
+
}
|
|
549
|
+
// ── Requirement 8.4: non-position directive → use routing, propagate augmentation ──
|
|
550
|
+
// ── Requirement 8.5: no directive → use routing as-is ─────────────
|
|
551
|
+
let updatedSession = signalResult.updatedSession;
|
|
552
|
+
// Apply data/context updates from signal to the routed session
|
|
553
|
+
if (signalResult.mergedDirective?.dataUpdate) {
|
|
554
|
+
updatedSession = (0, utils_1.mergeCollected)(updatedSession, signalResult.mergedDirective.dataUpdate);
|
|
555
|
+
}
|
|
556
|
+
// Use routing result for flow/step, but carry signal session state
|
|
557
|
+
// Merge routing session changes on top of signal session
|
|
558
|
+
const routingSession = routingResult.session;
|
|
559
|
+
updatedSession = {
|
|
560
|
+
...updatedSession,
|
|
561
|
+
currentFlow: routingSession.currentFlow,
|
|
562
|
+
currentStep: routingSession.currentStep,
|
|
563
|
+
flowHistory: routingSession.flowHistory,
|
|
564
|
+
pendingDirective: routingSession.pendingDirective,
|
|
565
|
+
};
|
|
566
|
+
const isFlowComplete = routingResult.isFlowComplete;
|
|
567
|
+
// PRE-EXTRACTION: same logic as below — extract data from user message
|
|
568
|
+
if (routingResult.selectedFlow && !isFlowComplete) {
|
|
569
|
+
if (this.shouldPreExtractData(routingResult.selectedFlow)) {
|
|
570
|
+
utils_1.logger.debug(`[ResponsePipeline] Pre-extracting data for flow: ${routingResult.selectedFlow.title}`);
|
|
571
|
+
const extractedData = await this.preExtractFlowData({
|
|
572
|
+
route: routingResult.selectedFlow,
|
|
573
|
+
history: params.history,
|
|
574
|
+
context: params.context,
|
|
575
|
+
session: updatedSession,
|
|
576
|
+
signal: params.signal,
|
|
577
|
+
});
|
|
578
|
+
if (extractedData && Object.keys(extractedData).length > 0) {
|
|
579
|
+
utils_1.logger.debug(`[ResponsePipeline] Pre-extracted data:`, extractedData);
|
|
580
|
+
updatedSession = (0, utils_1.mergeCollected)(updatedSession, extractedData);
|
|
581
|
+
await this.updateCollectedData(extractedData);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
// Determine next step
|
|
586
|
+
const stepResult = await this.determineNextStep({
|
|
587
|
+
selectedFlow: routingResult.selectedFlow,
|
|
588
|
+
selectedStep: routingResult.selectedStep,
|
|
589
|
+
session: updatedSession,
|
|
590
|
+
isFlowComplete,
|
|
591
|
+
context: params.context,
|
|
567
592
|
});
|
|
593
|
+
return {
|
|
594
|
+
selectedFlow: stepResult.flowChanged || routingResult.selectedFlow,
|
|
595
|
+
selectedStep: stepResult.nextStep,
|
|
596
|
+
responseDirectives: routingResult.responseDirectives,
|
|
597
|
+
session: stepResult.session,
|
|
598
|
+
isFlowComplete: stepResult.flowChanged ? false : isFlowComplete,
|
|
599
|
+
signalFirings: signalResult.firings,
|
|
600
|
+
signalPreDirective: signalResult.mergedDirective || undefined,
|
|
601
|
+
};
|
|
568
602
|
}
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
};
|
|
592
|
-
}
|
|
593
|
-
/**
|
|
594
|
-
* Execute tool loop for follow-up tool calls
|
|
595
|
-
*/
|
|
596
|
-
async executeToolLoop(params) {
|
|
597
|
-
const { initialToolCalls, selectedFlow, nextStep, responsePrompt, history, context, session, responseSchema, isStreaming = false, } = params;
|
|
598
|
-
const MAX_TOOL_LOOPS = 5;
|
|
599
|
-
let toolLoopCount = 0;
|
|
600
|
-
let currentToolCalls = initialToolCalls;
|
|
601
|
-
let hasToolCalls = currentToolCalls && currentToolCalls.length > 0;
|
|
602
|
-
let currentSession = session;
|
|
603
|
-
// Track tool results across loop iterations for history
|
|
604
|
-
let currentToolResults = new Map();
|
|
605
|
-
while (hasToolCalls && toolLoopCount < MAX_TOOL_LOOPS) {
|
|
606
|
-
toolLoopCount++;
|
|
607
|
-
utils_1.logger.debug(`[ResponseHandler] Starting ${isStreaming ? "streaming " : ""}tool loop ${toolLoopCount}/${MAX_TOOL_LOOPS}`);
|
|
608
|
-
// Add tool execution results to history so AI knows what happened
|
|
609
|
-
const toolResultItems = [];
|
|
610
|
-
for (const toolCall of currentToolCalls || []) {
|
|
611
|
-
const tool = this.findAvailableTool(toolCall.toolName, selectedFlow);
|
|
612
|
-
if (tool) {
|
|
613
|
-
toolResultItems.push({
|
|
614
|
-
role: "assistant",
|
|
615
|
-
content: null,
|
|
616
|
-
tool_calls: [{
|
|
617
|
-
id: toolCall.toolName,
|
|
618
|
-
name: toolCall.toolName,
|
|
619
|
-
arguments: toolCall.arguments,
|
|
620
|
-
}],
|
|
621
|
-
});
|
|
622
|
-
toolResultItems.push({
|
|
623
|
-
role: "tool",
|
|
624
|
-
tool_call_id: toolCall.toolName,
|
|
625
|
-
name: toolCall.toolName,
|
|
626
|
-
content: currentToolResults.get(toolCall.toolName) || "Tool executed successfully",
|
|
603
|
+
// ── No signal processor: existing behavior (zero overhead) ────────────
|
|
604
|
+
const routingResult = await this.handleRoutingAndStepSelection({
|
|
605
|
+
session: params.session,
|
|
606
|
+
history: params.history,
|
|
607
|
+
context: params.context,
|
|
608
|
+
signal: params.signal,
|
|
609
|
+
});
|
|
610
|
+
let updatedSession = routingResult.session;
|
|
611
|
+
const isFlowComplete = routingResult.isFlowComplete;
|
|
612
|
+
// PRE-EXTRACTION: If entering a flow that collects data, extract data from user message first
|
|
613
|
+
// This allows us to skip steps whose data is already provided
|
|
614
|
+
if (routingResult.selectedFlow && !isFlowComplete) {
|
|
615
|
+
// Always pre-extract when flow collects data (not just on new flow entry)
|
|
616
|
+
// This ensures step selection has the most up-to-date data
|
|
617
|
+
if (this.shouldPreExtractData(routingResult.selectedFlow)) {
|
|
618
|
+
utils_1.logger.debug(`[ResponsePipeline] Pre-extracting data for flow: ${routingResult.selectedFlow.title}`);
|
|
619
|
+
const extractedData = await this.preExtractFlowData({
|
|
620
|
+
route: routingResult.selectedFlow,
|
|
621
|
+
history: params.history,
|
|
622
|
+
context: params.context,
|
|
623
|
+
session: updatedSession,
|
|
624
|
+
signal: params.signal,
|
|
627
625
|
});
|
|
626
|
+
if (extractedData && Object.keys(extractedData).length > 0) {
|
|
627
|
+
utils_1.logger.debug(`[ResponsePipeline] Pre-extracted data:`, extractedData);
|
|
628
|
+
// Merge pre-extracted data into session before step selection
|
|
629
|
+
updatedSession = (0, utils_1.mergeCollected)(updatedSession, extractedData);
|
|
630
|
+
// Also update agent's collected data
|
|
631
|
+
await this.updateCollectedData(extractedData);
|
|
632
|
+
}
|
|
628
633
|
}
|
|
629
634
|
}
|
|
630
|
-
//
|
|
631
|
-
const
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
context,
|
|
637
|
-
tools: this.collectAvailableTools(selectedFlow, nextStep),
|
|
638
|
-
parameters: {
|
|
639
|
-
jsonSchema: responseSchema,
|
|
640
|
-
schemaName: isStreaming ? "tool_followup_streaming" : "tool_followup",
|
|
641
|
-
},
|
|
635
|
+
// Determine next step using pipeline method for consistency
|
|
636
|
+
const stepResult = await this.determineNextStep({
|
|
637
|
+
selectedFlow: routingResult.selectedFlow,
|
|
638
|
+
selectedStep: routingResult.selectedStep,
|
|
639
|
+
session: updatedSession, // Use updated session with pre-extracted data
|
|
640
|
+
isFlowComplete, // Use updated completion status
|
|
641
|
+
context: params.context,
|
|
642
642
|
});
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
//
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
selectedFlow,
|
|
652
|
-
context,
|
|
653
|
-
session: currentSession,
|
|
654
|
-
history: (0, utils_1.historyToEvents)(updatedHistory),
|
|
655
|
-
isStreaming,
|
|
656
|
-
});
|
|
657
|
-
currentSession = toolResult.session;
|
|
658
|
-
currentToolCalls = followUpToolCalls;
|
|
659
|
-
currentToolResults = toolResult.toolResults || new Map();
|
|
660
|
-
}
|
|
661
|
-
else {
|
|
662
|
-
utils_1.logger.debug(`[ResponseHandler] ${isStreaming ? "Streaming " : ""}Tool loop completed after ${toolLoopCount} iterations`);
|
|
663
|
-
// Update final toolCalls from follow-up result if no more tools
|
|
664
|
-
currentToolCalls = followUpToolCalls || [];
|
|
665
|
-
break;
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
if (toolLoopCount >= MAX_TOOL_LOOPS) {
|
|
669
|
-
utils_1.logger.warn(`[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.`);
|
|
670
|
-
}
|
|
671
|
-
return {
|
|
672
|
-
session: currentSession,
|
|
673
|
-
toolCalls: currentToolCalls,
|
|
674
|
-
};
|
|
675
|
-
}
|
|
676
|
-
/**
|
|
677
|
-
* Handle data collection from structured response
|
|
678
|
-
*/
|
|
679
|
-
async handleDataCollection(params) {
|
|
680
|
-
const { structured, nextStep, session } = params;
|
|
681
|
-
if (!structured || !nextStep.collect) {
|
|
682
|
-
return { session };
|
|
683
|
-
}
|
|
684
|
-
const collectedData = {};
|
|
685
|
-
// The structured response includes both base fields and collected extraction fields
|
|
686
|
-
const structuredData = structured;
|
|
687
|
-
for (const field of nextStep.collect) {
|
|
688
|
-
const fieldKey = field;
|
|
689
|
-
if (fieldKey in structuredData) {
|
|
690
|
-
collectedData[fieldKey] = structuredData[fieldKey];
|
|
691
|
-
}
|
|
692
|
-
}
|
|
693
|
-
let updatedSession = session;
|
|
694
|
-
if (Object.keys(collectedData).length > 0) {
|
|
695
|
-
// Update agent-level collected data with validation if available
|
|
696
|
-
if (this.updateCollectedData) {
|
|
697
|
-
await this.updateCollectedData(collectedData);
|
|
698
|
-
}
|
|
699
|
-
// Update session with validated data
|
|
700
|
-
updatedSession = await this.updateData(session, collectedData);
|
|
701
|
-
utils_1.logger.debug(`[ResponseHandler] Collected data:`, collectedData);
|
|
643
|
+
return {
|
|
644
|
+
selectedFlow: stepResult.flowChanged || routingResult.selectedFlow,
|
|
645
|
+
selectedStep: stepResult.nextStep, // Use the determined next step
|
|
646
|
+
responseDirectives: routingResult.responseDirectives,
|
|
647
|
+
session: stepResult.session,
|
|
648
|
+
// If a branch changed the flow, the original isFlowComplete no longer applies
|
|
649
|
+
isFlowComplete: stepResult.flowChanged ? false : isFlowComplete,
|
|
650
|
+
};
|
|
702
651
|
}
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
collectedData,
|
|
706
|
-
};
|
|
707
|
-
}
|
|
708
|
-
/**
|
|
709
|
-
* Handle context updates from structured response
|
|
710
|
-
*/
|
|
711
|
-
async handleContextUpdate(structured) {
|
|
712
|
-
if (structured &&
|
|
713
|
-
typeof structured === "object" &&
|
|
714
|
-
"contextUpdate" in structured) {
|
|
715
|
-
await this.updateContext(structured
|
|
716
|
-
.contextUpdate);
|
|
652
|
+
catch (error) {
|
|
653
|
+
throw ResponseGenerationError_1.ResponseGenerationError.fromError(error, 'routing_optimization', params);
|
|
717
654
|
}
|
|
718
655
|
}
|
|
719
656
|
/**
|
|
720
|
-
*
|
|
657
|
+
* RENDER-STEP RESOLUTION — resolve the step a flow response will render,
|
|
658
|
+
* shared by the streaming and non-streaming paths. When no step was
|
|
659
|
+
* pre-selected: branches win over the linear chain, then candidate steps,
|
|
660
|
+
* then the initial-step fallback. Enforces `requires` (stays at the current
|
|
661
|
+
* step when required fields are missing) and enters the resolved step.
|
|
721
662
|
*
|
|
722
|
-
*
|
|
723
|
-
*
|
|
724
|
-
*
|
|
725
|
-
* emits no message of its own at the completion boundary; callers that
|
|
726
|
-
* need closing copy should add a final interactive step.
|
|
663
|
+
* Returns `flowTransition: true` when a branch resolved to a flow
|
|
664
|
+
* transition or completion — there is no local step to render and the
|
|
665
|
+
* caller handles the transition.
|
|
727
666
|
*/
|
|
728
|
-
async
|
|
729
|
-
const { selectedFlow,
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
? [
|
|
736
|
-
...(selectedFlow.requiredFields ?? []),
|
|
737
|
-
...(selectedFlow.optionalFields ?? []),
|
|
738
|
-
]
|
|
739
|
-
: undefined;
|
|
740
|
-
let updatedSession = (0, utils_1.completeCurrentFlow)(session, {
|
|
741
|
-
clearOwnedFields: ownedFields,
|
|
742
|
-
});
|
|
743
|
-
if (transitionConfig) {
|
|
744
|
-
// Extract target from directive's goTo field
|
|
745
|
-
const goToTarget = typeof transitionConfig.goTo === 'string'
|
|
746
|
-
? transitionConfig.goTo
|
|
747
|
-
: transitionConfig.goTo?.flow;
|
|
748
|
-
// Find target flow by ID or title
|
|
749
|
-
const targetFlow = goToTarget ? this.getFlows().find((r) => r.id === goToTarget ||
|
|
750
|
-
r.title === goToTarget) : undefined;
|
|
751
|
-
if (targetFlow) {
|
|
752
|
-
updatedSession = {
|
|
753
|
-
...updatedSession,
|
|
754
|
-
pendingDirective: {
|
|
755
|
-
goTo: targetFlow.id,
|
|
756
|
-
},
|
|
757
|
-
};
|
|
758
|
-
utils_1.logger.debug(`[ResponseHandler] Flow ${selectedFlow.title} completed with pending directive to: ${targetFlow.title}`);
|
|
759
|
-
return { session: updatedSession, hasTransition: true };
|
|
760
|
-
}
|
|
761
|
-
else if (goToTarget) {
|
|
762
|
-
utils_1.logger.warn(`[FlowConfigurationError] onComplete target not found: flow "${selectedFlow.title}" completed but onComplete target "${goToTarget}" does not match any flow. ` +
|
|
763
|
-
`Fix the onComplete value to reference an existing flow id/title, or remove onComplete to release the session to idle.`);
|
|
764
|
-
}
|
|
765
|
-
}
|
|
766
|
-
utils_1.logger.debug(`[ResponseHandler] Flow ${selectedFlow.title} completed; session released to idle.`);
|
|
767
|
-
return { session: updatedSession, hasTransition: false };
|
|
768
|
-
}
|
|
769
|
-
/**
|
|
770
|
-
* Find an available tool by name for the given flow using ToolManager
|
|
771
|
-
* Delegates to ToolManager for unified tool resolution
|
|
772
|
-
*/
|
|
773
|
-
findAvailableTool(toolName, flow) {
|
|
774
|
-
// Use ToolManager for unified tool resolution if available
|
|
775
|
-
if (this.toolManager) {
|
|
776
|
-
return this.toolManager.find(toolName, undefined, undefined, flow);
|
|
777
|
-
}
|
|
778
|
-
// Fallback to legacy resolution if ToolManager not available
|
|
779
|
-
utils_1.logger.warn(`[ResponsePipeline] ToolManager not available, using legacy tool resolution for: ${toolName}`);
|
|
780
|
-
// Check flow-level tools first (if flow provided)
|
|
781
|
-
if (flow) {
|
|
782
|
-
const flowTool = flow
|
|
783
|
-
.getTools()
|
|
784
|
-
.find((tool) => tool.id === toolName || tool.id === toolName);
|
|
785
|
-
if (flowTool)
|
|
786
|
-
return flowTool;
|
|
787
|
-
}
|
|
788
|
-
// Fall back to agent-level tools
|
|
789
|
-
return this.tools.find((tool) => tool.id === toolName || tool.id === toolName);
|
|
790
|
-
}
|
|
791
|
-
/**
|
|
792
|
-
* Collect all available tools for the given flow and step context using ToolManager
|
|
793
|
-
* Delegates to ToolManager for unified tool resolution and deduplication
|
|
794
|
-
*/
|
|
795
|
-
collectAvailableTools(flow, step) {
|
|
796
|
-
// Use ToolManager for unified tool collection if available
|
|
797
|
-
if (this.toolManager) {
|
|
798
|
-
const availableTools = this.toolManager.getAvailable(undefined, step, flow);
|
|
799
|
-
return availableTools.map((tool) => ({
|
|
800
|
-
id: tool.id,
|
|
801
|
-
description: tool.description,
|
|
802
|
-
parameters: tool.parameters,
|
|
803
|
-
}));
|
|
804
|
-
}
|
|
805
|
-
// Fallback to legacy collection logic if ToolManager not available
|
|
806
|
-
utils_1.logger.warn(`[ResponsePipeline] ToolManager not available, using legacy tool collection`);
|
|
807
|
-
const availableTools = new Map();
|
|
808
|
-
// Add agent-level tools
|
|
809
|
-
this.tools.forEach((tool) => {
|
|
810
|
-
availableTools.set(tool.id, tool);
|
|
811
|
-
});
|
|
812
|
-
// Add flow-level tools (these take precedence)
|
|
813
|
-
if (flow) {
|
|
814
|
-
flow.getTools().forEach((tool) => {
|
|
815
|
-
availableTools.set(tool.id, tool);
|
|
816
|
-
});
|
|
667
|
+
async resolveRenderStep(params) {
|
|
668
|
+
const { selectedFlow, selectedStep, context } = params;
|
|
669
|
+
let session = params.session;
|
|
670
|
+
// Determine next step
|
|
671
|
+
let nextStep;
|
|
672
|
+
if (selectedStep) {
|
|
673
|
+
nextStep = selectedStep;
|
|
817
674
|
}
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
const
|
|
821
|
-
const
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
675
|
+
else {
|
|
676
|
+
// Determine current step from session if we're already in this flow
|
|
677
|
+
const isInSameFlow = session.currentFlow?.id === selectedFlow.id;
|
|
678
|
+
const currentStep = isInSameFlow && session.currentStep
|
|
679
|
+
? selectedFlow.getStep(session.currentStep.id)
|
|
680
|
+
: undefined;
|
|
681
|
+
utils_1.logger.debug(`[ResponsePipeline] Step determination: flow match=${isInSameFlow}, currentFlow=${session.currentFlow?.id}, selectedFlow=${selectedFlow.id}, currentStep=${currentStep?.id || 'none'}`);
|
|
682
|
+
// STEP 1 (Algorithm 1): branches win over linear chain
|
|
683
|
+
if (currentStep?.branches && currentStep.branches.length > 0) {
|
|
684
|
+
const branchResult = await this.evaluateStepBranches(currentStep, selectedFlow, session, context);
|
|
685
|
+
if (branchResult) {
|
|
686
|
+
if (branchResult.nextStep) {
|
|
687
|
+
nextStep = branchResult.nextStep;
|
|
688
|
+
session = branchResult.session;
|
|
689
|
+
}
|
|
690
|
+
else {
|
|
691
|
+
// Flow transition or completion — no local step to render
|
|
692
|
+
return { nextStep: undefined, session: branchResult.session, flowTransition: true };
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
if (!nextStep) {
|
|
697
|
+
// Get candidate steps based on current position in the flow
|
|
698
|
+
const candidates = await this.flowRouter.getCandidateStepsWithConditions(selectedFlow, currentStep, // Pass current step instead of undefined to maintain progression
|
|
699
|
+
(0, template_1.createTemplateContext)({ data: session.data, session, context }));
|
|
700
|
+
utils_1.logger.debug(`[ResponsePipeline] Found ${candidates.length} candidate steps${currentStep ? ' from current step ' + currentStep.id : ' (new flow entry)'}`);
|
|
701
|
+
if (candidates.length > 0) {
|
|
702
|
+
nextStep = candidates[0].step;
|
|
703
|
+
utils_1.logger.debug(`[ResponsePipeline] Using first valid step: ${nextStep.id}${currentStep ? ' (progressing from ' + currentStep.id + ')' : ' for new flow'}`);
|
|
826
704
|
}
|
|
827
705
|
else {
|
|
828
|
-
//
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
stepTools.push(toolRef);
|
|
832
|
-
}
|
|
706
|
+
// Fallback to initial step even if it should be skipped
|
|
707
|
+
nextStep = selectedFlow.initialStep;
|
|
708
|
+
utils_1.logger.warn(`[FlowConfigurationError] No valid steps found: all candidates were skipped in flow. Falling back to initial step "${nextStep.id}". Review step skip conditions.`);
|
|
833
709
|
}
|
|
834
710
|
}
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
711
|
+
}
|
|
712
|
+
// Update session with next step
|
|
713
|
+
// If the next step has requires fields that are missing, stay at the previous step
|
|
714
|
+
if (nextStep.requires && nextStep.requires.length > 0) {
|
|
715
|
+
const sessionData = session.data || {};
|
|
716
|
+
const missingRequires = nextStep.requires.filter(field => sessionData[String(field)] === undefined);
|
|
717
|
+
if (missingRequires.length > 0) {
|
|
718
|
+
const warning = `[FlowConfigurationError] Cannot advance to step "${nextStep.description || nextStep.id}": ` +
|
|
719
|
+
`missing required fields [${missingRequires.join(', ')}]. Staying at current step. Ensure preceding steps collect these fields.`;
|
|
720
|
+
utils_1.logger.warn(warning);
|
|
721
|
+
console.warn(warning);
|
|
722
|
+
// Stay at the current step - don't enter the next one
|
|
723
|
+
const currentStepId = session.currentStep?.id;
|
|
724
|
+
if (currentStepId) {
|
|
725
|
+
const currentStepInstance = selectedFlow.getStep(currentStepId);
|
|
726
|
+
if (currentStepInstance) {
|
|
727
|
+
nextStep = currentStepInstance;
|
|
728
|
+
utils_1.logger.debug(`[ResponsePipeline] Staying at current step: ${nextStep.id} due to missing requires`);
|
|
842
729
|
}
|
|
843
730
|
}
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
}
|
|
849
|
-
});
|
|
850
|
-
availableTools.clear();
|
|
851
|
-
filteredTools.forEach((tool, id) => availableTools.set(id, tool));
|
|
731
|
+
}
|
|
732
|
+
else {
|
|
733
|
+
session = (0, utils_1.enterStep)(session, nextStep.id, nextStep.description);
|
|
734
|
+
utils_1.logger.debug(`[ResponsePipeline] Entered step: ${nextStep.id}`);
|
|
852
735
|
}
|
|
853
736
|
}
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
description: tool.description,
|
|
858
|
-
parameters: tool.parameters,
|
|
859
|
-
}));
|
|
860
|
-
}
|
|
861
|
-
/**
|
|
862
|
-
* Get current context (fetches from provider if configured)
|
|
863
|
-
*/
|
|
864
|
-
async getContext() {
|
|
865
|
-
// If context provider is configured, use it to fetch fresh context
|
|
866
|
-
if (this.options.contextProvider) {
|
|
867
|
-
return await this.options.contextProvider();
|
|
737
|
+
else {
|
|
738
|
+
session = (0, utils_1.enterStep)(session, nextStep.id, nextStep.description);
|
|
739
|
+
utils_1.logger.debug(`[ResponsePipeline] Entered step: ${nextStep.id}`);
|
|
868
740
|
}
|
|
869
|
-
|
|
870
|
-
return this.context;
|
|
871
|
-
}
|
|
872
|
-
// Setters for context and current session (needed for beforeRespond hook)
|
|
873
|
-
setContext(context) {
|
|
874
|
-
this.context = context;
|
|
875
|
-
}
|
|
876
|
-
setCurrentSession(session) {
|
|
877
|
-
this.currentSession = session;
|
|
878
|
-
}
|
|
879
|
-
getStoredContext() {
|
|
880
|
-
return this.context;
|
|
881
|
-
}
|
|
882
|
-
getCurrentSession() {
|
|
883
|
-
return this.currentSession;
|
|
741
|
+
return { nextStep, session, flowTransition: false };
|
|
884
742
|
}
|
|
885
743
|
/**
|
|
886
|
-
*
|
|
887
|
-
*
|
|
744
|
+
* Routing skip optimization (Requirements 20.1, 20.2, 20.3):
|
|
745
|
+
* When the current step declares `collect` fields AND pre-extraction populates
|
|
746
|
+
* at least one of those fields from the user's message, skip routing for this turn.
|
|
747
|
+
*
|
|
748
|
+
* Returns the routing result if the skip applies, or undefined to fall through
|
|
749
|
+
* to normal routing.
|
|
888
750
|
*/
|
|
889
|
-
async
|
|
890
|
-
const {
|
|
891
|
-
//
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
751
|
+
async attemptRoutingSkipForCollect(params) {
|
|
752
|
+
const { session } = params;
|
|
753
|
+
// Only applies when we already have a current flow and step
|
|
754
|
+
if (!session.currentFlow || !session.currentStep) {
|
|
755
|
+
return undefined;
|
|
756
|
+
}
|
|
757
|
+
// Also skip this optimization if there's a pending directive (it takes priority)
|
|
758
|
+
if (session.pendingDirective) {
|
|
759
|
+
return undefined;
|
|
760
|
+
}
|
|
761
|
+
// Look up the actual Flow and Step objects to access `collect`
|
|
762
|
+
const currentFlow = this.getFlows().find((f) => f.id === session.currentFlow?.id);
|
|
763
|
+
if (!currentFlow) {
|
|
764
|
+
return undefined;
|
|
765
|
+
}
|
|
766
|
+
const currentStep = currentFlow.getStep(session.currentStep.id);
|
|
767
|
+
if (!currentStep || !currentStep.collect || currentStep.collect.length === 0) {
|
|
768
|
+
return undefined;
|
|
769
|
+
}
|
|
770
|
+
// We have a step with collect fields. Run pre-extraction to see if the
|
|
771
|
+
// user's message populates any of them.
|
|
772
|
+
const collectFields = currentStep.collect;
|
|
773
|
+
// Snapshot current data for comparison
|
|
774
|
+
const dataBefore = { ...session.data };
|
|
775
|
+
// Run pre-extraction against the current flow
|
|
776
|
+
const extractedData = await this.preExtractFlowData({
|
|
777
|
+
route: currentFlow,
|
|
778
|
+
history: params.history,
|
|
779
|
+
context: params.context,
|
|
780
|
+
session,
|
|
781
|
+
signal: params.signal,
|
|
782
|
+
});
|
|
783
|
+
if (!extractedData || Object.keys(extractedData).length === 0) {
|
|
784
|
+
return undefined;
|
|
785
|
+
}
|
|
786
|
+
// Determine which collect fields were newly populated by pre-extraction
|
|
787
|
+
const populatedCollectFields = [];
|
|
788
|
+
for (const field of collectFields) {
|
|
789
|
+
const key = field;
|
|
790
|
+
const hadValue = dataBefore[field] !== undefined && dataBefore[field] !== null;
|
|
791
|
+
const hasNewValue = extractedData[field] !== undefined && extractedData[field] !== null;
|
|
792
|
+
if (hasNewValue && !hadValue) {
|
|
793
|
+
populatedCollectFields.push(key);
|
|
904
794
|
}
|
|
905
795
|
}
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
796
|
+
if (populatedCollectFields.length === 0) {
|
|
797
|
+
// Pre-extraction didn't populate any declared collect field — no skip
|
|
798
|
+
return undefined;
|
|
799
|
+
}
|
|
800
|
+
// ROUTING SKIP: pre-extraction populated collect fields → retain current flow/step
|
|
801
|
+
utils_1.logger.debug(`[ResponsePipeline] Routing skip: pre-extraction populated collect fields [${populatedCollectFields.join(', ')}] for step "${currentStep.id}" — skipping FlowRouter`);
|
|
802
|
+
// Merge extracted data into session
|
|
803
|
+
const updatedSession = (0, utils_1.mergeCollected)(session, extractedData);
|
|
804
|
+
await this.updateCollectedData(extractedData);
|
|
805
|
+
// Determine next step using pipeline method for consistency
|
|
806
|
+
// Pass the current flow/step as the routing result (retained)
|
|
807
|
+
const stepResult = await this.determineNextStep({
|
|
808
|
+
selectedFlow: currentFlow,
|
|
809
|
+
selectedStep: currentStep,
|
|
810
|
+
session: updatedSession,
|
|
811
|
+
isFlowComplete: false,
|
|
812
|
+
context: params.context,
|
|
813
|
+
});
|
|
911
814
|
return {
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
815
|
+
selectedFlow: stepResult.flowChanged || currentFlow,
|
|
816
|
+
selectedStep: stepResult.nextStep,
|
|
817
|
+
responseDirectives: undefined,
|
|
818
|
+
session: stepResult.session,
|
|
819
|
+
isFlowComplete: false,
|
|
915
820
|
};
|
|
916
821
|
}
|
|
917
822
|
/**
|
|
918
|
-
*
|
|
919
|
-
* This method ensures that data updates are properly validated and propagated
|
|
823
|
+
* Check if a flow should pre-extract data before determining the initial step
|
|
920
824
|
*/
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
if (
|
|
927
|
-
|
|
928
|
-
}
|
|
929
|
-
//
|
|
930
|
-
const
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
825
|
+
shouldPreExtractData(flow) {
|
|
826
|
+
// Pre-extract if flow has declared required or optional fields
|
|
827
|
+
if (flow.requiredFields && flow.requiredFields.length > 0) {
|
|
828
|
+
return true;
|
|
829
|
+
}
|
|
830
|
+
if (flow.optionalFields && flow.optionalFields.length > 0) {
|
|
831
|
+
return true;
|
|
832
|
+
}
|
|
833
|
+
// Pre-extract if any step in the flow collects data
|
|
834
|
+
const steps = flow.getAllSteps();
|
|
835
|
+
const hasDataCollectionSteps = steps.some(step => step.collect && step.collect.length > 0);
|
|
836
|
+
return hasDataCollectionSteps;
|
|
837
|
+
}
|
|
838
|
+
/**
|
|
839
|
+
* Pre-extract data from user message when entering a flow
|
|
840
|
+
* This allows skipping steps whose data is already provided
|
|
841
|
+
*/
|
|
842
|
+
async preExtractFlowData(params) {
|
|
843
|
+
const { route: flow, history, signal } = params;
|
|
844
|
+
// Build a schema for data extraction based on flow's fields
|
|
845
|
+
const extractionSchema = this.getSchema();
|
|
846
|
+
if (!extractionSchema) {
|
|
847
|
+
utils_1.logger.warn(`[ResponsePipeline] No schema available for pre-extraction`);
|
|
848
|
+
return {};
|
|
849
|
+
}
|
|
850
|
+
// Get last user message
|
|
851
|
+
const lastMessage = (0, utils_1.getLastMessageFromHistory)(history);
|
|
852
|
+
// Build extraction prompt
|
|
853
|
+
const extractionPrompt = [
|
|
854
|
+
`Extract any relevant information from the user's message that matches the following data fields.`,
|
|
855
|
+
`Only extract information that is explicitly stated or clearly implied.`,
|
|
856
|
+
``,
|
|
857
|
+
`User's message: "${lastMessage}"`,
|
|
858
|
+
``,
|
|
859
|
+
`Extract data for these fields if present:`,
|
|
860
|
+
];
|
|
861
|
+
// Add field descriptions
|
|
862
|
+
if (flow.requiredFields) {
|
|
863
|
+
extractionPrompt.push(`Required fields: ${flow.requiredFields.join(', ')}`);
|
|
864
|
+
}
|
|
865
|
+
if (flow.optionalFields) {
|
|
866
|
+
extractionPrompt.push(`Optional fields: ${flow.optionalFields.join(', ')}`);
|
|
867
|
+
}
|
|
868
|
+
extractionPrompt.push(``, `Return ONLY the extracted data as JSON. If no data can be extracted, return an empty object {}.`);
|
|
869
|
+
// Convert Event[] to HistoryItem[] for provider call
|
|
870
|
+
const historyItems = (0, utils_1.eventsToHistory)(history);
|
|
871
|
+
// Call AI to extract data
|
|
872
|
+
try {
|
|
873
|
+
const result = await this.options.provider.generateMessage({
|
|
874
|
+
prompt: extractionPrompt.join('\n'),
|
|
875
|
+
history: historyItems,
|
|
876
|
+
context: {}, // Passed as empty object so AI doesn't "extract" from context
|
|
877
|
+
// NOTE: context is intentionally NOT passed here.
|
|
878
|
+
// Passing context caused the AI to "extract" data from the lead's context
|
|
879
|
+
// (e.g., name, sector, city) instead of from what the user actually said.
|
|
880
|
+
signal,
|
|
881
|
+
parameters: {
|
|
882
|
+
jsonSchema: extractionSchema,
|
|
883
|
+
schemaName: 'data_extraction',
|
|
884
|
+
},
|
|
885
|
+
});
|
|
886
|
+
return result.structured || {};
|
|
887
|
+
}
|
|
888
|
+
catch (error) {
|
|
889
|
+
utils_1.logger.error(`[ResponsePipeline] Pre-extraction failed:`, error);
|
|
890
|
+
return {};
|
|
939
891
|
}
|
|
940
|
-
return completionResults.session;
|
|
941
892
|
}
|
|
942
893
|
}
|
|
943
894
|
exports.ResponsePipeline = ResponsePipeline;
|