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