@falai/agent 1.2.7 → 2.0.0
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 +40 -886
- package/dist/adapters/MemoryAdapter.js +2 -2
- package/dist/adapters/MemoryAdapter.js.map +1 -1
- package/dist/adapters/MongoAdapter.js +2 -2
- package/dist/adapters/MongoAdapter.js.map +1 -1
- package/dist/adapters/OpenSearchAdapter.d.ts.map +1 -1
- package/dist/adapters/OpenSearchAdapter.js +9 -7
- package/dist/adapters/OpenSearchAdapter.js.map +1 -1
- package/dist/adapters/PostgreSQLAdapter.d.ts +14 -0
- package/dist/adapters/PostgreSQLAdapter.d.ts.map +1 -1
- package/dist/adapters/PostgreSQLAdapter.js +25 -9
- package/dist/adapters/PostgreSQLAdapter.js.map +1 -1
- package/dist/adapters/PrismaAdapter.js +5 -5
- package/dist/adapters/PrismaAdapter.js.map +1 -1
- package/dist/adapters/RedisAdapter.js +2 -2
- package/dist/adapters/RedisAdapter.js.map +1 -1
- package/dist/adapters/SQLiteAdapter.d.ts +17 -0
- package/dist/adapters/SQLiteAdapter.d.ts.map +1 -1
- package/dist/adapters/SQLiteAdapter.js +30 -11
- package/dist/adapters/SQLiteAdapter.js.map +1 -1
- package/dist/cjs/adapters/MemoryAdapter.js +2 -2
- package/dist/cjs/adapters/MemoryAdapter.js.map +1 -1
- package/dist/cjs/adapters/MongoAdapter.js +2 -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 +9 -7
- package/dist/cjs/adapters/OpenSearchAdapter.js.map +1 -1
- package/dist/cjs/adapters/PostgreSQLAdapter.d.ts +14 -0
- package/dist/cjs/adapters/PostgreSQLAdapter.d.ts.map +1 -1
- package/dist/cjs/adapters/PostgreSQLAdapter.js +25 -9
- package/dist/cjs/adapters/PostgreSQLAdapter.js.map +1 -1
- package/dist/cjs/adapters/PrismaAdapter.js +5 -5
- package/dist/cjs/adapters/PrismaAdapter.js.map +1 -1
- package/dist/cjs/adapters/RedisAdapter.js +2 -2
- package/dist/cjs/adapters/RedisAdapter.js.map +1 -1
- package/dist/cjs/adapters/SQLiteAdapter.d.ts +17 -0
- package/dist/cjs/adapters/SQLiteAdapter.d.ts.map +1 -1
- package/dist/cjs/adapters/SQLiteAdapter.js +30 -11
- package/dist/cjs/adapters/SQLiteAdapter.js.map +1 -1
- package/dist/cjs/constants/index.d.ts +0 -9
- package/dist/cjs/constants/index.d.ts.map +1 -1
- package/dist/cjs/constants/index.js +2 -11
- package/dist/cjs/constants/index.js.map +1 -1
- package/dist/cjs/core/Agent.d.ts +119 -153
- package/dist/cjs/core/Agent.d.ts.map +1 -1
- package/dist/cjs/core/Agent.js +471 -324
- package/dist/cjs/core/Agent.js.map +1 -1
- package/dist/cjs/core/AutoChainExecutor.d.ts +107 -0
- package/dist/cjs/core/AutoChainExecutor.d.ts.map +1 -0
- package/dist/cjs/core/AutoChainExecutor.js +297 -0
- package/dist/cjs/core/AutoChainExecutor.js.map +1 -0
- package/dist/cjs/core/BranchEvaluator.d.ts +54 -0
- package/dist/cjs/core/BranchEvaluator.d.ts.map +1 -0
- package/dist/cjs/core/BranchEvaluator.js +130 -0
- package/dist/cjs/core/BranchEvaluator.js.map +1 -0
- package/dist/cjs/core/DirectiveBus.d.ts +88 -0
- package/dist/cjs/core/DirectiveBus.d.ts.map +1 -0
- package/dist/cjs/core/DirectiveBus.js +196 -0
- package/dist/cjs/core/DirectiveBus.js.map +1 -0
- package/dist/cjs/core/DirectiveChainTracker.d.ts +49 -0
- package/dist/cjs/core/DirectiveChainTracker.d.ts.map +1 -0
- package/dist/cjs/core/DirectiveChainTracker.js +121 -0
- package/dist/cjs/core/DirectiveChainTracker.js.map +1 -0
- package/dist/cjs/core/Flow.d.ts +186 -0
- package/dist/cjs/core/Flow.d.ts.map +1 -0
- package/dist/cjs/core/Flow.js +550 -0
- package/dist/cjs/core/Flow.js.map +1 -0
- package/dist/cjs/core/FlowRouter.d.ts +182 -0
- package/dist/cjs/core/FlowRouter.d.ts.map +1 -0
- package/dist/cjs/core/{RoutingEngine.js → FlowRouter.js} +323 -306
- package/dist/cjs/core/FlowRouter.js.map +1 -0
- package/dist/cjs/core/PersistenceManager.d.ts +2 -2
- package/dist/cjs/core/PersistenceManager.d.ts.map +1 -1
- package/dist/cjs/core/PersistenceManager.js +7 -7
- package/dist/cjs/core/PersistenceManager.js.map +1 -1
- package/dist/cjs/core/PromptComposer.d.ts +21 -8
- package/dist/cjs/core/PromptComposer.d.ts.map +1 -1
- package/dist/cjs/core/PromptComposer.js +182 -105
- package/dist/cjs/core/PromptComposer.js.map +1 -1
- package/dist/cjs/core/PromptSectionCache.d.ts +1 -1
- package/dist/cjs/core/PromptSectionCache.js +1 -1
- package/dist/cjs/core/ResponseEngine.d.ts +18 -8
- package/dist/cjs/core/ResponseEngine.d.ts.map +1 -1
- package/dist/cjs/core/ResponseEngine.js +38 -36
- package/dist/cjs/core/ResponseEngine.js.map +1 -1
- package/dist/cjs/core/ResponseModal.d.ts +73 -56
- package/dist/cjs/core/ResponseModal.d.ts.map +1 -1
- package/dist/cjs/core/ResponseModal.js +1196 -1015
- package/dist/cjs/core/ResponseModal.js.map +1 -1
- package/dist/cjs/core/ResponsePipeline.d.ts +124 -26
- package/dist/cjs/core/ResponsePipeline.d.ts.map +1 -1
- package/dist/cjs/core/ResponsePipeline.js +524 -134
- package/dist/cjs/core/ResponsePipeline.js.map +1 -1
- package/dist/cjs/core/SignalEvaluator.d.ts +86 -0
- package/dist/cjs/core/SignalEvaluator.d.ts.map +1 -0
- package/dist/cjs/core/SignalEvaluator.js +333 -0
- package/dist/cjs/core/SignalEvaluator.js.map +1 -0
- package/dist/cjs/core/SignalProcessor.d.ts +152 -0
- package/dist/cjs/core/SignalProcessor.d.ts.map +1 -0
- package/dist/cjs/core/SignalProcessor.js +562 -0
- package/dist/cjs/core/SignalProcessor.js.map +1 -0
- package/dist/cjs/core/Step.d.ts +43 -32
- package/dist/cjs/core/Step.d.ts.map +1 -1
- package/dist/cjs/core/Step.js +221 -126
- package/dist/cjs/core/Step.js.map +1 -1
- package/dist/cjs/core/StreamingToolExecutor.d.ts +2 -2
- package/dist/cjs/core/StreamingToolExecutor.d.ts.map +1 -1
- package/dist/cjs/core/StreamingToolExecutor.js.map +1 -1
- package/dist/cjs/core/ToolManager.d.ts +44 -13
- package/dist/cjs/core/ToolManager.d.ts.map +1 -1
- package/dist/cjs/core/ToolManager.js +174 -91
- package/dist/cjs/core/ToolManager.js.map +1 -1
- package/dist/cjs/core/createAgent.d.ts +35 -0
- package/dist/cjs/core/createAgent.d.ts.map +1 -0
- package/dist/cjs/core/createAgent.js +39 -0
- package/dist/cjs/core/createAgent.js.map +1 -0
- package/dist/cjs/core/flow-namespace.d.ts +49 -0
- package/dist/cjs/core/flow-namespace.d.ts.map +1 -0
- package/dist/cjs/core/flow-namespace.js +171 -0
- package/dist/cjs/core/flow-namespace.js.map +1 -0
- package/dist/cjs/index.d.ts +11 -14
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +18 -22
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/providers/GeminiProvider.d.ts +3 -3
- package/dist/cjs/providers/GeminiProvider.d.ts.map +1 -1
- package/dist/cjs/providers/GeminiProvider.js +16 -14
- package/dist/cjs/providers/GeminiProvider.js.map +1 -1
- package/dist/cjs/types/agent.d.ts +183 -54
- package/dist/cjs/types/agent.d.ts.map +1 -1
- package/dist/cjs/types/agent.js +0 -6
- package/dist/cjs/types/agent.js.map +1 -1
- package/dist/cjs/types/ai.d.ts +3 -3
- package/dist/cjs/types/ai.d.ts.map +1 -1
- package/dist/cjs/types/errors.d.ts +15 -0
- package/dist/cjs/types/errors.d.ts.map +1 -0
- package/dist/cjs/types/errors.js +22 -0
- package/dist/cjs/types/errors.js.map +1 -0
- package/dist/cjs/types/flow.d.ts +513 -0
- package/dist/cjs/types/flow.d.ts.map +1 -0
- package/dist/cjs/types/{route.js → flow.js} +2 -2
- package/dist/cjs/types/flow.js.map +1 -0
- package/dist/cjs/types/index.d.ts +7 -6
- package/dist/cjs/types/index.d.ts.map +1 -1
- package/dist/cjs/types/index.js +6 -2
- package/dist/cjs/types/index.js.map +1 -1
- package/dist/cjs/types/persistence.d.ts +11 -7
- package/dist/cjs/types/persistence.d.ts.map +1 -1
- package/dist/cjs/types/routing.d.ts +1 -1
- package/dist/cjs/types/routing.d.ts.map +1 -1
- package/dist/cjs/types/session.d.ts +24 -23
- package/dist/cjs/types/session.d.ts.map +1 -1
- package/dist/cjs/types/signals.d.ts +248 -0
- package/dist/cjs/types/signals.d.ts.map +1 -0
- package/dist/cjs/types/signals.js +11 -0
- package/dist/cjs/types/signals.js.map +1 -0
- package/dist/cjs/types/template.d.ts +2 -8
- package/dist/cjs/types/template.d.ts.map +1 -1
- package/dist/cjs/types/tool.d.ts +36 -29
- package/dist/cjs/types/tool.d.ts.map +1 -1
- package/dist/cjs/types/tool.js +1 -1
- package/dist/cjs/types/tool.js.map +1 -1
- package/dist/cjs/utils/condition.d.ts +7 -1
- package/dist/cjs/utils/condition.d.ts.map +1 -1
- package/dist/cjs/utils/condition.js.map +1 -1
- package/dist/cjs/utils/id.d.ts +13 -5
- package/dist/cjs/utils/id.d.ts.map +1 -1
- package/dist/cjs/utils/id.js +24 -10
- package/dist/cjs/utils/id.js.map +1 -1
- package/dist/cjs/utils/index.d.ts +2 -2
- package/dist/cjs/utils/index.d.ts.map +1 -1
- package/dist/cjs/utils/index.js +7 -3
- package/dist/cjs/utils/index.js.map +1 -1
- package/dist/cjs/utils/session.d.ts +44 -5
- package/dist/cjs/utils/session.d.ts.map +1 -1
- package/dist/cjs/utils/session.js +197 -38
- package/dist/cjs/utils/session.js.map +1 -1
- package/dist/constants/index.d.ts +0 -9
- package/dist/constants/index.d.ts.map +1 -1
- package/dist/constants/index.js +3 -9
- package/dist/constants/index.js.map +1 -1
- package/dist/core/Agent.d.ts +119 -153
- package/dist/core/Agent.d.ts.map +1 -1
- package/dist/core/Agent.js +472 -325
- package/dist/core/Agent.js.map +1 -1
- package/dist/core/AutoChainExecutor.d.ts +107 -0
- package/dist/core/AutoChainExecutor.d.ts.map +1 -0
- package/dist/core/AutoChainExecutor.js +293 -0
- package/dist/core/AutoChainExecutor.js.map +1 -0
- package/dist/core/BranchEvaluator.d.ts +54 -0
- package/dist/core/BranchEvaluator.d.ts.map +1 -0
- package/dist/core/BranchEvaluator.js +126 -0
- package/dist/core/BranchEvaluator.js.map +1 -0
- package/dist/core/DirectiveBus.d.ts +88 -0
- package/dist/core/DirectiveBus.d.ts.map +1 -0
- package/dist/core/DirectiveBus.js +192 -0
- package/dist/core/DirectiveBus.js.map +1 -0
- package/dist/core/DirectiveChainTracker.d.ts +49 -0
- package/dist/core/DirectiveChainTracker.d.ts.map +1 -0
- package/dist/core/DirectiveChainTracker.js +117 -0
- package/dist/core/DirectiveChainTracker.js.map +1 -0
- package/dist/core/Flow.d.ts +186 -0
- package/dist/core/Flow.d.ts.map +1 -0
- package/dist/core/Flow.js +546 -0
- package/dist/core/Flow.js.map +1 -0
- package/dist/core/FlowRouter.d.ts +182 -0
- package/dist/core/FlowRouter.d.ts.map +1 -0
- package/dist/core/{RoutingEngine.js → FlowRouter.js} +322 -305
- package/dist/core/FlowRouter.js.map +1 -0
- package/dist/core/PersistenceManager.d.ts +2 -2
- package/dist/core/PersistenceManager.d.ts.map +1 -1
- package/dist/core/PersistenceManager.js +7 -7
- package/dist/core/PersistenceManager.js.map +1 -1
- package/dist/core/PromptComposer.d.ts +21 -8
- package/dist/core/PromptComposer.d.ts.map +1 -1
- package/dist/core/PromptComposer.js +183 -106
- package/dist/core/PromptComposer.js.map +1 -1
- package/dist/core/PromptSectionCache.d.ts +1 -1
- package/dist/core/PromptSectionCache.js +1 -1
- package/dist/core/ResponseEngine.d.ts +18 -8
- package/dist/core/ResponseEngine.d.ts.map +1 -1
- package/dist/core/ResponseEngine.js +38 -36
- package/dist/core/ResponseEngine.js.map +1 -1
- package/dist/core/ResponseModal.d.ts +73 -56
- package/dist/core/ResponseModal.d.ts.map +1 -1
- package/dist/core/ResponseModal.js +1198 -1017
- package/dist/core/ResponseModal.js.map +1 -1
- package/dist/core/ResponsePipeline.d.ts +124 -26
- package/dist/core/ResponsePipeline.d.ts.map +1 -1
- package/dist/core/ResponsePipeline.js +524 -135
- package/dist/core/ResponsePipeline.js.map +1 -1
- package/dist/core/SignalEvaluator.d.ts +86 -0
- package/dist/core/SignalEvaluator.d.ts.map +1 -0
- package/dist/core/SignalEvaluator.js +326 -0
- package/dist/core/SignalEvaluator.js.map +1 -0
- package/dist/core/SignalProcessor.d.ts +152 -0
- package/dist/core/SignalProcessor.d.ts.map +1 -0
- package/dist/core/SignalProcessor.js +555 -0
- package/dist/core/SignalProcessor.js.map +1 -0
- package/dist/core/Step.d.ts +43 -32
- package/dist/core/Step.d.ts.map +1 -1
- package/dist/core/Step.js +220 -126
- package/dist/core/Step.js.map +1 -1
- package/dist/core/StreamingToolExecutor.d.ts +2 -2
- package/dist/core/StreamingToolExecutor.d.ts.map +1 -1
- package/dist/core/StreamingToolExecutor.js.map +1 -1
- package/dist/core/ToolManager.d.ts +44 -13
- package/dist/core/ToolManager.d.ts.map +1 -1
- package/dist/core/ToolManager.js +174 -91
- package/dist/core/ToolManager.js.map +1 -1
- package/dist/core/createAgent.d.ts +35 -0
- package/dist/core/createAgent.d.ts.map +1 -0
- package/dist/core/createAgent.js +36 -0
- package/dist/core/createAgent.js.map +1 -0
- package/dist/core/flow-namespace.d.ts +49 -0
- package/dist/core/flow-namespace.d.ts.map +1 -0
- package/dist/core/flow-namespace.js +168 -0
- package/dist/core/flow-namespace.js.map +1 -0
- package/dist/index.d.ts +11 -14
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -12
- package/dist/index.js.map +1 -1
- package/dist/providers/GeminiProvider.d.ts +3 -3
- package/dist/providers/GeminiProvider.d.ts.map +1 -1
- package/dist/providers/GeminiProvider.js +16 -14
- package/dist/providers/GeminiProvider.js.map +1 -1
- package/dist/types/agent.d.ts +183 -54
- package/dist/types/agent.d.ts.map +1 -1
- package/dist/types/agent.js +0 -6
- package/dist/types/agent.js.map +1 -1
- package/dist/types/ai.d.ts +3 -3
- package/dist/types/ai.d.ts.map +1 -1
- package/dist/types/errors.d.ts +15 -0
- package/dist/types/errors.d.ts.map +1 -0
- package/dist/types/errors.js +18 -0
- package/dist/types/errors.js.map +1 -0
- package/dist/types/flow.d.ts +513 -0
- package/dist/types/flow.d.ts.map +1 -0
- package/dist/types/flow.js +5 -0
- package/dist/types/flow.js.map +1 -0
- package/dist/types/index.d.ts +7 -6
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +4 -1
- package/dist/types/index.js.map +1 -1
- package/dist/types/persistence.d.ts +11 -7
- package/dist/types/persistence.d.ts.map +1 -1
- package/dist/types/routing.d.ts +1 -1
- package/dist/types/routing.d.ts.map +1 -1
- package/dist/types/session.d.ts +24 -23
- package/dist/types/session.d.ts.map +1 -1
- package/dist/types/signals.d.ts +248 -0
- package/dist/types/signals.d.ts.map +1 -0
- package/dist/types/signals.js +10 -0
- package/dist/types/signals.js.map +1 -0
- package/dist/types/template.d.ts +2 -8
- package/dist/types/template.d.ts.map +1 -1
- package/dist/types/tool.d.ts +36 -29
- package/dist/types/tool.d.ts.map +1 -1
- package/dist/types/tool.js +1 -1
- package/dist/types/tool.js.map +1 -1
- package/dist/utils/condition.d.ts +7 -1
- package/dist/utils/condition.d.ts.map +1 -1
- package/dist/utils/condition.js.map +1 -1
- package/dist/utils/id.d.ts +13 -5
- package/dist/utils/id.d.ts.map +1 -1
- package/dist/utils/id.js +22 -9
- package/dist/utils/id.js.map +1 -1
- package/dist/utils/index.d.ts +2 -2
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +2 -2
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/session.d.ts +44 -5
- package/dist/utils/session.d.ts.map +1 -1
- package/dist/utils/session.js +193 -37
- package/dist/utils/session.js.map +1 -1
- package/docs/README.md +15 -202
- package/docs/concepts/architecture.md +281 -0
- package/docs/concepts/directives.md +400 -0
- package/docs/concepts/pipeline.md +399 -0
- package/docs/guides/branching.md +263 -0
- package/docs/guides/compaction.md +163 -0
- package/docs/guides/conditions.md +167 -0
- package/docs/guides/error-handling.md +176 -0
- package/docs/guides/flow-control.md +409 -0
- package/docs/guides/instructions.md +210 -0
- package/docs/guides/persistence.md +182 -0
- package/docs/guides/streaming.md +137 -0
- package/docs/migration/README.md +15 -0
- package/docs/migration/route-to-flow.md +560 -0
- package/docs/migration/v1-to-v2.md +909 -0
- package/docs/reference/adapters.md +481 -0
- package/docs/reference/branches.md +241 -0
- package/docs/reference/create-agent.md +186 -0
- package/docs/reference/directive.md +243 -0
- package/docs/reference/errors.md +122 -0
- package/docs/reference/flow.md +238 -0
- package/docs/reference/instruction.md +177 -0
- package/docs/reference/pre-directive.md +131 -0
- package/docs/reference/providers.md +227 -0
- package/docs/reference/signals.md +356 -0
- package/docs/reference/step.md +339 -0
- package/docs/reference/tool.md +269 -0
- package/docs/start/01-install.md +81 -0
- package/docs/start/02-first-agent.md +196 -0
- package/docs/start/03-collect-data.md +222 -0
- package/docs/start/04-add-tools.md +276 -0
- package/docs/start/05-go-to-production.md +216 -0
- package/examples/01-quickstart.ts +20 -0
- package/examples/02-data-extraction.ts +90 -0
- package/examples/03-tools.ts +136 -0
- package/examples/04-instructions.ts +100 -0
- package/examples/05-branching.ts +140 -0
- package/examples/06-flow-control.ts +103 -0
- package/examples/07-streaming.ts +69 -0
- package/examples/08-persistence.ts +98 -0
- package/examples/09-signals.ts +144 -0
- package/examples/tsconfig.json +30 -0
- package/package.json +2 -1
- package/src/adapters/MemoryAdapter.ts +3 -3
- package/src/adapters/MongoAdapter.ts +3 -3
- package/src/adapters/OpenSearchAdapter.ts +10 -8
- package/src/adapters/PostgreSQLAdapter.ts +26 -10
- package/src/adapters/PrismaAdapter.ts +6 -6
- package/src/adapters/RedisAdapter.ts +3 -3
- package/src/adapters/SQLiteAdapter.ts +31 -12
- package/src/constants/index.ts +2 -10
- package/src/core/Agent.ts +585 -374
- package/src/core/AutoChainExecutor.ts +440 -0
- package/src/core/BranchEvaluator.ts +167 -0
- package/src/core/DirectiveBus.ts +248 -0
- package/src/core/DirectiveChainTracker.ts +144 -0
- package/src/core/Flow.ts +666 -0
- package/src/core/{RoutingEngine.ts → FlowRouter.ts} +385 -365
- package/src/core/PersistenceManager.ts +8 -8
- package/src/core/PromptComposer.ts +209 -140
- package/src/core/PromptSectionCache.ts +1 -1
- package/src/core/ResponseEngine.ts +61 -46
- package/src/core/ResponseModal.ts +1458 -1241
- package/src/core/ResponsePipeline.ts +675 -173
- package/src/core/SignalEvaluator.ts +420 -0
- package/src/core/SignalProcessor.ts +723 -0
- package/src/core/Step.ts +279 -176
- package/src/core/StreamingToolExecutor.ts +4 -4
- package/src/core/ToolManager.ts +200 -97
- package/src/core/createAgent.ts +40 -0
- package/src/core/flow-namespace.ts +219 -0
- package/src/index.ts +42 -36
- package/src/providers/GeminiProvider.ts +17 -15
- package/src/types/agent.ts +182 -53
- package/src/types/ai.ts +3 -3
- package/src/types/errors.ts +18 -0
- package/src/types/flow.ts +590 -0
- package/src/types/index.ts +43 -16
- package/src/types/persistence.ts +12 -8
- package/src/types/routing.ts +1 -1
- package/src/types/session.ts +26 -23
- package/src/types/signals.ts +321 -0
- package/src/types/template.ts +3 -11
- package/src/types/tool.ts +50 -42
- package/src/utils/condition.ts +13 -4
- package/src/utils/id.ts +27 -9
- package/src/utils/index.ts +6 -2
- package/src/utils/session.ts +238 -42
- package/dist/cjs/core/BatchExecutor.d.ts +0 -359
- package/dist/cjs/core/BatchExecutor.d.ts.map +0 -1
- package/dist/cjs/core/BatchExecutor.js +0 -861
- package/dist/cjs/core/BatchExecutor.js.map +0 -1
- package/dist/cjs/core/BatchPromptBuilder.d.ts +0 -89
- package/dist/cjs/core/BatchPromptBuilder.d.ts.map +0 -1
- package/dist/cjs/core/BatchPromptBuilder.js +0 -223
- package/dist/cjs/core/BatchPromptBuilder.js.map +0 -1
- package/dist/cjs/core/Route.d.ts +0 -180
- package/dist/cjs/core/Route.d.ts.map +0 -1
- package/dist/cjs/core/Route.js +0 -542
- package/dist/cjs/core/Route.js.map +0 -1
- package/dist/cjs/core/RoutingEngine.d.ts +0 -185
- package/dist/cjs/core/RoutingEngine.d.ts.map +0 -1
- package/dist/cjs/core/RoutingEngine.js.map +0 -1
- package/dist/cjs/types/route.d.ts +0 -336
- package/dist/cjs/types/route.d.ts.map +0 -1
- package/dist/cjs/types/route.js.map +0 -1
- package/dist/core/BatchExecutor.d.ts +0 -359
- package/dist/core/BatchExecutor.d.ts.map +0 -1
- package/dist/core/BatchExecutor.js +0 -856
- package/dist/core/BatchExecutor.js.map +0 -1
- package/dist/core/BatchPromptBuilder.d.ts +0 -89
- package/dist/core/BatchPromptBuilder.d.ts.map +0 -1
- package/dist/core/BatchPromptBuilder.js +0 -219
- package/dist/core/BatchPromptBuilder.js.map +0 -1
- package/dist/core/Route.d.ts +0 -180
- package/dist/core/Route.d.ts.map +0 -1
- package/dist/core/Route.js +0 -538
- package/dist/core/Route.js.map +0 -1
- package/dist/core/RoutingEngine.d.ts +0 -185
- package/dist/core/RoutingEngine.d.ts.map +0 -1
- package/dist/core/RoutingEngine.js.map +0 -1
- package/dist/types/route.d.ts +0 -336
- package/dist/types/route.d.ts.map +0 -1
- package/dist/types/route.js +0 -5
- package/dist/types/route.js.map +0 -1
- package/docs/CONTRIBUTING.md +0 -521
- package/docs/api/README.md +0 -3299
- package/docs/api/overview.md +0 -1410
- package/docs/architecture/data-extraction-flow.md +0 -360
- package/docs/architecture/multi-step-execution.md +0 -277
- package/docs/core/agent/README.md +0 -938
- package/docs/core/agent/context-management.md +0 -796
- package/docs/core/agent/rules-and-prohibitions.md +0 -113
- package/docs/core/agent/session-management.md +0 -693
- package/docs/core/ai-integration/prompt-composition.md +0 -355
- package/docs/core/ai-integration/providers.md +0 -515
- package/docs/core/ai-integration/response-processing.md +0 -433
- package/docs/core/conversation-flows/data-collection.md +0 -772
- package/docs/core/conversation-flows/route-dsl.md +0 -509
- package/docs/core/conversation-flows/routes.md +0 -249
- package/docs/core/conversation-flows/step-transitions.md +0 -731
- package/docs/core/conversation-flows/steps.md +0 -268
- package/docs/core/error-handling.md +0 -830
- package/docs/core/persistence/adapters.md +0 -255
- package/docs/core/persistence/session-storage.md +0 -656
- package/docs/core/routing/intelligent-routing.md +0 -470
- package/docs/core/tools/enhanced-tool.md +0 -186
- package/docs/core/tools/streaming-execution.md +0 -161
- package/docs/core/tools/tool-definition.md +0 -970
- package/docs/core/tools/tool-scoping.md +0 -819
- package/docs/guides/advanced-patterns/publishing.md +0 -186
- package/docs/guides/context-compaction.md +0 -96
- package/docs/guides/error-handling-patterns.md +0 -578
- package/docs/guides/getting-started/README.md +0 -795
- package/docs/guides/migration/README.md +0 -101
- package/docs/guides/migration/flexible-routing-conditions.md +0 -375
- package/docs/guides/migration/multi-step-execution.md +0 -393
- package/docs/guides/migration/response-modal-refactor.md +0 -518
- package/docs/guides/prompt-optimization.md +0 -164
- package/examples/advanced-patterns/context-compaction.ts +0 -223
- package/examples/advanced-patterns/knowledge-based-agent.ts +0 -735
- package/examples/advanced-patterns/persistent-onboarding.ts +0 -728
- package/examples/advanced-patterns/route-lifecycle-hooks.ts +0 -556
- package/examples/advanced-patterns/streaming-responses.ts +0 -656
- package/examples/ai-providers/anthropic-integration.ts +0 -388
- package/examples/ai-providers/openai-integration.ts +0 -228
- package/examples/condition-patterns/function-only-conditions.ts +0 -365
- package/examples/condition-patterns/mixed-array-conditions.ts +0 -477
- package/examples/condition-patterns/route-skipif-patterns.ts +0 -468
- package/examples/condition-patterns/step-skipif-patterns.ts +0 -0
- package/examples/condition-patterns/string-only-conditions.ts +0 -296
- package/examples/conversation-flows/completion-transitions.ts +0 -318
- package/examples/core-concepts/basic-agent.ts +0 -503
- package/examples/core-concepts/modern-streaming-api.ts +0 -309
- package/examples/core-concepts/schema-driven-extraction.ts +0 -332
- package/examples/core-concepts/session-management.ts +0 -494
- package/examples/integrations/database-integration.ts +0 -631
- package/examples/integrations/healthcare-integration.ts +0 -595
- package/examples/integrations/search-integration.ts +0 -530
- package/examples/integrations/server-session-management.ts +0 -307
- package/examples/persistence/custom-adapter.ts +0 -526
- package/examples/persistence/database-persistence.ts +0 -583
- package/examples/persistence/memory-sessions.ts +0 -495
- package/examples/persistence/prisma-schema.example.prisma +0 -74
- package/examples/persistence/redis-persistence.ts +0 -488
- package/examples/tools/basic-tools.ts +0 -765
- package/examples/tools/data-enrichment-tools.ts +0 -593
- package/examples/tools/enhanced-tool-metadata.ts +0 -268
- package/examples/tools/streaming-tool-execution.ts +0 -283
- package/src/core/BatchExecutor.ts +0 -1187
- package/src/core/BatchPromptBuilder.ts +0 -299
- package/src/core/Route.ts +0 -678
- package/src/types/route.ts +0 -392
|
@@ -13,22 +13,23 @@ import type {
|
|
|
13
13
|
Tool,
|
|
14
14
|
Event,
|
|
15
15
|
AgentStructuredResponse,
|
|
16
|
-
Term,
|
|
17
16
|
StoppedReason,
|
|
18
17
|
ToolCallRequest,
|
|
18
|
+
ScopedInstructions,
|
|
19
|
+
AppliedInstruction,
|
|
20
|
+
PrepareResult,
|
|
21
|
+
PreDirective,
|
|
19
22
|
} from "../types";
|
|
23
|
+
import type { SignalFiring } from "../types/signals";
|
|
20
24
|
import type { Agent } from "./Agent";
|
|
21
|
-
import type {
|
|
25
|
+
import type { Flow } from "./Flow";
|
|
22
26
|
import { Step } from "./Step";
|
|
23
27
|
import { ResponseEngine } from "./ResponseEngine";
|
|
24
|
-
import { ResponsePipeline } from "./ResponsePipeline";
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
import { cloneDeep, mergeCollected, enterStep, getLastMessageFromHistory, render, logger, historyToEvents, eventsToHistory, serializeToolResult } from "../utils";
|
|
28
|
+
import { ResponsePipeline, hasDirectivePositionField } from "./ResponsePipeline";
|
|
29
|
+
import { AutoChainExecutor, type AutoChainResult } from "./AutoChainExecutor";
|
|
30
|
+
import { cloneDeep, mergeCollected, enterStep, enterFlow, getLastMessageFromHistory, logger, historyToEvents, eventsToHistory, serializeToolResult, completeCurrentFlow, render } from "../utils";
|
|
28
31
|
import { createTemplateContext } from "../utils/template";
|
|
29
32
|
import type { ToolManager } from "./ToolManager";
|
|
30
|
-
import { END_ROUTE_ID } from "../constants";
|
|
31
|
-
import type { StepOptions } from "../types/route";
|
|
32
33
|
|
|
33
34
|
/**
|
|
34
35
|
* Configuration options for ResponseModal
|
|
@@ -109,7 +110,8 @@ export class ResponseGenerationError extends Error {
|
|
|
109
110
|
): ResponseGenerationError {
|
|
110
111
|
const message = error instanceof Error ? error.message : String(error);
|
|
111
112
|
return new ResponseGenerationError(
|
|
112
|
-
`Response generation failed in ${phase}: ${message}
|
|
113
|
+
`[ResponseGenerationError] Response generation failed in ${phase}: ${message}. ` +
|
|
114
|
+
`Check provider configuration and the ${phase} phase handler.`,
|
|
113
115
|
{ originalError: error, params, phase, context }
|
|
114
116
|
);
|
|
115
117
|
}
|
|
@@ -129,18 +131,20 @@ interface ResponseContext<TContext = unknown, TData = unknown> {
|
|
|
129
131
|
effectiveContext: TContext;
|
|
130
132
|
session: SessionState<TData>;
|
|
131
133
|
history: HistoryItem[]; // Keep as HistoryItem[] for external API compatibility
|
|
132
|
-
|
|
134
|
+
selectedFlow?: Flow<TContext, TData>;
|
|
133
135
|
selectedStep?: Step<TContext, TData>;
|
|
134
136
|
responseDirectives?: string[];
|
|
135
|
-
|
|
136
|
-
/** Batch of steps to execute (for multi-step execution) */
|
|
137
|
-
batchSteps?: StepOptions<TContext, TData>[];
|
|
138
|
-
/** Reason why batch determination stopped */
|
|
139
|
-
batchStoppedReason?: StoppedReason;
|
|
140
|
-
/** Step that caused batch to stop (if applicable) */
|
|
141
|
-
batchStoppedAtStep?: StepOptions<TContext, TData>;
|
|
137
|
+
isFlowComplete: boolean;
|
|
142
138
|
/** AbortSignal for cancellation propagation */
|
|
143
139
|
signal?: AbortSignal;
|
|
140
|
+
/** Signal firings accumulated across both phases (pre + post) for the response surface. */
|
|
141
|
+
signalFirings?: SignalFiring<TContext, TData>[];
|
|
142
|
+
/** Pre-phase merged directive from signals (non-position fields like appendPrompt, injectTools). */
|
|
143
|
+
signalPreDirective?: PreDirective<TContext, TData>;
|
|
144
|
+
/** Whether the pre-signal phase emitted a halt directive. */
|
|
145
|
+
signalHalted?: boolean;
|
|
146
|
+
/** Reply from a halt directive. */
|
|
147
|
+
signalHaltReply?: string;
|
|
144
148
|
}
|
|
145
149
|
|
|
146
150
|
/**
|
|
@@ -150,8 +154,6 @@ interface ResponseContext<TContext = unknown, TData = unknown> {
|
|
|
150
154
|
export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
151
155
|
private readonly responseEngine: ResponseEngine<TContext, TData>;
|
|
152
156
|
private readonly responsePipeline: ResponsePipeline<TContext, TData>;
|
|
153
|
-
private readonly batchExecutor: BatchExecutor<TContext, TData>;
|
|
154
|
-
private readonly batchPromptBuilder: BatchPromptBuilder<TContext, TData>;
|
|
155
157
|
|
|
156
158
|
constructor(
|
|
157
159
|
private readonly agent: Agent<TContext, TData>,
|
|
@@ -163,20 +165,15 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
163
165
|
// Initialize response pipeline with agent dependencies
|
|
164
166
|
this.responsePipeline = new ResponsePipeline<TContext, TData>(
|
|
165
167
|
this.agent.getAgentOptions(),
|
|
166
|
-
() => this.agent.
|
|
168
|
+
() => this.agent.getFlows(), // Pass a function to get flows dynamically
|
|
167
169
|
this.agent.getTools(),
|
|
168
|
-
this.agent.
|
|
170
|
+
this.agent.getFlowRouter(),
|
|
169
171
|
this.agent.updateContext.bind(this.agent),
|
|
170
172
|
this.agent.getUpdateDataMethod(),
|
|
171
173
|
this.agent.updateCollectedData.bind(this.agent),
|
|
172
|
-
this.getToolManager()
|
|
174
|
+
this.getToolManager(),
|
|
175
|
+
this.agent.signalProcessor
|
|
173
176
|
);
|
|
174
|
-
|
|
175
|
-
// Initialize batch executor for multi-step execution
|
|
176
|
-
this.batchExecutor = new BatchExecutor<TContext, TData>();
|
|
177
|
-
|
|
178
|
-
// Initialize batch prompt builder for combined prompts
|
|
179
|
-
this.batchPromptBuilder = new BatchPromptBuilder<TContext, TData>(this.agent.promptSectionCache);
|
|
180
177
|
}
|
|
181
178
|
|
|
182
179
|
/**
|
|
@@ -196,7 +193,8 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
196
193
|
|
|
197
194
|
} catch (error) {
|
|
198
195
|
throw new ResponseGenerationError(
|
|
199
|
-
`
|
|
196
|
+
`[ResponseGenerationError] Response generation failed: ${error instanceof Error ? error.message : String(error)}. ` +
|
|
197
|
+
`Check provider configuration and network connectivity.`,
|
|
200
198
|
{ originalError: error, params, phase: 'response_generation' }
|
|
201
199
|
);
|
|
202
200
|
}
|
|
@@ -379,6 +377,21 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
379
377
|
return undefined;
|
|
380
378
|
}
|
|
381
379
|
|
|
380
|
+
/**
|
|
381
|
+
* Collect scoped instructions from agent, flow, and step into a ScopedInstructions value.
|
|
382
|
+
* @private
|
|
383
|
+
*/
|
|
384
|
+
private collectScopedInstructions(
|
|
385
|
+
flow?: Flow<TContext, TData>,
|
|
386
|
+
step?: Step<TContext, TData>,
|
|
387
|
+
): ScopedInstructions<TContext, TData> {
|
|
388
|
+
return {
|
|
389
|
+
global: this.agent.instructions,
|
|
390
|
+
flow: flow ? { flowTitle: flow.title, items: flow.instructions } : undefined,
|
|
391
|
+
step: step ? { stepId: step.id, items: step.getInstructions() } : undefined,
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
|
|
382
395
|
// UNIFIED RESPONSE LOGIC - Consolidates common logic between streaming and non-streaming
|
|
383
396
|
// ============================================================================
|
|
384
397
|
|
|
@@ -393,7 +406,11 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
393
406
|
|
|
394
407
|
// Validate input parameters
|
|
395
408
|
if (!simpleHistory) {
|
|
396
|
-
throw new ResponseGenerationError(
|
|
409
|
+
throw new ResponseGenerationError(
|
|
410
|
+
'[ResponseGenerationError] Missing history: history is required for response generation. ' +
|
|
411
|
+
'Pass a valid history array to the respond/stream method.',
|
|
412
|
+
{ params, phase: 'validation' }
|
|
413
|
+
);
|
|
397
414
|
}
|
|
398
415
|
|
|
399
416
|
// Convert HistoryItem[] to Event[] for internal processing
|
|
@@ -410,7 +427,7 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
410
427
|
try {
|
|
411
428
|
// Set current context and session in pipeline for consistency
|
|
412
429
|
this.responsePipeline.setContext(await this.agent.getContext());
|
|
413
|
-
this.responsePipeline.setCurrentSession(this.agent.
|
|
430
|
+
this.responsePipeline.setCurrentSession(this.agent.currentSession);
|
|
414
431
|
|
|
415
432
|
responseContext = await this.responsePipeline.prepareResponseContext({
|
|
416
433
|
contextOverride,
|
|
@@ -451,17 +468,18 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
451
468
|
throw ResponseGenerationError.fromError(error, 'step_preparation', params, { session, effectiveContext });
|
|
452
469
|
}
|
|
453
470
|
|
|
454
|
-
// PHASE 2: ROUTING + STEP SELECTION - Determine which
|
|
455
|
-
//
|
|
471
|
+
// PHASE 2: ROUTING + STEP SELECTION - Determine which flow and step to use
|
|
472
|
+
// Performs pre-extraction and step selection
|
|
456
473
|
let routingResult: {
|
|
457
|
-
|
|
474
|
+
selectedFlow?: Flow<TContext, TData>;
|
|
458
475
|
selectedStep?: Step<TContext, TData>;
|
|
459
476
|
responseDirectives?: string[];
|
|
460
477
|
session: SessionState<TData>;
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
478
|
+
isFlowComplete: boolean;
|
|
479
|
+
signalFirings?: SignalFiring<TContext, TData>[];
|
|
480
|
+
signalPreDirective?: PreDirective<TContext, TData>;
|
|
481
|
+
signalHalted?: boolean;
|
|
482
|
+
signalHaltReply?: string;
|
|
465
483
|
};
|
|
466
484
|
try {
|
|
467
485
|
routingResult = await this.handleUnifiedRoutingAndStepSelection({
|
|
@@ -478,14 +496,15 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
478
496
|
effectiveContext,
|
|
479
497
|
session: routingResult.session,
|
|
480
498
|
history,
|
|
481
|
-
|
|
499
|
+
selectedFlow: routingResult.selectedFlow,
|
|
482
500
|
selectedStep: routingResult.selectedStep,
|
|
483
501
|
responseDirectives: routingResult.responseDirectives,
|
|
484
|
-
|
|
485
|
-
batchSteps: routingResult.batchSteps,
|
|
486
|
-
batchStoppedReason: routingResult.batchStoppedReason,
|
|
487
|
-
batchStoppedAtStep: routingResult.batchStoppedAtStep,
|
|
502
|
+
isFlowComplete: routingResult.isFlowComplete,
|
|
488
503
|
signal,
|
|
504
|
+
signalFirings: routingResult.signalFirings,
|
|
505
|
+
signalPreDirective: routingResult.signalPreDirective,
|
|
506
|
+
signalHalted: routingResult.signalHalted,
|
|
507
|
+
signalHaltReply: routingResult.signalHaltReply,
|
|
489
508
|
};
|
|
490
509
|
} catch (error) {
|
|
491
510
|
// Re-throw ResponseGenerationError as-is, wrap others
|
|
@@ -506,45 +525,183 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
506
525
|
context: TContext;
|
|
507
526
|
signal?: AbortSignal;
|
|
508
527
|
}): Promise<{
|
|
509
|
-
|
|
528
|
+
selectedFlow?: Flow<TContext, TData>;
|
|
510
529
|
selectedStep?: Step<TContext, TData>;
|
|
511
530
|
responseDirectives?: string[];
|
|
512
531
|
session: SessionState<TData>;
|
|
513
|
-
|
|
514
|
-
/**
|
|
515
|
-
|
|
516
|
-
/**
|
|
517
|
-
|
|
518
|
-
/**
|
|
519
|
-
|
|
532
|
+
isFlowComplete: boolean;
|
|
533
|
+
/** Signal firings from the pre-phase (threaded through for response surface). */
|
|
534
|
+
signalFirings?: SignalFiring<TContext, TData>[];
|
|
535
|
+
/** Non-position signal directive for pre-LLM augmentation (appendPrompt, injectTools, etc). */
|
|
536
|
+
signalPreDirective?: PreDirective<TContext, TData>;
|
|
537
|
+
/** Pre-signal phase halted the turn. */
|
|
538
|
+
signalHalted?: boolean;
|
|
539
|
+
/** Reply text from the halt directive. */
|
|
540
|
+
signalHaltReply?: string;
|
|
520
541
|
}> {
|
|
521
542
|
try {
|
|
522
|
-
//
|
|
523
|
-
|
|
524
|
-
|
|
543
|
+
// Create a fresh chain tracker for this turn (Requirement 22.1)
|
|
544
|
+
this.responsePipeline.createChainTracker();
|
|
545
|
+
|
|
546
|
+
// ROUTING SKIP OPTIMIZATION (Requirements 20.1, 20.2, 20.3):
|
|
547
|
+
// When the current step has collect fields AND pre-extraction populates at least
|
|
548
|
+
// one of those fields, skip FlowRouter.decideFlowAndStep for this turn.
|
|
549
|
+
const routingSkipResult = await this.attemptRoutingSkipForCollect(params);
|
|
550
|
+
if (routingSkipResult) {
|
|
551
|
+
// Even when routing is skipped, run pre-signal phase if processor is present
|
|
552
|
+
if (this.agent.signalProcessor) {
|
|
553
|
+
const signalResult = await this.responsePipeline.runPreSignalPhase(
|
|
554
|
+
params.session, params.context, params.history,
|
|
555
|
+
);
|
|
556
|
+
// If signal halts, override the routing skip result
|
|
557
|
+
if (signalResult.mergedDirective?.halt) {
|
|
558
|
+
return {
|
|
559
|
+
...routingSkipResult,
|
|
560
|
+
session: signalResult.updatedSession,
|
|
561
|
+
signalFirings: signalResult.firings,
|
|
562
|
+
signalHalted: true,
|
|
563
|
+
signalHaltReply: signalResult.mergedDirective.reply,
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
// If signal has position fields, override routing skip result
|
|
567
|
+
if (hasDirectivePositionField(signalResult.mergedDirective)) {
|
|
568
|
+
return this.applySignalPositionDirective(
|
|
569
|
+
signalResult, params,
|
|
570
|
+
);
|
|
571
|
+
}
|
|
572
|
+
// Non-position directive: propagate for pre-LLM augmentation
|
|
573
|
+
return {
|
|
574
|
+
...routingSkipResult,
|
|
575
|
+
session: signalResult.updatedSession,
|
|
576
|
+
signalFirings: signalResult.firings,
|
|
577
|
+
signalPreDirective: signalResult.mergedDirective || undefined,
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
return routingSkipResult;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// ── PARALLEL PRE-SIGNAL PHASE + ROUTING (Algorithm 5) ────────────────
|
|
584
|
+
// When signalProcessor is present, run pre-signals in parallel with routing.
|
|
585
|
+
// When absent, call the router directly (zero overhead, preserve current behavior).
|
|
586
|
+
if (this.agent.signalProcessor) {
|
|
587
|
+
// Run pre-signal phase in parallel with routing (Requirement 8.1)
|
|
588
|
+
const [signalResult, routingResult] = await Promise.all([
|
|
589
|
+
this.responsePipeline.runPreSignalPhase(
|
|
590
|
+
params.session, params.context, params.history,
|
|
591
|
+
),
|
|
592
|
+
this.responsePipeline.handleRoutingAndStepSelection({
|
|
593
|
+
session: params.session,
|
|
594
|
+
history: params.history,
|
|
595
|
+
context: params.context,
|
|
596
|
+
signal: params.signal,
|
|
597
|
+
}),
|
|
598
|
+
]);
|
|
599
|
+
|
|
600
|
+
// ── Requirement 8.2: halt → discard routing, skip LLM ────────────
|
|
601
|
+
if (signalResult.mergedDirective?.halt) {
|
|
602
|
+
return {
|
|
603
|
+
selectedFlow: undefined,
|
|
604
|
+
selectedStep: undefined,
|
|
605
|
+
session: signalResult.updatedSession,
|
|
606
|
+
isFlowComplete: false,
|
|
607
|
+
signalFirings: signalResult.firings,
|
|
608
|
+
signalHalted: true,
|
|
609
|
+
signalHaltReply: signalResult.mergedDirective.reply,
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// ── Requirement 8.3: position directive → discard routing, apply signal position ──
|
|
614
|
+
if (hasDirectivePositionField(signalResult.mergedDirective)) {
|
|
615
|
+
return this.applySignalPositionDirective(
|
|
616
|
+
signalResult, params,
|
|
617
|
+
);
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// ── Requirement 8.4: non-position directive → use routing, propagate augmentation ──
|
|
621
|
+
// ── Requirement 8.5: no directive → use routing as-is ─────────────
|
|
622
|
+
let updatedSession = signalResult.updatedSession;
|
|
623
|
+
|
|
624
|
+
// Apply data/context updates from signal to the routed session
|
|
625
|
+
if (signalResult.mergedDirective?.dataUpdate) {
|
|
626
|
+
updatedSession = mergeCollected(updatedSession, signalResult.mergedDirective.dataUpdate);
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// Use routing result for flow/step, but carry signal session state
|
|
630
|
+
// Merge routing session changes on top of signal session
|
|
631
|
+
const routingSession = routingResult.session;
|
|
632
|
+
updatedSession = {
|
|
633
|
+
...updatedSession,
|
|
634
|
+
currentFlow: routingSession.currentFlow,
|
|
635
|
+
currentStep: routingSession.currentStep,
|
|
636
|
+
flowHistory: routingSession.flowHistory,
|
|
637
|
+
pendingDirective: routingSession.pendingDirective,
|
|
638
|
+
};
|
|
639
|
+
|
|
640
|
+
const isFlowComplete = routingResult.isFlowComplete;
|
|
641
|
+
|
|
642
|
+
// PRE-EXTRACTION: same logic as below — extract data from user message
|
|
643
|
+
if (routingResult.selectedFlow && !isFlowComplete) {
|
|
644
|
+
if (this.shouldPreExtractData(routingResult.selectedFlow)) {
|
|
645
|
+
logger.debug(
|
|
646
|
+
`[ResponseModal] Pre-extracting data for flow: ${routingResult.selectedFlow.title}`
|
|
647
|
+
);
|
|
648
|
+
const extractedData = await this.preExtractFlowData({
|
|
649
|
+
route: routingResult.selectedFlow,
|
|
650
|
+
history: params.history,
|
|
651
|
+
context: params.context,
|
|
652
|
+
session: updatedSession,
|
|
653
|
+
signal: params.signal,
|
|
654
|
+
});
|
|
655
|
+
if (extractedData && Object.keys(extractedData).length > 0) {
|
|
656
|
+
logger.debug(`[ResponseModal] Pre-extracted data:`, extractedData);
|
|
657
|
+
updatedSession = mergeCollected(updatedSession, extractedData);
|
|
658
|
+
await this.agent.updateCollectedData(extractedData);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
// Determine next step
|
|
664
|
+
const stepResult = await this.responsePipeline.determineNextStep({
|
|
665
|
+
selectedFlow: routingResult.selectedFlow,
|
|
666
|
+
selectedStep: routingResult.selectedStep,
|
|
667
|
+
session: updatedSession,
|
|
668
|
+
isFlowComplete,
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
return {
|
|
672
|
+
selectedFlow: stepResult.flowChanged || routingResult.selectedFlow,
|
|
673
|
+
selectedStep: stepResult.nextStep,
|
|
674
|
+
responseDirectives: routingResult.responseDirectives,
|
|
675
|
+
session: stepResult.session,
|
|
676
|
+
isFlowComplete: stepResult.flowChanged ? false : isFlowComplete,
|
|
677
|
+
signalFirings: signalResult.firings,
|
|
678
|
+
signalPreDirective: signalResult.mergedDirective || undefined,
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// ── No signal processor: existing behavior (zero overhead) ────────────
|
|
525
683
|
const routingResult = await this.responsePipeline.handleRoutingAndStepSelection({
|
|
526
684
|
session: params.session,
|
|
527
|
-
history: params.history,
|
|
685
|
+
history: params.history,
|
|
528
686
|
context: params.context,
|
|
529
687
|
signal: params.signal,
|
|
530
688
|
});
|
|
531
689
|
|
|
532
690
|
let updatedSession = routingResult.session;
|
|
533
|
-
const
|
|
691
|
+
const isFlowComplete = routingResult.isFlowComplete;
|
|
534
692
|
|
|
535
|
-
// PRE-EXTRACTION: If entering a
|
|
693
|
+
// PRE-EXTRACTION: If entering a flow that collects data, extract data from user message first
|
|
536
694
|
// This allows us to skip steps whose data is already provided
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
//
|
|
540
|
-
|
|
541
|
-
if (this.shouldPreExtractData(routingResult.selectedRoute)) {
|
|
695
|
+
if (routingResult.selectedFlow && !isFlowComplete) {
|
|
696
|
+
// Always pre-extract when flow collects data (not just on new flow entry)
|
|
697
|
+
// This ensures step selection has the most up-to-date data
|
|
698
|
+
if (this.shouldPreExtractData(routingResult.selectedFlow)) {
|
|
542
699
|
logger.debug(
|
|
543
|
-
`[ResponseModal] Pre-extracting data for
|
|
700
|
+
`[ResponseModal] Pre-extracting data for flow: ${routingResult.selectedFlow.title}`
|
|
544
701
|
);
|
|
545
702
|
|
|
546
|
-
const extractedData = await this.
|
|
547
|
-
route: routingResult.
|
|
703
|
+
const extractedData = await this.preExtractFlowData({
|
|
704
|
+
route: routingResult.selectedFlow,
|
|
548
705
|
history: params.history,
|
|
549
706
|
context: params.context,
|
|
550
707
|
session: updatedSession,
|
|
@@ -556,7 +713,7 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
556
713
|
`[ResponseModal] Pre-extracted data:`,
|
|
557
714
|
extractedData
|
|
558
715
|
);
|
|
559
|
-
//
|
|
716
|
+
// Merge pre-extracted data into session before step selection
|
|
560
717
|
updatedSession = mergeCollected(updatedSession, extractedData);
|
|
561
718
|
// Also update agent's collected data
|
|
562
719
|
await this.agent.updateCollectedData(extractedData);
|
|
@@ -564,51 +721,21 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
564
721
|
}
|
|
565
722
|
}
|
|
566
723
|
|
|
567
|
-
// BATCH DETERMINATION: Use BatchExecutor to determine which steps can execute together
|
|
568
|
-
// Requirement 3.4: Pre-extraction results affect batch determination
|
|
569
|
-
let batchSteps: StepOptions<TContext, TData>[] | undefined;
|
|
570
|
-
let batchStoppedReason: StoppedReason | undefined;
|
|
571
|
-
let batchStoppedAtStep: StepOptions<TContext, TData> | undefined;
|
|
572
|
-
|
|
573
|
-
if (routingResult.selectedRoute && !isRouteComplete) {
|
|
574
|
-
// Determine current step position for batch determination
|
|
575
|
-
const currentStep = routingResult.selectedStep ||
|
|
576
|
-
(updatedSession.currentStep ? routingResult.selectedRoute.getStep(updatedSession.currentStep.id) : undefined);
|
|
577
|
-
|
|
578
|
-
logger.debug(`[ResponseModal] Determining batch starting from step: ${currentStep?.id || 'initial'}`);
|
|
579
|
-
|
|
580
|
-
const batchResult = await this.batchExecutor.determineBatch({
|
|
581
|
-
route: routingResult.selectedRoute,
|
|
582
|
-
currentStep,
|
|
583
|
-
sessionData: updatedSession.data || {},
|
|
584
|
-
context: params.context,
|
|
585
|
-
maxSteps: this.agent.getAgentOptions().maxStepsPerBatch,
|
|
586
|
-
});
|
|
587
|
-
|
|
588
|
-
batchSteps = batchResult.steps;
|
|
589
|
-
batchStoppedReason = batchResult.stoppedReason;
|
|
590
|
-
batchStoppedAtStep = batchResult.stoppedAtStep;
|
|
591
|
-
|
|
592
|
-
logger.debug(`[ResponseModal] Batch determined: ${batchSteps.length} steps, stopped reason: ${batchStoppedReason}`);
|
|
593
|
-
}
|
|
594
|
-
|
|
595
724
|
// Determine next step using pipeline method for consistency
|
|
596
725
|
const stepResult = await this.responsePipeline.determineNextStep({
|
|
597
|
-
|
|
726
|
+
selectedFlow: routingResult.selectedFlow,
|
|
598
727
|
selectedStep: routingResult.selectedStep,
|
|
599
728
|
session: updatedSession, // Use updated session with pre-extracted data
|
|
600
|
-
|
|
729
|
+
isFlowComplete, // Use updated completion status
|
|
601
730
|
});
|
|
602
731
|
|
|
603
732
|
return {
|
|
604
|
-
|
|
733
|
+
selectedFlow: stepResult.flowChanged || routingResult.selectedFlow,
|
|
605
734
|
selectedStep: stepResult.nextStep, // Use the determined next step
|
|
606
735
|
responseDirectives: routingResult.responseDirectives,
|
|
607
736
|
session: stepResult.session,
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
batchStoppedReason,
|
|
611
|
-
batchStoppedAtStep,
|
|
737
|
+
// If a branch changed the flow, the original isFlowComplete no longer applies
|
|
738
|
+
isFlowComplete: stepResult.flowChanged ? false : isFlowComplete,
|
|
612
739
|
};
|
|
613
740
|
} catch (error) {
|
|
614
741
|
throw ResponseGenerationError.fromError(error, 'routing_optimization', params);
|
|
@@ -616,20 +743,255 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
616
743
|
}
|
|
617
744
|
|
|
618
745
|
/**
|
|
619
|
-
*
|
|
746
|
+
* Apply a signal's position directive (goTo, goToStep, complete, abort, reset).
|
|
747
|
+
* Discards routing result and uses the signal's position decision.
|
|
748
|
+
* @private
|
|
749
|
+
* @requirements 8.3
|
|
750
|
+
*/
|
|
751
|
+
private applySignalPositionDirective(
|
|
752
|
+
signalResult: {
|
|
753
|
+
firings: SignalFiring<TContext, TData>[];
|
|
754
|
+
updatedSession: SessionState<TData>;
|
|
755
|
+
mergedDirective: PreDirective<TContext, TData> | undefined;
|
|
756
|
+
},
|
|
757
|
+
_params: { session: SessionState<TData>; history: Event[]; context: TContext },
|
|
758
|
+
): {
|
|
759
|
+
selectedFlow?: Flow<TContext, TData>;
|
|
760
|
+
selectedStep?: Step<TContext, TData>;
|
|
761
|
+
responseDirectives?: string[];
|
|
762
|
+
session: SessionState<TData>;
|
|
763
|
+
isFlowComplete: boolean;
|
|
764
|
+
signalFirings?: SignalFiring<TContext, TData>[];
|
|
765
|
+
signalPreDirective?: PreDirective<TContext, TData>;
|
|
766
|
+
signalHalted?: boolean;
|
|
767
|
+
signalHaltReply?: string;
|
|
768
|
+
} {
|
|
769
|
+
const directive = signalResult.mergedDirective!;
|
|
770
|
+
let session = signalResult.updatedSession;
|
|
771
|
+
const flows = this.agent.getFlows();
|
|
772
|
+
let selectedFlow: Flow<TContext, TData> | undefined;
|
|
773
|
+
let selectedStep: Step<TContext, TData> | undefined;
|
|
774
|
+
let isFlowComplete = false;
|
|
775
|
+
|
|
776
|
+
// Apply data updates if present alongside position
|
|
777
|
+
if (directive.dataUpdate) {
|
|
778
|
+
session = mergeCollected(session, directive.dataUpdate);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
if (directive.goTo) {
|
|
782
|
+
const flowTarget = typeof directive.goTo === 'string'
|
|
783
|
+
? directive.goTo
|
|
784
|
+
: directive.goTo.flow ?? directive.goTo.step;
|
|
785
|
+
|
|
786
|
+
if (flowTarget) {
|
|
787
|
+
const targetFlow = flows.find(f => f.id === flowTarget || f.title === flowTarget);
|
|
788
|
+
if (targetFlow) {
|
|
789
|
+
session = enterFlow(session, targetFlow.id, targetFlow.title);
|
|
790
|
+
selectedFlow = targetFlow;
|
|
791
|
+
|
|
792
|
+
if (typeof directive.goTo === 'object' && directive.goTo.step) {
|
|
793
|
+
const targetStep = targetFlow.getStep(directive.goTo.step);
|
|
794
|
+
if (targetStep) {
|
|
795
|
+
session = enterStep(session, targetStep.id, targetStep.description);
|
|
796
|
+
selectedStep = targetStep;
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
} else {
|
|
800
|
+
logger.warn(`[Signals] Pre-phase goTo target not found: "${flowTarget}". Falling back to no flow.`);
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
} else if (directive.goToStep) {
|
|
804
|
+
const stepTarget = typeof directive.goToStep === 'string'
|
|
805
|
+
? directive.goToStep
|
|
806
|
+
: directive.goToStep.step;
|
|
807
|
+
const flowTarget = typeof directive.goToStep === 'object'
|
|
808
|
+
? directive.goToStep.flow
|
|
809
|
+
: undefined;
|
|
810
|
+
|
|
811
|
+
if (flowTarget) {
|
|
812
|
+
const targetFlow = flows.find(f => f.id === flowTarget || f.title === flowTarget);
|
|
813
|
+
if (targetFlow) {
|
|
814
|
+
session = enterFlow(session, targetFlow.id, targetFlow.title);
|
|
815
|
+
selectedFlow = targetFlow;
|
|
816
|
+
const targetStep = targetFlow.getStep(stepTarget);
|
|
817
|
+
if (targetStep) {
|
|
818
|
+
session = enterStep(session, targetStep.id, targetStep.description);
|
|
819
|
+
selectedStep = targetStep;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
} else if (session.currentFlow) {
|
|
823
|
+
const currentFlow = flows.find(f => f.id === session.currentFlow?.id);
|
|
824
|
+
if (currentFlow) {
|
|
825
|
+
selectedFlow = currentFlow;
|
|
826
|
+
const targetStep = currentFlow.getStep(stepTarget);
|
|
827
|
+
if (targetStep) {
|
|
828
|
+
session = enterStep(session, targetStep.id, targetStep.description);
|
|
829
|
+
selectedStep = targetStep;
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
} else if (directive.complete) {
|
|
834
|
+
isFlowComplete = true;
|
|
835
|
+
} else if (directive.abort) {
|
|
836
|
+
// Abort — no flow, session cleared or marked
|
|
837
|
+
isFlowComplete = true;
|
|
838
|
+
} else if (directive.reset) {
|
|
839
|
+
if (session.currentFlow) {
|
|
840
|
+
const currentFlow = flows.find(f => f.id === session.currentFlow?.id);
|
|
841
|
+
if (currentFlow) {
|
|
842
|
+
selectedFlow = currentFlow;
|
|
843
|
+
const resetStep = typeof directive.reset === 'object' && directive.reset.step
|
|
844
|
+
? directive.reset.step
|
|
845
|
+
: undefined;
|
|
846
|
+
if (resetStep) {
|
|
847
|
+
const targetStep = currentFlow.getStep(resetStep);
|
|
848
|
+
if (targetStep) {
|
|
849
|
+
session = enterStep(session, targetStep.id, targetStep.description);
|
|
850
|
+
selectedStep = targetStep;
|
|
851
|
+
}
|
|
852
|
+
} else {
|
|
853
|
+
const initialStep = currentFlow.initialStep;
|
|
854
|
+
session = enterStep(session, initialStep.id, initialStep.description);
|
|
855
|
+
selectedStep = initialStep;
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
return {
|
|
862
|
+
selectedFlow,
|
|
863
|
+
selectedStep,
|
|
864
|
+
session,
|
|
865
|
+
isFlowComplete,
|
|
866
|
+
signalFirings: signalResult.firings,
|
|
867
|
+
signalPreDirective: signalResult.mergedDirective || undefined,
|
|
868
|
+
};
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
/**
|
|
872
|
+
* Routing skip optimization (Requirements 20.1, 20.2, 20.3):
|
|
873
|
+
* When the current step declares `collect` fields AND pre-extraction populates
|
|
874
|
+
* at least one of those fields from the user's message, skip routing for this turn.
|
|
875
|
+
*
|
|
876
|
+
* Returns the routing result if the skip applies, or undefined to fall through
|
|
877
|
+
* to normal routing.
|
|
878
|
+
* @private
|
|
879
|
+
*/
|
|
880
|
+
private async attemptRoutingSkipForCollect(params: {
|
|
881
|
+
session: SessionState<TData>;
|
|
882
|
+
history: Event[];
|
|
883
|
+
context: TContext;
|
|
884
|
+
signal?: AbortSignal;
|
|
885
|
+
}): Promise<{
|
|
886
|
+
selectedFlow?: Flow<TContext, TData>;
|
|
887
|
+
selectedStep?: Step<TContext, TData>;
|
|
888
|
+
responseDirectives?: string[];
|
|
889
|
+
session: SessionState<TData>;
|
|
890
|
+
isFlowComplete: boolean;
|
|
891
|
+
} | undefined> {
|
|
892
|
+
const { session } = params;
|
|
893
|
+
|
|
894
|
+
// Only applies when we already have a current flow and step
|
|
895
|
+
if (!session.currentFlow || !session.currentStep) {
|
|
896
|
+
return undefined;
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
// Also skip this optimization if there's a pending directive (it takes priority)
|
|
900
|
+
if (session.pendingDirective) {
|
|
901
|
+
return undefined;
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
// Look up the actual Flow and Step objects to access `collect`
|
|
905
|
+
const currentFlow = this.agent.getFlows().find(
|
|
906
|
+
(f) => f.id === session.currentFlow?.id
|
|
907
|
+
);
|
|
908
|
+
if (!currentFlow) {
|
|
909
|
+
return undefined;
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
const currentStep = currentFlow.getStep(session.currentStep.id);
|
|
913
|
+
if (!currentStep || !currentStep.collect || currentStep.collect.length === 0) {
|
|
914
|
+
return undefined;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
// We have a step with collect fields. Run pre-extraction to see if the
|
|
918
|
+
// user's message populates any of them.
|
|
919
|
+
const collectFields = currentStep.collect;
|
|
920
|
+
|
|
921
|
+
// Snapshot current data for comparison
|
|
922
|
+
const dataBefore = { ...session.data };
|
|
923
|
+
|
|
924
|
+
// Run pre-extraction against the current flow
|
|
925
|
+
const extractedData = await this.preExtractFlowData({
|
|
926
|
+
route: currentFlow,
|
|
927
|
+
history: params.history,
|
|
928
|
+
context: params.context,
|
|
929
|
+
session,
|
|
930
|
+
signal: params.signal,
|
|
931
|
+
});
|
|
932
|
+
|
|
933
|
+
if (!extractedData || Object.keys(extractedData).length === 0) {
|
|
934
|
+
return undefined;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
// Determine which collect fields were newly populated by pre-extraction
|
|
938
|
+
const populatedCollectFields: string[] = [];
|
|
939
|
+
for (const field of collectFields) {
|
|
940
|
+
const key = field as string;
|
|
941
|
+
const hadValue = dataBefore[field] !== undefined && dataBefore[field] !== null;
|
|
942
|
+
const hasNewValue = extractedData[field] !== undefined && extractedData[field] !== null;
|
|
943
|
+
if (hasNewValue && !hadValue) {
|
|
944
|
+
populatedCollectFields.push(key);
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
if (populatedCollectFields.length === 0) {
|
|
949
|
+
// Pre-extraction didn't populate any declared collect field — no skip
|
|
950
|
+
return undefined;
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
// ROUTING SKIP: pre-extraction populated collect fields → retain current flow/step
|
|
954
|
+
logger.debug(
|
|
955
|
+
`[ResponseModal] Routing skip: pre-extraction populated collect fields [${populatedCollectFields.join(', ')}] for step "${currentStep.id}" — skipping FlowRouter`
|
|
956
|
+
);
|
|
957
|
+
|
|
958
|
+
// Merge extracted data into session
|
|
959
|
+
const updatedSession = mergeCollected(session, extractedData);
|
|
960
|
+
await this.agent.updateCollectedData(extractedData);
|
|
961
|
+
|
|
962
|
+
// Determine next step using pipeline method for consistency
|
|
963
|
+
// Pass the current flow/step as the routing result (retained)
|
|
964
|
+
const stepResult = await this.responsePipeline.determineNextStep({
|
|
965
|
+
selectedFlow: currentFlow,
|
|
966
|
+
selectedStep: currentStep,
|
|
967
|
+
session: updatedSession,
|
|
968
|
+
isFlowComplete: false,
|
|
969
|
+
});
|
|
970
|
+
|
|
971
|
+
return {
|
|
972
|
+
selectedFlow: stepResult.flowChanged || currentFlow,
|
|
973
|
+
selectedStep: stepResult.nextStep,
|
|
974
|
+
responseDirectives: undefined,
|
|
975
|
+
session: stepResult.session,
|
|
976
|
+
isFlowComplete: stepResult.flowChanged ? false : false,
|
|
977
|
+
};
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
/**
|
|
981
|
+
* Check if a flow should pre-extract data before determining the initial step
|
|
620
982
|
* @private
|
|
621
983
|
*/
|
|
622
|
-
private shouldPreExtractData(
|
|
623
|
-
// Pre-extract if
|
|
624
|
-
if (
|
|
984
|
+
private shouldPreExtractData(flow: Flow<TContext, TData>): boolean {
|
|
985
|
+
// Pre-extract if flow has declared required or optional fields
|
|
986
|
+
if (flow.requiredFields && flow.requiredFields.length > 0) {
|
|
625
987
|
return true;
|
|
626
988
|
}
|
|
627
|
-
if (
|
|
989
|
+
if (flow.optionalFields && flow.optionalFields.length > 0) {
|
|
628
990
|
return true;
|
|
629
991
|
}
|
|
630
992
|
|
|
631
|
-
// Pre-extract if any step in the
|
|
632
|
-
const steps =
|
|
993
|
+
// Pre-extract if any step in the flow collects data
|
|
994
|
+
const steps = flow.getAllSteps();
|
|
633
995
|
const hasDataCollectionSteps = steps.some(
|
|
634
996
|
step => step.collect && step.collect.length > 0
|
|
635
997
|
);
|
|
@@ -638,21 +1000,21 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
638
1000
|
}
|
|
639
1001
|
|
|
640
1002
|
/**
|
|
641
|
-
* Pre-extract data from user message when entering a
|
|
1003
|
+
* Pre-extract data from user message when entering a flow
|
|
642
1004
|
* This allows skipping steps whose data is already provided
|
|
643
1005
|
* @private
|
|
644
1006
|
*/
|
|
645
|
-
private async
|
|
646
|
-
route:
|
|
1007
|
+
private async preExtractFlowData(params: {
|
|
1008
|
+
route: Flow<TContext, TData>;
|
|
647
1009
|
history: Event[];
|
|
648
1010
|
context: TContext;
|
|
649
1011
|
session: SessionState<TData>;
|
|
650
1012
|
signal?: AbortSignal;
|
|
651
1013
|
}): Promise<Partial<TData>> {
|
|
652
|
-
const { route, history, signal } = params;
|
|
1014
|
+
const { route: flow, history, signal } = params;
|
|
653
1015
|
|
|
654
|
-
// Build a schema for data extraction based on
|
|
655
|
-
const extractionSchema = this.agent.
|
|
1016
|
+
// Build a schema for data extraction based on flow's fields
|
|
1017
|
+
const extractionSchema = this.agent.schema;
|
|
656
1018
|
if (!extractionSchema) {
|
|
657
1019
|
logger.warn(`[ResponseModal] No schema available for pre-extraction`);
|
|
658
1020
|
return {};
|
|
@@ -672,11 +1034,11 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
672
1034
|
];
|
|
673
1035
|
|
|
674
1036
|
// Add field descriptions
|
|
675
|
-
if (
|
|
676
|
-
extractionPrompt.push(`Required fields: ${
|
|
1037
|
+
if (flow.requiredFields) {
|
|
1038
|
+
extractionPrompt.push(`Required fields: ${flow.requiredFields.join(', ')}`);
|
|
677
1039
|
}
|
|
678
|
-
if (
|
|
679
|
-
extractionPrompt.push(`Optional fields: ${
|
|
1040
|
+
if (flow.optionalFields) {
|
|
1041
|
+
extractionPrompt.push(`Optional fields: ${flow.optionalFields.join(', ')}`);
|
|
680
1042
|
}
|
|
681
1043
|
|
|
682
1044
|
extractionPrompt.push(
|
|
@@ -722,118 +1084,216 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
722
1084
|
effectiveContext,
|
|
723
1085
|
session: initialSession,
|
|
724
1086
|
history,
|
|
725
|
-
|
|
1087
|
+
selectedFlow,
|
|
726
1088
|
selectedStep,
|
|
727
1089
|
responseDirectives,
|
|
728
|
-
|
|
729
|
-
batchSteps,
|
|
730
|
-
batchStoppedReason,
|
|
1090
|
+
isFlowComplete,
|
|
731
1091
|
signal,
|
|
1092
|
+
signalFirings: preSignalFirings,
|
|
1093
|
+
signalPreDirective,
|
|
1094
|
+
signalHalted,
|
|
1095
|
+
signalHaltReply,
|
|
732
1096
|
} = responseContext;
|
|
733
1097
|
let session = initialSession;
|
|
734
1098
|
|
|
735
|
-
//
|
|
1099
|
+
// Accumulator for signal firings across both phases (fire order)
|
|
1100
|
+
const signalFirings: SignalFiring<TContext, TData>[] = [...(preSignalFirings || [])];
|
|
1101
|
+
|
|
1102
|
+
// Get last user message (needed for both flow and completion handling)
|
|
736
1103
|
// Convert HistoryItem[] to Event[] for internal processing
|
|
737
1104
|
const historyEvents = historyToEvents(history);
|
|
738
1105
|
|
|
1106
|
+
// ── SIGNAL HALT (Requirement 8.2) ─────────────────────────────────────
|
|
1107
|
+
// Pre-signal phase emitted halt → skip LLM call entirely.
|
|
1108
|
+
if (signalHalted) {
|
|
1109
|
+
const haltMessage = signalHaltReply || '';
|
|
1110
|
+
// Run post-signal phase even on halt (post-phase sees complete turn context)
|
|
1111
|
+
const postResult = await this.responsePipeline.runPostSignalPhase(
|
|
1112
|
+
session, effectiveContext, historyEvents,
|
|
1113
|
+
);
|
|
1114
|
+
session = postResult.updatedSession;
|
|
1115
|
+
signalFirings.push(...postResult.firings);
|
|
1116
|
+
|
|
1117
|
+
// Apply post-phase position directive as pendingDirective (Requirement 9.3)
|
|
1118
|
+
if (postResult.mergedDirective && hasDirectivePositionField(postResult.mergedDirective)) {
|
|
1119
|
+
session = { ...session, pendingDirective: postResult.mergedDirective };
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
await this.finalizeSession(session, effectiveContext);
|
|
1123
|
+
return {
|
|
1124
|
+
message: haltMessage,
|
|
1125
|
+
session,
|
|
1126
|
+
toolCalls: undefined,
|
|
1127
|
+
isFlowComplete: false,
|
|
1128
|
+
executedSteps: [],
|
|
1129
|
+
stoppedReason: signalHaltReply ? 'reply' : 'halt',
|
|
1130
|
+
triggeredSignals: signalFirings.length > 0 ? signalFirings as unknown as SignalFiring<unknown, TData>[] : undefined,
|
|
1131
|
+
};
|
|
1132
|
+
}
|
|
1133
|
+
|
|
739
1134
|
let message: string;
|
|
740
1135
|
let toolCalls: Array<{ toolName: string; arguments: Record<string, unknown> }> | undefined = undefined;
|
|
741
1136
|
let executedSteps: StepRef[] | undefined;
|
|
742
1137
|
let stoppedReason: StoppedReason | undefined;
|
|
1138
|
+
let appliedInstructions: AppliedInstruction[] | undefined;
|
|
743
1139
|
|
|
744
1140
|
|
|
745
1141
|
|
|
746
|
-
if (
|
|
747
|
-
//
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
1142
|
+
if (selectedFlow && !isFlowComplete) {
|
|
1143
|
+
// AUTO-CHAIN: Walk consecutive auto-steps before any LLM work.
|
|
1144
|
+
// If the current step is auto, the executor advances through it (and any
|
|
1145
|
+
// subsequent auto-steps) until an interactive step or terminal condition.
|
|
1146
|
+
let resolvedStep = selectedStep;
|
|
1147
|
+
const currentStepInstance = session.currentStep
|
|
1148
|
+
? selectedFlow.getStep(session.currentStep.id)
|
|
1149
|
+
: selectedStep;
|
|
751
1150
|
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
1151
|
+
if (currentStepInstance?.auto) {
|
|
1152
|
+
const autoChainExecutor = new AutoChainExecutor<TContext, TData>({
|
|
1153
|
+
maxAutoStepsPerTurn: this.agent.maxAutoStepsPerTurn,
|
|
1154
|
+
});
|
|
1155
|
+
const autoResult: AutoChainResult<TContext, TData> = await autoChainExecutor.run({
|
|
756
1156
|
session,
|
|
757
|
-
history,
|
|
758
1157
|
context: effectiveContext,
|
|
759
|
-
|
|
1158
|
+
flow: selectedFlow,
|
|
760
1159
|
});
|
|
761
1160
|
|
|
762
|
-
|
|
763
|
-
toolCalls = batchResult.toolCalls;
|
|
764
|
-
session = batchResult.session;
|
|
765
|
-
executedSteps = batchResult.executedSteps;
|
|
766
|
-
stoppedReason = batchStoppedReason;
|
|
1161
|
+
session = autoResult.session;
|
|
767
1162
|
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
1163
|
+
// Handle halt: emit verbatim reply, persist, return — no LLM call.
|
|
1164
|
+
if (autoResult.stoppedReason === 'halt') {
|
|
1165
|
+
message = autoResult.mergedDirective?.reply || '';
|
|
1166
|
+
stoppedReason = 'halt';
|
|
1167
|
+
executedSteps = [];
|
|
1168
|
+
|
|
1169
|
+
await this.finalizeSession(session, effectiveContext);
|
|
1170
|
+
return {
|
|
1171
|
+
message,
|
|
1172
|
+
session,
|
|
1173
|
+
toolCalls: undefined,
|
|
1174
|
+
isFlowComplete: false,
|
|
1175
|
+
executedSteps,
|
|
1176
|
+
stoppedReason,
|
|
1177
|
+
};
|
|
1178
|
+
}
|
|
781
1179
|
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
1180
|
+
// Handle flow completion or cross-flow redirect from auto-chain.
|
|
1181
|
+
// The auto-chain ended without resolving to an interactive step.
|
|
1182
|
+
// Possible reasons: last_step (no successor), completed (explicit
|
|
1183
|
+
// complete directive), or goto (cross-flow redirect).
|
|
1184
|
+
if (autoResult.stoppedReason === 'last_step' || autoResult.stoppedReason === 'completed' || autoResult.stoppedReason === 'goto') {
|
|
1185
|
+
logger.debug(`[ResponseModal] Auto-chain ended with ${autoResult.stoppedReason}`);
|
|
1186
|
+
session = await this.applyFlowCompletion({
|
|
1187
|
+
selectedFlow,
|
|
1188
|
+
session,
|
|
1189
|
+
context: effectiveContext,
|
|
1190
|
+
history,
|
|
1191
|
+
});
|
|
785
1192
|
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
1193
|
+
await this.finalizeSession(session, effectiveContext);
|
|
1194
|
+
return {
|
|
1195
|
+
message: '',
|
|
1196
|
+
session,
|
|
1197
|
+
toolCalls: undefined,
|
|
1198
|
+
isFlowComplete: true,
|
|
1199
|
+
executedSteps: [],
|
|
1200
|
+
stoppedReason: autoResult.stoppedReason,
|
|
1201
|
+
};
|
|
792
1202
|
}
|
|
793
|
-
stoppedReason = batchStoppedReason || 'needs_input';
|
|
794
|
-
}
|
|
795
1203
|
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
1204
|
+
// Normal case: auto-chain resolved to an interactive step.
|
|
1205
|
+
resolvedStep = autoResult.resolvedStep;
|
|
1206
|
+
}
|
|
799
1207
|
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
1208
|
+
// SINGLE STEP EXECUTION: Process the resolved interactive step.
|
|
1209
|
+
// The auto-chain (if it ran) already walked auto-steps. Only the
|
|
1210
|
+
// interactive step remains for the LLM call.
|
|
1211
|
+
const result = await this.processFlowResponse({
|
|
1212
|
+
selectedFlow,
|
|
1213
|
+
selectedStep: resolvedStep,
|
|
1214
|
+
responseDirectives,
|
|
1215
|
+
session,
|
|
1216
|
+
history,
|
|
1217
|
+
context: effectiveContext,
|
|
1218
|
+
historyEvents,
|
|
1219
|
+
signal,
|
|
1220
|
+
// Propagate signal pre-directive's appendPrompt for this turn's LLM call (Requirement 8.4)
|
|
1221
|
+
transientAppendage: signalPreDirective?.appendPrompt,
|
|
1222
|
+
// Merge signal pre-directive (halt/reply/injectTools) into the pre-LLM bus
|
|
1223
|
+
mergedPreDirective: signalPreDirective,
|
|
1224
|
+
});
|
|
809
1225
|
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
1226
|
+
message = result.message;
|
|
1227
|
+
toolCalls = result.toolCalls;
|
|
1228
|
+
session = result.session;
|
|
1229
|
+
appliedInstructions = result.appliedInstructions;
|
|
1230
|
+
|
|
1231
|
+
// Track executed step for single-step execution
|
|
1232
|
+
if (resolvedStep) {
|
|
1233
|
+
executedSteps = [{
|
|
1234
|
+
id: resolvedStep.id,
|
|
1235
|
+
flowId: selectedFlow.id,
|
|
1236
|
+
}];
|
|
820
1237
|
}
|
|
1238
|
+
// Use stoppedReason from processFlowResponse if set (halt/reply),
|
|
1239
|
+
// otherwise default to 'needs_input' for normal LLM responses.
|
|
1240
|
+
stoppedReason = result.stoppedReason || 'needs_input';
|
|
1241
|
+
|
|
1242
|
+
} else if (isFlowComplete && selectedFlow) {
|
|
1243
|
+
// Flow completion path: pure state transition, no LLM call.
|
|
1244
|
+
// The framework emits no message of its own.
|
|
1245
|
+
// stoppedReason is 'last_step' because this completion was detected by
|
|
1246
|
+
// implicit terminus (no successor or all successors skipped), not by an
|
|
1247
|
+
// explicit `complete` directive.
|
|
1248
|
+
logger.debug(`[ResponseModal] Releasing session to idle for completed flow: ${selectedFlow.title}`);
|
|
1249
|
+
|
|
1250
|
+
session = await this.applyFlowCompletion({
|
|
1251
|
+
selectedFlow,
|
|
1252
|
+
session,
|
|
1253
|
+
context: effectiveContext,
|
|
1254
|
+
history,
|
|
1255
|
+
});
|
|
1256
|
+
message = '';
|
|
1257
|
+
stoppedReason = 'last_step';
|
|
1258
|
+
executedSteps = [];
|
|
821
1259
|
|
|
822
1260
|
} else {
|
|
823
|
-
// Fallback: No
|
|
1261
|
+
// Fallback: No flows defined, generate a simple response
|
|
824
1262
|
|
|
825
|
-
|
|
1263
|
+
const fallbackResult = await this.generateFallbackResponse({
|
|
826
1264
|
history,
|
|
827
1265
|
context: effectiveContext,
|
|
828
1266
|
session,
|
|
829
1267
|
});
|
|
830
1268
|
|
|
1269
|
+
message = fallbackResult.message;
|
|
1270
|
+
appliedInstructions = fallbackResult.appliedInstructions;
|
|
1271
|
+
|
|
831
1272
|
// For fallback responses, set empty executedSteps and no stoppedReason
|
|
832
|
-
// since there's no
|
|
1273
|
+
// since there's no flow/step execution happening
|
|
833
1274
|
executedSteps = [];
|
|
834
1275
|
stoppedReason = undefined;
|
|
835
1276
|
}
|
|
836
1277
|
|
|
1278
|
+
// POST-SIGNAL PHASE (Requirement 9.1, 9.2, 9.3, 9.4)
|
|
1279
|
+
// Runs after finalize/onComplete and before session persistence.
|
|
1280
|
+
// Post-phase signals see the complete turn result: assistant message in
|
|
1281
|
+
// history, collected data, tool results.
|
|
1282
|
+
const postResult = await this.responsePipeline.runPostSignalPhase(
|
|
1283
|
+
session, effectiveContext, historyEvents,
|
|
1284
|
+
);
|
|
1285
|
+
session = postResult.updatedSession;
|
|
1286
|
+
|
|
1287
|
+
// Append post-phase firings to the accumulator (preserves fire order)
|
|
1288
|
+
signalFirings.push(...postResult.firings);
|
|
1289
|
+
|
|
1290
|
+
// Requirement 9.3: Post-phase position directive sets session.pendingDirective
|
|
1291
|
+
// (no mid-turn re-entry per D6 decision). Pre-LLM-only fields are already
|
|
1292
|
+
// dropped inside runPostSignalPhase per Phase 4.5.
|
|
1293
|
+
if (postResult.mergedDirective && hasDirectivePositionField(postResult.mergedDirective)) {
|
|
1294
|
+
session = { ...session, pendingDirective: postResult.mergedDirective };
|
|
1295
|
+
}
|
|
1296
|
+
|
|
837
1297
|
// Ensure response structure completeness (Requirement 8.1, 8.2, 8.3)
|
|
838
1298
|
// - executedSteps: array of steps executed (empty array if none)
|
|
839
1299
|
// - stoppedReason: why execution stopped (undefined for fallback)
|
|
@@ -842,281 +1302,300 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
842
1302
|
message,
|
|
843
1303
|
session,
|
|
844
1304
|
toolCalls,
|
|
845
|
-
|
|
1305
|
+
isFlowComplete: isFlowComplete,
|
|
846
1306
|
executedSteps: executedSteps || [],
|
|
847
1307
|
stoppedReason,
|
|
1308
|
+
appliedInstructions,
|
|
1309
|
+
triggeredSignals: signalFirings.length > 0 ? signalFirings as unknown as SignalFiring<unknown, TData>[] : undefined,
|
|
848
1310
|
};
|
|
849
1311
|
}
|
|
850
1312
|
|
|
851
1313
|
/**
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
1314
|
+
* Execute prepare function for current step if available
|
|
1315
|
+
* @private
|
|
1316
|
+
*/
|
|
1317
|
+
private async executeStepPrepare(session: SessionState<TData>, context: TContext): Promise<void> {
|
|
1318
|
+
if (session.currentFlow && session.currentStep) {
|
|
1319
|
+
const currentFlow = this.agent.getFlows().find(
|
|
1320
|
+
(r) => r.id === session.currentFlow?.id
|
|
1321
|
+
);
|
|
1322
|
+
if (currentFlow) {
|
|
1323
|
+
const currentStep = currentFlow.getStep(session.currentStep.id);
|
|
1324
|
+
// Skip auto-steps — their prepare is handled by AutoChainExecutor
|
|
1325
|
+
if (currentStep?.auto) {
|
|
1326
|
+
logger.debug(`[ResponseModal] Skipping pre-routing prepare for auto-step: ${currentStep.id}`);
|
|
1327
|
+
return;
|
|
1328
|
+
}
|
|
1329
|
+
if (currentStep?.prepare) {
|
|
1330
|
+
logger.debug(`[ResponseModal] Executing prepare for step: ${currentStep.id}`);
|
|
1331
|
+
await this.executePrepareFinalize(
|
|
1332
|
+
currentStep.prepare,
|
|
1333
|
+
context,
|
|
1334
|
+
session.data,
|
|
1335
|
+
currentFlow,
|
|
1336
|
+
currentStep
|
|
1337
|
+
);
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
/**
|
|
1344
|
+
* Execute finalize function for current step if available
|
|
1345
|
+
* @private
|
|
1346
|
+
*/
|
|
1347
|
+
private async executeStepFinalize(session: SessionState<TData>, context: TContext): Promise<void> {
|
|
1348
|
+
if (session.currentFlow && session.currentStep) {
|
|
1349
|
+
const currentFlow = this.agent.getFlows().find(
|
|
1350
|
+
(r) => r.id === session.currentFlow?.id
|
|
1351
|
+
);
|
|
1352
|
+
if (currentFlow) {
|
|
1353
|
+
const currentStep = currentFlow.getStep(session.currentStep.id);
|
|
1354
|
+
if (currentStep?.finalize) {
|
|
1355
|
+
logger.debug(
|
|
1356
|
+
`[ResponseModal] Executing finalize for step: ${currentStep.id}`
|
|
1357
|
+
);
|
|
1358
|
+
await this.executePrepareFinalize(
|
|
1359
|
+
currentStep.finalize,
|
|
1360
|
+
context,
|
|
1361
|
+
session.data,
|
|
1362
|
+
currentFlow,
|
|
1363
|
+
currentStep
|
|
1364
|
+
);
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
/**
|
|
1371
|
+
* Process flow response with unified tool execution and data collection
|
|
861
1372
|
* @private
|
|
862
|
-
* **Validates: Requirements 1.1, 4.4, 5.1, 5.2**
|
|
863
1373
|
*/
|
|
864
|
-
private async
|
|
865
|
-
|
|
866
|
-
|
|
1374
|
+
private async processFlowResponse(params: {
|
|
1375
|
+
selectedFlow: Flow<TContext, TData>;
|
|
1376
|
+
selectedStep?: Step<TContext, TData>;
|
|
867
1377
|
responseDirectives?: string[];
|
|
868
1378
|
session: SessionState<TData>;
|
|
869
1379
|
history: HistoryItem[];
|
|
870
1380
|
context: TContext;
|
|
871
1381
|
historyEvents: Event[];
|
|
872
1382
|
signal?: AbortSignal;
|
|
1383
|
+
/**
|
|
1384
|
+
* Per-turn transient appendage from merged PreDirective.appendPrompt.
|
|
1385
|
+
* Fresh every turn, never cached, never persisted.
|
|
1386
|
+
*/
|
|
1387
|
+
transientAppendage?: string[];
|
|
1388
|
+
/**
|
|
1389
|
+
* Merged PreDirective from the directive bus's pre-LLM phase drain.
|
|
1390
|
+
* When `halt: true`, the LLM call is skipped entirely.
|
|
1391
|
+
*/
|
|
1392
|
+
mergedPreDirective?: PreDirective<TContext, TData>;
|
|
873
1393
|
}): Promise<{
|
|
874
1394
|
message: string;
|
|
875
1395
|
toolCalls?: Array<{ toolName: string; arguments: Record<string, unknown> }>;
|
|
876
1396
|
session: SessionState<TData>;
|
|
877
|
-
|
|
1397
|
+
appliedInstructions?: AppliedInstruction[];
|
|
1398
|
+
stoppedReason?: StoppedReason;
|
|
878
1399
|
}> {
|
|
879
|
-
const {
|
|
1400
|
+
const { selectedFlow, selectedStep, responseDirectives, history, context, historyEvents, signal, transientAppendage, mergedPreDirective } = params;
|
|
880
1401
|
let session = params.session;
|
|
881
1402
|
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
const route = selectedRoute;
|
|
893
|
-
// Convert StepOptions to Step if needed for executePrepareFinalize
|
|
894
|
-
const stepInstance = step?.id ? route.getStep(step.id) : undefined;
|
|
895
|
-
await this.executePrepareFinalize(hook, hookContext, data, route, stepInstance);
|
|
896
|
-
};
|
|
1403
|
+
// Determine next step
|
|
1404
|
+
let nextStep: Step<TContext, TData>;
|
|
1405
|
+
if (selectedStep) {
|
|
1406
|
+
nextStep = selectedStep;
|
|
1407
|
+
} else {
|
|
1408
|
+
// Determine current step from session if we're already in this flow
|
|
1409
|
+
const isInSameFlow = session.currentFlow?.id === selectedFlow.id;
|
|
1410
|
+
const currentStep = isInSameFlow && session.currentStep
|
|
1411
|
+
? selectedFlow.getStep(session.currentStep.id)
|
|
1412
|
+
: undefined;
|
|
897
1413
|
|
|
898
|
-
|
|
899
|
-
logger.debug(`[ResponseModal] Executing prepare hooks for batch`);
|
|
900
|
-
const prepareResult = await this.batchExecutor.executePrepareHooks({
|
|
901
|
-
steps: batchSteps,
|
|
902
|
-
context,
|
|
903
|
-
data: session.data,
|
|
904
|
-
executeHook,
|
|
905
|
-
});
|
|
1414
|
+
logger.debug(`[ResponseModal] Step determination: flow match=${isInSameFlow}, currentFlow=${session.currentFlow?.id}, selectedFlow=${selectedFlow.id}, currentStep=${currentStep?.id || 'none'}`);
|
|
906
1415
|
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
{
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
1416
|
+
// STEP 1 (Algorithm 1): branches win over linear chain
|
|
1417
|
+
if (currentStep?.branches && currentStep.branches.length > 0) {
|
|
1418
|
+
const branchResult = await this.responsePipeline.evaluateStepBranches(
|
|
1419
|
+
currentStep, selectedFlow, session, context
|
|
1420
|
+
);
|
|
1421
|
+
if (branchResult) {
|
|
1422
|
+
if (branchResult.nextStep) {
|
|
1423
|
+
nextStep = branchResult.nextStep;
|
|
1424
|
+
session = branchResult.session;
|
|
1425
|
+
} else {
|
|
1426
|
+
// Flow transition or completion — no local step to render
|
|
1427
|
+
// Return empty message with updated session; caller handles flow transition
|
|
1428
|
+
return { message: '', session: branchResult.session };
|
|
917
1429
|
}
|
|
918
1430
|
}
|
|
919
|
-
|
|
920
|
-
}
|
|
921
|
-
|
|
922
|
-
// PHASE 2: Build combined prompt using BatchPromptBuilder (Requirement 4.4)
|
|
923
|
-
logger.debug(`[ResponseModal] Building batch prompt`);
|
|
924
|
-
const batchPromptResult = await this.batchPromptBuilder.buildBatchPrompt({
|
|
925
|
-
steps: batchSteps,
|
|
926
|
-
route: selectedRoute,
|
|
927
|
-
history: historyEvents,
|
|
928
|
-
context,
|
|
929
|
-
session,
|
|
930
|
-
agentOptions: this.agent.getAgentOptions(),
|
|
931
|
-
});
|
|
932
|
-
|
|
933
|
-
logger.debug(`[ResponseModal] Batch prompt built with ${batchPromptResult.stepCount} steps, collecting: ${batchPromptResult.collectFields.join(', ')}`);
|
|
934
|
-
|
|
935
|
-
// Build response schema for batch (includes all collect fields)
|
|
936
|
-
const responseSchema = this.buildBatchResponseSchema(batchPromptResult.collectFields);
|
|
937
|
-
|
|
938
|
-
// Collect available tools for AI (from all steps in batch)
|
|
939
|
-
const availableTools = this.collectBatchAvailableTools(selectedRoute, batchSteps);
|
|
1431
|
+
}
|
|
940
1432
|
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
signal,
|
|
950
|
-
parameters: responseSchema ? { jsonSchema: responseSchema, schemaName: "batch_response" } : undefined,
|
|
951
|
-
});
|
|
1433
|
+
if (!nextStep!) {
|
|
1434
|
+
// Get candidate steps based on current position in the flow
|
|
1435
|
+
const flowRouter = this.agent.getFlowRouter();
|
|
1436
|
+
const candidates = await flowRouter.getCandidateStepsWithConditions(
|
|
1437
|
+
selectedFlow,
|
|
1438
|
+
currentStep, // Pass current step instead of undefined to maintain progression
|
|
1439
|
+
createTemplateContext({ data: session.data, session, context })
|
|
1440
|
+
);
|
|
952
1441
|
|
|
953
|
-
|
|
954
|
-
let toolCalls = result.structured?.toolCalls;
|
|
1442
|
+
logger.debug(`[ResponseModal] Found ${candidates.length} candidate steps${currentStep ? ' from current step ' + currentStep.id : ' (new flow entry)'}`);
|
|
955
1443
|
|
|
956
|
-
|
|
1444
|
+
if (candidates.length > 0) {
|
|
1445
|
+
nextStep = candidates[0].step;
|
|
1446
|
+
logger.debug(`[ResponseModal] Using first valid step: ${nextStep.id}${currentStep ? ' (progressing from ' + currentStep.id + ')' : ' for new flow'}`);
|
|
1447
|
+
} else {
|
|
1448
|
+
// Fallback to initial step even if it should be skipped
|
|
1449
|
+
nextStep = selectedFlow.initialStep;
|
|
1450
|
+
logger.warn(`[FlowConfigurationError] No valid steps found: all candidates were skipped in flow. Falling back to initial step "${nextStep.id}". Review step skip conditions.`);
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
957
1454
|
|
|
958
|
-
//
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
1455
|
+
// Update session with next step
|
|
1456
|
+
// If the next step has requires fields that are missing, stay at the previous step
|
|
1457
|
+
if (nextStep.requires && nextStep.requires.length > 0) {
|
|
1458
|
+
const sessionData = session.data || {};
|
|
1459
|
+
const missingRequires = nextStep.requires.filter(
|
|
1460
|
+
field => (sessionData as Record<string, unknown>)[String(field)] === undefined
|
|
1461
|
+
);
|
|
1462
|
+
if (missingRequires.length > 0) {
|
|
1463
|
+
const warning = `[FlowConfigurationError] Cannot advance to step "${nextStep.description || nextStep.id}": ` +
|
|
1464
|
+
`missing required fields [${missingRequires.join(', ')}]. Staying at current step. Ensure preceding steps collect these fields.`;
|
|
1465
|
+
logger.warn(warning);
|
|
1466
|
+
console.warn(warning);
|
|
1467
|
+
// Stay at the current step - don't enter the next one
|
|
1468
|
+
const currentStepId = session.currentStep?.id;
|
|
1469
|
+
if (currentStepId) {
|
|
1470
|
+
const currentStepInstance = selectedFlow.getStep(currentStepId);
|
|
1471
|
+
if (currentStepInstance) {
|
|
1472
|
+
nextStep = currentStepInstance;
|
|
1473
|
+
logger.debug(`[ResponseModal] Staying at current step: ${nextStep.id} due to missing requires`);
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
} else {
|
|
1477
|
+
session = enterStep(session, nextStep.id, nextStep.description);
|
|
1478
|
+
logger.debug(`[ResponseModal] Entered step: ${nextStep.id}`);
|
|
976
1479
|
}
|
|
1480
|
+
} else {
|
|
1481
|
+
session = enterStep(session, nextStep.id, nextStep.description);
|
|
1482
|
+
logger.debug(`[ResponseModal] Entered step: ${nextStep.id}`);
|
|
977
1483
|
}
|
|
978
1484
|
|
|
979
|
-
//
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
if (collectResult.validationErrors && collectResult.validationErrors.length > 0) {
|
|
997
|
-
logger.warn(`[ResponseModal] Batch data validation errors:`, collectResult.validationErrors);
|
|
1485
|
+
// Build response schema for this flow (with collect fields from step)
|
|
1486
|
+
const responseSchema = this.responseEngine.responseSchemaForFlow(selectedFlow, nextStep, this.agent.schema);
|
|
1487
|
+
|
|
1488
|
+
// ── HALT SHORT-CIRCUIT (Requirement 2.5, 2.6, 2.7) ──────────────────────
|
|
1489
|
+
// After pre-LLM emissions are merged, if `halt: true` then skip the LLM
|
|
1490
|
+
// call entirely. The behavior depends on whether `reply` is also set.
|
|
1491
|
+
if (mergedPreDirective?.halt) {
|
|
1492
|
+
if (mergedPreDirective.reply) {
|
|
1493
|
+
// halt + reply: emit the reply as the assistant message
|
|
1494
|
+
logger.debug(`[ResponseModal] Halt with reply — skipping LLM call for step ${nextStep.id}`);
|
|
1495
|
+
return { message: mergedPreDirective.reply, session, stoppedReason: 'reply' };
|
|
1496
|
+
} else {
|
|
1497
|
+
// halt without reply: emit empty assistant content
|
|
1498
|
+
logger.debug(`[ResponseModal] Halt without reply — skipping LLM call for step ${nextStep.id}`);
|
|
1499
|
+
return { message: '', session, stoppedReason: 'halt' };
|
|
1500
|
+
}
|
|
998
1501
|
}
|
|
999
1502
|
|
|
1000
|
-
//
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1503
|
+
// ── STEP.REPLY SHORT-CIRCUIT (Requirement 25.1–25.7, 17.9) ──────────────
|
|
1504
|
+
// A step with `reply` set emits a verbatim template response without LLM.
|
|
1505
|
+
// onEnter and prepare have already fired normally at this point.
|
|
1506
|
+
// If prepare returned a PreDirective with `reply`, that overrides
|
|
1507
|
+
// the step-declared reply (last-emission-wins per Algorithm 4).
|
|
1508
|
+
if (nextStep.reply != null) {
|
|
1509
|
+
// Determine the effective reply: prepare-emitted reply wins over step-declared
|
|
1510
|
+
const effectiveReply = mergedPreDirective?.reply ?? await render(
|
|
1511
|
+
nextStep.reply,
|
|
1512
|
+
createTemplateContext({ data: session.data || {}, context, session })
|
|
1513
|
+
);
|
|
1514
|
+
logger.debug(`[ResponseModal] Step.reply — skipping LLM call for step ${nextStep.id}`);
|
|
1515
|
+
return { message: effectiveReply, session, stoppedReason: 'reply' };
|
|
1005
1516
|
}
|
|
1006
1517
|
|
|
1007
|
-
//
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1518
|
+
// Transient appendage: per-turn slot from PreDirective.appendPrompt.
|
|
1519
|
+
// Fresh each turn, never cached, never persisted.
|
|
1520
|
+
// Wrapped in try/finally to ensure cleanup even on abnormal termination.
|
|
1521
|
+
let turnTransientAppendage: string[] | undefined = transientAppendage;
|
|
1522
|
+
try {
|
|
1523
|
+
// Build response prompt
|
|
1524
|
+
const { prompt: responsePrompt, appliedInstructions } = await this.responseEngine.buildResponsePrompt({
|
|
1525
|
+
flow: selectedFlow,
|
|
1526
|
+
currentStep: nextStep,
|
|
1527
|
+
rules: [],
|
|
1528
|
+
prohibitions: [],
|
|
1529
|
+
directives: responseDirectives,
|
|
1530
|
+
history: historyEvents,
|
|
1531
|
+
agentOptions: this.agent.getAgentOptions(),
|
|
1532
|
+
instructions: this.collectScopedInstructions(selectedFlow, nextStep),
|
|
1533
|
+
combinedTerms: this.agent.getTerms(),
|
|
1534
|
+
context,
|
|
1535
|
+
session,
|
|
1536
|
+
agentSchema: this.agent.schema,
|
|
1537
|
+
transientAppendage: turnTransientAppendage,
|
|
1538
|
+
});
|
|
1020
1539
|
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
.filter(step => step.id)
|
|
1024
|
-
.map(step => ({
|
|
1025
|
-
id: step.id!,
|
|
1026
|
-
routeId: selectedRoute.id,
|
|
1027
|
-
}));
|
|
1540
|
+
// Collect available tools for AI
|
|
1541
|
+
const availableTools = this.collectAvailableTools(selectedFlow, nextStep);
|
|
1028
1542
|
|
|
1029
|
-
|
|
1543
|
+
// Generate message using AI provider
|
|
1544
|
+
const agentOptions = this.agent.getAgentOptions();
|
|
1545
|
+
const result = await agentOptions.provider.generateMessage({
|
|
1546
|
+
prompt: responsePrompt,
|
|
1547
|
+
history, // Use HistoryItem[] for AI provider
|
|
1548
|
+
context,
|
|
1549
|
+
tools: availableTools,
|
|
1550
|
+
signal,
|
|
1551
|
+
parameters: responseSchema ? { jsonSchema: responseSchema, schemaName: "response_output" } : undefined,
|
|
1552
|
+
});
|
|
1030
1553
|
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
toolCalls,
|
|
1034
|
-
session,
|
|
1035
|
-
executedSteps,
|
|
1036
|
-
};
|
|
1037
|
-
}
|
|
1554
|
+
let message = result.structured?.message || result.message;
|
|
1555
|
+
let toolCalls = result.structured?.toolCalls;
|
|
1038
1556
|
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
description: "Natural, conversational response directed at the user. Must NOT contain field names, raw data, or internal information.",
|
|
1048
|
-
},
|
|
1049
|
-
};
|
|
1557
|
+
// Debug: Log initial AI response
|
|
1558
|
+
logger.debug(`[ResponseModal] Initial AI response:`, {
|
|
1559
|
+
hasMessage: !!message,
|
|
1560
|
+
messageLength: message?.length || 0,
|
|
1561
|
+
hasToolCalls: !!toolCalls,
|
|
1562
|
+
toolCallsCount: toolCalls?.length || 0,
|
|
1563
|
+
toolNames: toolCalls?.map(tc => tc.toolName) || [],
|
|
1564
|
+
});
|
|
1050
1565
|
|
|
1051
|
-
|
|
1566
|
+
// Execute tools with unified loop handling
|
|
1567
|
+
const toolResult = await this.executeUnifiedToolLoop({
|
|
1568
|
+
toolCalls,
|
|
1569
|
+
context,
|
|
1570
|
+
session,
|
|
1571
|
+
history,
|
|
1572
|
+
selectedFlow,
|
|
1573
|
+
responsePrompt,
|
|
1574
|
+
availableTools,
|
|
1575
|
+
responseSchema,
|
|
1576
|
+
signal,
|
|
1577
|
+
});
|
|
1052
1578
|
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
if (
|
|
1056
|
-
|
|
1057
|
-
} else {
|
|
1058
|
-
// Dynamic fallback when no agent schema is defined
|
|
1059
|
-
properties[field] = {
|
|
1060
|
-
type: "string",
|
|
1061
|
-
description: `Collected value for ${field}`,
|
|
1062
|
-
};
|
|
1579
|
+
session = toolResult.session;
|
|
1580
|
+
toolCalls = toolResult.finalToolCalls;
|
|
1581
|
+
if (toolResult.finalMessage) {
|
|
1582
|
+
message = toolResult.finalMessage;
|
|
1063
1583
|
}
|
|
1064
|
-
}
|
|
1065
|
-
|
|
1066
|
-
return {
|
|
1067
|
-
type: "object",
|
|
1068
|
-
properties,
|
|
1069
|
-
required: ["message"],
|
|
1070
|
-
additionalProperties: true,
|
|
1071
|
-
};
|
|
1072
|
-
}
|
|
1073
1584
|
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
const availableTools = new Map<string, Tool<TContext, TData>>();
|
|
1088
|
-
|
|
1089
|
-
// Add agent-level tools
|
|
1090
|
-
this.agent.getTools().forEach((tool) => {
|
|
1091
|
-
availableTools.set(tool.id, tool);
|
|
1092
|
-
});
|
|
1093
|
-
|
|
1094
|
-
// Add route-level tools
|
|
1095
|
-
route.getTools().forEach((tool: Tool<TContext, TData>) => {
|
|
1096
|
-
availableTools.set(tool.id, tool);
|
|
1097
|
-
});
|
|
1098
|
-
|
|
1099
|
-
// Add step-level tools from all batch steps
|
|
1100
|
-
for (const step of batchSteps) {
|
|
1101
|
-
if (step.tools) {
|
|
1102
|
-
for (const toolRef of step.tools) {
|
|
1103
|
-
if (typeof toolRef === "string") {
|
|
1104
|
-
// Reference to registered tool - already in availableTools
|
|
1105
|
-
} else if (typeof toolRef === 'object' && 'id' in toolRef && toolRef.id) {
|
|
1106
|
-
// Inline tool definition
|
|
1107
|
-
availableTools.set(toolRef.id, toolRef);
|
|
1108
|
-
}
|
|
1109
|
-
}
|
|
1110
|
-
}
|
|
1585
|
+
// Collect data from response
|
|
1586
|
+
// Use follow-up structured data from tool loop when available, fall back to original result
|
|
1587
|
+
const dataSource = toolResult.structured
|
|
1588
|
+
? { structured: toolResult.structured }
|
|
1589
|
+
: result;
|
|
1590
|
+
session = await this.collectDataFromResponse({ result: dataSource, selectedFlow, nextStep, session });
|
|
1591
|
+
|
|
1592
|
+
return { message, toolCalls, session, appliedInstructions };
|
|
1593
|
+
} finally {
|
|
1594
|
+
// Drain the transient appendage at end of turn.
|
|
1595
|
+
// This ensures PreDirective.appendPrompt does not leak to subsequent
|
|
1596
|
+
// turns even when the turn terminates abnormally (error, abort, reject).
|
|
1597
|
+
turnTransientAppendage = undefined;
|
|
1111
1598
|
}
|
|
1112
|
-
|
|
1113
|
-
// Convert to the format expected by AI providers
|
|
1114
|
-
return Array.from(availableTools.values()).map((tool) => ({
|
|
1115
|
-
id: tool.id,
|
|
1116
|
-
name: tool.name || tool.id,
|
|
1117
|
-
description: tool.description,
|
|
1118
|
-
parameters: tool.parameters,
|
|
1119
|
-
}));
|
|
1120
1599
|
}
|
|
1121
1600
|
|
|
1122
1601
|
/**
|
|
@@ -1130,426 +1609,188 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
1130
1609
|
effectiveContext,
|
|
1131
1610
|
session: initialSession,
|
|
1132
1611
|
history,
|
|
1133
|
-
|
|
1612
|
+
selectedFlow,
|
|
1134
1613
|
selectedStep,
|
|
1135
1614
|
responseDirectives,
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1615
|
+
isFlowComplete,
|
|
1616
|
+
signal,
|
|
1617
|
+
signalFirings: preSignalFirings,
|
|
1618
|
+
signalPreDirective,
|
|
1619
|
+
signalHalted,
|
|
1620
|
+
signalHaltReply,
|
|
1139
1621
|
} = responseContext;
|
|
1140
|
-
|
|
1622
|
+
let session = initialSession;
|
|
1623
|
+
|
|
1624
|
+
// Accumulator for signal firings across both phases (fire order)
|
|
1625
|
+
const signalFirings: SignalFiring<TContext, TData>[] = [...(preSignalFirings || [])];
|
|
1141
1626
|
|
|
1142
|
-
// Get last user message (needed for both route and completion handling)
|
|
1143
1627
|
// Convert HistoryItem[] to Event[] for internal processing
|
|
1144
1628
|
const historyEvents = historyToEvents(history);
|
|
1145
1629
|
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
yield* this.streamBatchResponse({
|
|
1154
|
-
selectedRoute,
|
|
1155
|
-
batchSteps,
|
|
1156
|
-
responseDirectives,
|
|
1157
|
-
session,
|
|
1158
|
-
history,
|
|
1159
|
-
context: effectiveContext,
|
|
1160
|
-
historyEvents,
|
|
1161
|
-
batchStoppedReason,
|
|
1162
|
-
});
|
|
1163
|
-
} else {
|
|
1164
|
-
// SINGLE STEP EXECUTION: Fall back to single-step streaming
|
|
1165
|
-
yield* this.processRouteStreamingResponse({
|
|
1166
|
-
selectedRoute,
|
|
1167
|
-
selectedStep,
|
|
1168
|
-
responseDirectives,
|
|
1169
|
-
session,
|
|
1170
|
-
history,
|
|
1171
|
-
context: effectiveContext,
|
|
1172
|
-
historyEvents,
|
|
1173
|
-
});
|
|
1174
|
-
}
|
|
1175
|
-
|
|
1176
|
-
} else if (isRouteComplete && selectedRoute) {
|
|
1177
|
-
// Handle route completion streaming
|
|
1178
|
-
yield* this.streamRouteCompletion({
|
|
1179
|
-
selectedRoute,
|
|
1180
|
-
session,
|
|
1181
|
-
context: effectiveContext,
|
|
1182
|
-
history,
|
|
1183
|
-
historyEvents,
|
|
1184
|
-
});
|
|
1185
|
-
|
|
1186
|
-
} else {
|
|
1187
|
-
// Fallback: No routes defined, stream a simple response
|
|
1188
|
-
yield* this.streamFallbackResponse({
|
|
1189
|
-
history,
|
|
1190
|
-
context: effectiveContext,
|
|
1191
|
-
session,
|
|
1192
|
-
});
|
|
1193
|
-
}
|
|
1194
|
-
}
|
|
1195
|
-
|
|
1196
|
-
/**
|
|
1197
|
-
* Stream a batch response with multiple steps
|
|
1198
|
-
*
|
|
1199
|
-
* Similar to executeBatchResponse but streams the LLM response.
|
|
1200
|
-
*
|
|
1201
|
-
* @private
|
|
1202
|
-
*/
|
|
1203
|
-
private async *streamBatchResponse(params: {
|
|
1204
|
-
selectedRoute: Route<TContext, TData>;
|
|
1205
|
-
batchSteps: StepOptions<TContext, TData>[];
|
|
1206
|
-
responseDirectives?: string[];
|
|
1207
|
-
session: SessionState<TData>;
|
|
1208
|
-
history: HistoryItem[];
|
|
1209
|
-
context: TContext;
|
|
1210
|
-
historyEvents: Event[];
|
|
1211
|
-
batchStoppedReason?: StoppedReason;
|
|
1212
|
-
signal?: AbortSignal;
|
|
1213
|
-
}): AsyncGenerator<AgentResponseStreamChunk<TData>> {
|
|
1214
|
-
const { selectedRoute, batchSteps, history, context, historyEvents, batchStoppedReason, signal } = params;
|
|
1215
|
-
let session = params.session;
|
|
1216
|
-
|
|
1217
|
-
// Create hook executor function
|
|
1218
|
-
const executeHook = async (
|
|
1219
|
-
hook: HookFunction<TContext, TData>,
|
|
1220
|
-
hookContext: TContext,
|
|
1221
|
-
data?: Partial<TData>,
|
|
1222
|
-
step?: StepOptions<TContext, TData>
|
|
1223
|
-
): Promise<void> => {
|
|
1224
|
-
const route = selectedRoute;
|
|
1225
|
-
const stepInstance = step?.id ? route.getStep(step.id) : undefined;
|
|
1226
|
-
await this.executePrepareFinalize(hook, hookContext, data, route, stepInstance);
|
|
1227
|
-
};
|
|
1228
|
-
|
|
1229
|
-
// PHASE 1: Execute all prepare hooks
|
|
1230
|
-
const prepareResult = await this.batchExecutor.executePrepareHooks({
|
|
1231
|
-
steps: batchSteps,
|
|
1232
|
-
context,
|
|
1233
|
-
data: session.data,
|
|
1234
|
-
executeHook,
|
|
1235
|
-
});
|
|
1236
|
-
|
|
1237
|
-
if (!prepareResult.success) {
|
|
1238
|
-
// Yield error chunk
|
|
1239
|
-
yield {
|
|
1240
|
-
delta: "",
|
|
1241
|
-
accumulated: "",
|
|
1242
|
-
done: true,
|
|
1243
|
-
session,
|
|
1244
|
-
error: new ResponseGenerationError(
|
|
1245
|
-
`Prepare hook failed: ${prepareResult.error?.message}`,
|
|
1246
|
-
{ phase: 'prepare_hooks' }
|
|
1247
|
-
),
|
|
1248
|
-
};
|
|
1249
|
-
return;
|
|
1250
|
-
}
|
|
1251
|
-
|
|
1252
|
-
// PHASE 2: Build combined prompt
|
|
1253
|
-
const batchPromptResult = await this.batchPromptBuilder.buildBatchPrompt({
|
|
1254
|
-
steps: batchSteps,
|
|
1255
|
-
route: selectedRoute,
|
|
1256
|
-
history: historyEvents,
|
|
1257
|
-
context,
|
|
1258
|
-
session,
|
|
1259
|
-
agentOptions: this.agent.getAgentOptions(),
|
|
1260
|
-
});
|
|
1261
|
-
|
|
1262
|
-
const responseSchema = this.buildBatchResponseSchema(batchPromptResult.collectFields);
|
|
1263
|
-
const availableTools = this.collectBatchAvailableTools(selectedRoute, batchSteps);
|
|
1264
|
-
|
|
1265
|
-
// PHASE 3: Stream LLM response
|
|
1266
|
-
const agentOptions = this.agent.getAgentOptions();
|
|
1267
|
-
const stream = agentOptions.provider.generateMessageStream({
|
|
1268
|
-
prompt: batchPromptResult.prompt,
|
|
1269
|
-
history, // Use HistoryItem[] for AI provider
|
|
1270
|
-
context,
|
|
1271
|
-
tools: availableTools,
|
|
1272
|
-
signal,
|
|
1273
|
-
parameters: responseSchema ? { jsonSchema: responseSchema, schemaName: "batch_stream_response" } : undefined,
|
|
1274
|
-
});
|
|
1275
|
-
|
|
1276
|
-
// Build executed steps list
|
|
1277
|
-
const executedSteps: StepRef[] = batchSteps
|
|
1278
|
-
.filter(step => step.id)
|
|
1279
|
-
.map(step => ({
|
|
1280
|
-
id: step.id!,
|
|
1281
|
-
routeId: selectedRoute.id,
|
|
1282
|
-
}));
|
|
1283
|
-
|
|
1284
|
-
// Stream chunks
|
|
1285
|
-
for await (const chunk of stream) {
|
|
1286
|
-
// On final chunk, collect data and execute finalize hooks
|
|
1287
|
-
if (chunk.done) {
|
|
1288
|
-
// Collect data from response
|
|
1289
|
-
if (chunk.structured) {
|
|
1290
|
-
const collectResult = this.batchExecutor.collectBatchData({
|
|
1291
|
-
steps: batchSteps,
|
|
1292
|
-
llmResponse: chunk.structured,
|
|
1293
|
-
session,
|
|
1294
|
-
schema: this.agent.getSchema(),
|
|
1295
|
-
});
|
|
1296
|
-
|
|
1297
|
-
session = collectResult.session;
|
|
1298
|
-
|
|
1299
|
-
if (collectResult.collectedData && Object.keys(collectResult.collectedData).length > 0) {
|
|
1300
|
-
await this.agent.updateCollectedData(collectResult.collectedData);
|
|
1301
|
-
}
|
|
1302
|
-
}
|
|
1303
|
-
|
|
1304
|
-
// Update session to final step position
|
|
1305
|
-
const lastStep = batchSteps[batchSteps.length - 1];
|
|
1306
|
-
if (lastStep?.id) {
|
|
1307
|
-
session = enterStep(session, lastStep.id, lastStep.description);
|
|
1308
|
-
}
|
|
1309
|
-
|
|
1310
|
-
// Execute finalize hooks
|
|
1311
|
-
await this.batchExecutor.executeFinalizeHooks({
|
|
1312
|
-
steps: batchSteps,
|
|
1313
|
-
context,
|
|
1314
|
-
data: session.data,
|
|
1315
|
-
executeHook,
|
|
1316
|
-
});
|
|
1317
|
-
|
|
1318
|
-
// Finalize session
|
|
1319
|
-
await this.finalizeSession(session, context);
|
|
1320
|
-
}
|
|
1321
|
-
|
|
1322
|
-
yield {
|
|
1323
|
-
delta: chunk.delta,
|
|
1324
|
-
accumulated: chunk.accumulated,
|
|
1325
|
-
done: chunk.done,
|
|
1326
|
-
session,
|
|
1327
|
-
toolCalls: chunk.structured?.toolCalls,
|
|
1328
|
-
isRouteComplete: false,
|
|
1329
|
-
executedSteps: chunk.done ? executedSteps : undefined,
|
|
1330
|
-
stoppedReason: chunk.done ? batchStoppedReason : undefined,
|
|
1331
|
-
metadata: chunk.metadata,
|
|
1332
|
-
structured: chunk.structured,
|
|
1333
|
-
};
|
|
1334
|
-
}
|
|
1335
|
-
}
|
|
1336
|
-
|
|
1337
|
-
/**
|
|
1338
|
-
* Execute prepare function for current step if available
|
|
1339
|
-
* @private
|
|
1340
|
-
*/
|
|
1341
|
-
private async executeStepPrepare(session: SessionState<TData>, context: TContext): Promise<void> {
|
|
1342
|
-
if (session.currentRoute && session.currentStep) {
|
|
1343
|
-
const currentRoute = this.agent.getRoutes().find(
|
|
1344
|
-
(r) => r.id === session.currentRoute?.id
|
|
1345
|
-
);
|
|
1346
|
-
if (currentRoute) {
|
|
1347
|
-
const currentStep = currentRoute.getStep(session.currentStep.id);
|
|
1348
|
-
if (currentStep?.prepare) {
|
|
1349
|
-
logger.debug(`[ResponseModal] Executing prepare for step: ${currentStep.id}`);
|
|
1350
|
-
await this.executePrepareFinalize(
|
|
1351
|
-
currentStep.prepare,
|
|
1352
|
-
context,
|
|
1353
|
-
session.data,
|
|
1354
|
-
currentRoute,
|
|
1355
|
-
currentStep
|
|
1356
|
-
);
|
|
1357
|
-
}
|
|
1358
|
-
}
|
|
1359
|
-
}
|
|
1360
|
-
}
|
|
1361
|
-
|
|
1362
|
-
/**
|
|
1363
|
-
* Execute finalize function for current step if available
|
|
1364
|
-
* @private
|
|
1365
|
-
*/
|
|
1366
|
-
private async executeStepFinalize(session: SessionState<TData>, context: TContext): Promise<void> {
|
|
1367
|
-
if (session.currentRoute && session.currentStep) {
|
|
1368
|
-
const currentRoute = this.agent.getRoutes().find(
|
|
1369
|
-
(r) => r.id === session.currentRoute?.id
|
|
1370
|
-
);
|
|
1371
|
-
if (currentRoute) {
|
|
1372
|
-
const currentStep = currentRoute.getStep(session.currentStep.id);
|
|
1373
|
-
if (currentStep?.finalize) {
|
|
1374
|
-
logger.debug(
|
|
1375
|
-
`[ResponseModal] Executing finalize for step: ${currentStep.id}`
|
|
1376
|
-
);
|
|
1377
|
-
await this.executePrepareFinalize(
|
|
1378
|
-
currentStep.finalize,
|
|
1379
|
-
context,
|
|
1380
|
-
session.data,
|
|
1381
|
-
currentRoute,
|
|
1382
|
-
currentStep
|
|
1383
|
-
);
|
|
1384
|
-
}
|
|
1385
|
-
}
|
|
1386
|
-
}
|
|
1387
|
-
}
|
|
1388
|
-
|
|
1389
|
-
/**
|
|
1390
|
-
* Process route response with unified tool execution and data collection
|
|
1391
|
-
* @private
|
|
1392
|
-
*/
|
|
1393
|
-
private async processRouteResponse(params: {
|
|
1394
|
-
selectedRoute: Route<TContext, TData>;
|
|
1395
|
-
selectedStep?: Step<TContext, TData>;
|
|
1396
|
-
responseDirectives?: string[];
|
|
1397
|
-
session: SessionState<TData>;
|
|
1398
|
-
history: HistoryItem[];
|
|
1399
|
-
context: TContext;
|
|
1400
|
-
historyEvents: Event[];
|
|
1401
|
-
signal?: AbortSignal;
|
|
1402
|
-
}): Promise<{
|
|
1403
|
-
message: string;
|
|
1404
|
-
toolCalls?: Array<{ toolName: string; arguments: Record<string, unknown> }>;
|
|
1405
|
-
session: SessionState<TData>;
|
|
1406
|
-
}> {
|
|
1407
|
-
const { selectedRoute, selectedStep, responseDirectives, history, context, historyEvents, signal } = params;
|
|
1408
|
-
let session = params.session;
|
|
1409
|
-
|
|
1410
|
-
// Determine next step
|
|
1411
|
-
let nextStep: Step<TContext, TData>;
|
|
1412
|
-
if (selectedStep) {
|
|
1413
|
-
nextStep = selectedStep;
|
|
1414
|
-
} else {
|
|
1415
|
-
// Determine current step from session if we're already in this route
|
|
1416
|
-
const isInSameRoute = session.currentRoute?.id === selectedRoute.id;
|
|
1417
|
-
const currentStep = isInSameRoute && session.currentStep
|
|
1418
|
-
? selectedRoute.getStep(session.currentStep.id)
|
|
1419
|
-
: undefined;
|
|
1420
|
-
|
|
1421
|
-
logger.debug(`[ResponseModal] Step determination: route match=${isInSameRoute}, currentRoute=${session.currentRoute?.id}, selectedRoute=${selectedRoute.id}, currentStep=${currentStep?.id || 'none'}`);
|
|
1422
|
-
|
|
1423
|
-
// Get candidate steps based on current position in the route
|
|
1424
|
-
const routingEngine = this.agent.getRoutingEngine();
|
|
1425
|
-
const candidates = await routingEngine.getCandidateStepsWithConditions(
|
|
1426
|
-
selectedRoute,
|
|
1427
|
-
currentStep, // Pass current step instead of undefined to maintain progression
|
|
1428
|
-
createTemplateContext({ data: session.data, session, context })
|
|
1429
|
-
);
|
|
1430
|
-
|
|
1431
|
-
logger.debug(`[ResponseModal] Found ${candidates.length} candidate steps${currentStep ? ' from current step ' + currentStep.id : ' (new route entry)'}`);
|
|
1432
|
-
|
|
1433
|
-
if (candidates.length > 0) {
|
|
1434
|
-
nextStep = candidates[0].step;
|
|
1435
|
-
logger.debug(`[ResponseModal] Using first valid step: ${nextStep.id}${currentStep ? ' (progressing from ' + currentStep.id + ')' : ' for new route'}`);
|
|
1436
|
-
} else {
|
|
1437
|
-
// Fallback to initial step even if it should be skipped
|
|
1438
|
-
nextStep = selectedRoute.initialStep;
|
|
1439
|
-
logger.warn(`[ResponseModal] No valid steps found, using initial step: ${nextStep.id}`);
|
|
1440
|
-
}
|
|
1441
|
-
}
|
|
1442
|
-
|
|
1443
|
-
// Update session with next step
|
|
1444
|
-
// If the next step has requires fields that are missing, stay at the previous step
|
|
1445
|
-
if (nextStep.requires && nextStep.requires.length > 0) {
|
|
1446
|
-
const sessionData = session.data || {};
|
|
1447
|
-
const missingRequires = nextStep.requires.filter(
|
|
1448
|
-
field => (sessionData as Record<string, unknown>)[String(field)] === undefined
|
|
1630
|
+
// ── SIGNAL HALT (Requirement 8.2) ─────────────────────────────────────
|
|
1631
|
+
if (signalHalted) {
|
|
1632
|
+
const haltMessage = signalHaltReply || '';
|
|
1633
|
+
// Run post-signal phase even on halt
|
|
1634
|
+
const postResult = await this.responsePipeline.runPostSignalPhase(
|
|
1635
|
+
session, effectiveContext, historyEvents,
|
|
1449
1636
|
);
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
// Stay at the current step - don't enter the next one
|
|
1456
|
-
const currentStepId = session.currentStep?.id;
|
|
1457
|
-
if (currentStepId) {
|
|
1458
|
-
const currentStepInstance = selectedRoute.getStep(currentStepId);
|
|
1459
|
-
if (currentStepInstance) {
|
|
1460
|
-
nextStep = currentStepInstance;
|
|
1461
|
-
logger.debug(`[ResponseModal] Staying at current step: ${nextStep.id} due to missing requires`);
|
|
1462
|
-
}
|
|
1463
|
-
}
|
|
1464
|
-
} else {
|
|
1465
|
-
session = enterStep(session, nextStep.id, nextStep.description);
|
|
1466
|
-
logger.debug(`[ResponseModal] Entered step: ${nextStep.id}`);
|
|
1637
|
+
session = postResult.updatedSession;
|
|
1638
|
+
signalFirings.push(...postResult.firings);
|
|
1639
|
+
|
|
1640
|
+
if (postResult.mergedDirective && hasDirectivePositionField(postResult.mergedDirective)) {
|
|
1641
|
+
session = { ...session, pendingDirective: postResult.mergedDirective };
|
|
1467
1642
|
}
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1643
|
+
|
|
1644
|
+
await this.finalizeSession(session, effectiveContext);
|
|
1645
|
+
yield {
|
|
1646
|
+
delta: haltMessage,
|
|
1647
|
+
accumulated: haltMessage,
|
|
1648
|
+
done: true,
|
|
1649
|
+
session,
|
|
1650
|
+
stoppedReason: haltMessage ? 'reply' : 'halt',
|
|
1651
|
+
executedSteps: [],
|
|
1652
|
+
triggeredSignals: signalFirings.length > 0 ? signalFirings as unknown as SignalFiring<unknown, TData>[] : undefined,
|
|
1653
|
+
} as AgentResponseStreamChunk<TData>;
|
|
1654
|
+
return;
|
|
1471
1655
|
}
|
|
1472
1656
|
|
|
1473
|
-
//
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
// Build response prompt
|
|
1477
|
-
const responsePrompt = await this.responseEngine.buildResponsePrompt({
|
|
1478
|
-
route: selectedRoute,
|
|
1479
|
-
currentStep: nextStep,
|
|
1480
|
-
rules: selectedRoute.getRules(),
|
|
1481
|
-
prohibitions: selectedRoute.getProhibitions(),
|
|
1482
|
-
directives: responseDirectives,
|
|
1483
|
-
history: historyEvents,
|
|
1484
|
-
agentOptions: this.agent.getAgentOptions(),
|
|
1485
|
-
combinedGuidelines: [...this.agent.getGuidelines(), ...selectedRoute.getGuidelines()],
|
|
1486
|
-
combinedTerms: this.mergeTerms(this.agent.getTerms(), selectedRoute.getTerms()),
|
|
1487
|
-
context,
|
|
1488
|
-
session,
|
|
1489
|
-
agentSchema: this.agent.getSchema(),
|
|
1490
|
-
});
|
|
1657
|
+
// ── Determine the inner stream generator based on flow state ────────
|
|
1658
|
+
let innerStream: AsyncGenerator<AgentResponseStreamChunk<TData>>;
|
|
1491
1659
|
|
|
1492
|
-
|
|
1493
|
-
|
|
1660
|
+
if (selectedFlow && !isFlowComplete) {
|
|
1661
|
+
// AUTO-CHAIN: Walk consecutive auto-steps before any LLM work (streaming path).
|
|
1662
|
+
let resolvedStep = selectedStep;
|
|
1663
|
+
const currentStepInstance = session.currentStep
|
|
1664
|
+
? selectedFlow.getStep(session.currentStep.id)
|
|
1665
|
+
: selectedStep;
|
|
1494
1666
|
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
});
|
|
1667
|
+
if (currentStepInstance?.auto) {
|
|
1668
|
+
const autoChainExecutor = new AutoChainExecutor<TContext, TData>({
|
|
1669
|
+
maxAutoStepsPerTurn: this.agent.maxAutoStepsPerTurn,
|
|
1670
|
+
});
|
|
1671
|
+
const autoResult: AutoChainResult<TContext, TData> = await autoChainExecutor.run({
|
|
1672
|
+
session,
|
|
1673
|
+
context: effectiveContext,
|
|
1674
|
+
flow: selectedFlow,
|
|
1675
|
+
});
|
|
1505
1676
|
|
|
1506
|
-
|
|
1507
|
-
let toolCalls = result.structured?.toolCalls;
|
|
1677
|
+
session = autoResult.session;
|
|
1508
1678
|
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1679
|
+
// Handle halt: emit verbatim reply as a single chunk, done.
|
|
1680
|
+
if (autoResult.stoppedReason === 'halt') {
|
|
1681
|
+
const reply = autoResult.mergedDirective?.reply || '';
|
|
1682
|
+
await this.finalizeSession(session, effectiveContext);
|
|
1683
|
+
yield {
|
|
1684
|
+
delta: reply,
|
|
1685
|
+
accumulated: reply,
|
|
1686
|
+
done: true,
|
|
1687
|
+
session,
|
|
1688
|
+
stoppedReason: 'halt',
|
|
1689
|
+
executedSteps: [],
|
|
1690
|
+
triggeredSignals: signalFirings.length > 0 ? signalFirings as unknown as SignalFiring<unknown, TData>[] : undefined,
|
|
1691
|
+
} as AgentResponseStreamChunk<TData>;
|
|
1692
|
+
return;
|
|
1693
|
+
}
|
|
1517
1694
|
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1695
|
+
// Handle flow completion or cross-flow redirect from auto-chain.
|
|
1696
|
+
if (autoResult.stoppedReason === 'last_step' || autoResult.stoppedReason === 'completed' || autoResult.stoppedReason === 'goto') {
|
|
1697
|
+
innerStream = this.streamFlowCompletion({
|
|
1698
|
+
selectedFlow,
|
|
1699
|
+
session,
|
|
1700
|
+
context: effectiveContext,
|
|
1701
|
+
history,
|
|
1702
|
+
historyEvents,
|
|
1703
|
+
stoppedReason: autoResult.stoppedReason,
|
|
1704
|
+
});
|
|
1705
|
+
} else {
|
|
1706
|
+
// Normal case: resolved to an interactive step.
|
|
1707
|
+
resolvedStep = autoResult.resolvedStep;
|
|
1708
|
+
innerStream = this.processFlowStreamingResponse({
|
|
1709
|
+
selectedFlow,
|
|
1710
|
+
selectedStep: resolvedStep,
|
|
1711
|
+
responseDirectives,
|
|
1712
|
+
session,
|
|
1713
|
+
history,
|
|
1714
|
+
context: effectiveContext,
|
|
1715
|
+
historyEvents,
|
|
1716
|
+
signal,
|
|
1717
|
+
transientAppendage: signalPreDirective?.appendPrompt,
|
|
1718
|
+
mergedPreDirective: signalPreDirective,
|
|
1719
|
+
});
|
|
1720
|
+
}
|
|
1721
|
+
} else {
|
|
1722
|
+
// No auto-step: directly stream the interactive step.
|
|
1723
|
+
innerStream = this.processFlowStreamingResponse({
|
|
1724
|
+
selectedFlow,
|
|
1725
|
+
selectedStep: resolvedStep,
|
|
1726
|
+
responseDirectives,
|
|
1727
|
+
session,
|
|
1728
|
+
history,
|
|
1729
|
+
context: effectiveContext,
|
|
1730
|
+
historyEvents,
|
|
1731
|
+
signal,
|
|
1732
|
+
// Propagate signal pre-directive's appendPrompt for this turn's LLM call
|
|
1733
|
+
transientAppendage: signalPreDirective?.appendPrompt,
|
|
1734
|
+
mergedPreDirective: signalPreDirective,
|
|
1735
|
+
});
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
} else if (isFlowComplete && selectedFlow) {
|
|
1739
|
+
// Handle flow completion streaming — implicit terminus (no successor
|
|
1740
|
+
// or all successors skipped), so the reason is 'last_step'.
|
|
1741
|
+
innerStream = this.streamFlowCompletion({
|
|
1742
|
+
selectedFlow,
|
|
1743
|
+
session,
|
|
1744
|
+
context: effectiveContext,
|
|
1745
|
+
history,
|
|
1746
|
+
historyEvents,
|
|
1747
|
+
stoppedReason: 'last_step',
|
|
1748
|
+
});
|
|
1530
1749
|
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1750
|
+
} else {
|
|
1751
|
+
// Fallback: No flows defined, stream a simple response
|
|
1752
|
+
innerStream = this.streamFallbackResponse({
|
|
1753
|
+
history,
|
|
1754
|
+
context: effectiveContext,
|
|
1755
|
+
session,
|
|
1756
|
+
});
|
|
1535
1757
|
}
|
|
1536
1758
|
|
|
1537
|
-
//
|
|
1538
|
-
//
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1759
|
+
// ── Intercept the inner stream to run post-signal phase on the final chunk ──
|
|
1760
|
+
// This mirrors the non-streaming path: post-phase runs after finalize/onComplete
|
|
1761
|
+
// and before session persistence, attaching triggeredSignals to the final chunk
|
|
1762
|
+
// (Requirement 11.2).
|
|
1763
|
+
for await (const chunk of innerStream!) {
|
|
1764
|
+
if (chunk.done) {
|
|
1765
|
+
// Run post-signal phase on final chunk (Requirement 9.1, 9.2)
|
|
1766
|
+
const postResult = await this.responsePipeline.runPostSignalPhase(
|
|
1767
|
+
chunk.session || session, effectiveContext, historyEvents,
|
|
1768
|
+
);
|
|
1769
|
+
let finalSession = postResult.updatedSession;
|
|
1770
|
+
signalFirings.push(...postResult.firings);
|
|
1771
|
+
|
|
1772
|
+
// Requirement 9.3: Post-phase position directive sets session.pendingDirective
|
|
1773
|
+
if (postResult.mergedDirective && hasDirectivePositionField(postResult.mergedDirective)) {
|
|
1774
|
+
finalSession = { ...finalSession, pendingDirective: postResult.mergedDirective };
|
|
1775
|
+
}
|
|
1543
1776
|
|
|
1544
|
-
|
|
1777
|
+
yield {
|
|
1778
|
+
...chunk,
|
|
1779
|
+
session: finalSession,
|
|
1780
|
+
triggeredSignals: signalFirings.length > 0 ? signalFirings as unknown as SignalFiring<unknown, TData>[] : undefined,
|
|
1781
|
+
} as AgentResponseStreamChunk<TData>;
|
|
1782
|
+
} else {
|
|
1783
|
+
yield chunk;
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1545
1786
|
}
|
|
1546
1787
|
|
|
1547
1788
|
/**
|
|
1548
|
-
* Process
|
|
1789
|
+
* Process flow streaming response with unified tool execution and data collection
|
|
1549
1790
|
* @private
|
|
1550
1791
|
*/
|
|
1551
|
-
private async *
|
|
1552
|
-
|
|
1792
|
+
private async *processFlowStreamingResponse(params: {
|
|
1793
|
+
selectedFlow: Flow<TContext, TData>;
|
|
1553
1794
|
selectedStep?: Step<TContext, TData>;
|
|
1554
1795
|
responseDirectives?: string[];
|
|
1555
1796
|
session: SessionState<TData>;
|
|
@@ -1557,8 +1798,18 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
1557
1798
|
context: TContext;
|
|
1558
1799
|
historyEvents: Event[];
|
|
1559
1800
|
signal?: AbortSignal;
|
|
1801
|
+
/**
|
|
1802
|
+
* Per-turn transient appendage from merged PreDirective.appendPrompt.
|
|
1803
|
+
* Fresh every turn, never cached, never persisted.
|
|
1804
|
+
*/
|
|
1805
|
+
transientAppendage?: string[];
|
|
1806
|
+
/**
|
|
1807
|
+
* Merged PreDirective from the directive bus's pre-LLM phase drain.
|
|
1808
|
+
* When `halt: true`, the LLM call is skipped entirely.
|
|
1809
|
+
*/
|
|
1810
|
+
mergedPreDirective?: PreDirective<TContext, TData>;
|
|
1560
1811
|
}): AsyncGenerator<AgentResponseStreamChunk<TData>> {
|
|
1561
|
-
const {
|
|
1812
|
+
const { selectedFlow, selectedStep, responseDirectives, history, context, historyEvents, signal, transientAppendage, mergedPreDirective } = params;
|
|
1562
1813
|
let session = params.session;
|
|
1563
1814
|
|
|
1564
1815
|
// Determine next step (same logic as non-streaming)
|
|
@@ -1566,25 +1817,50 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
1566
1817
|
if (selectedStep) {
|
|
1567
1818
|
nextStep = selectedStep;
|
|
1568
1819
|
} else {
|
|
1569
|
-
// Determine current step from session if we're already in this
|
|
1570
|
-
const currentStep = session.
|
|
1571
|
-
?
|
|
1820
|
+
// Determine current step from session if we're already in this flow
|
|
1821
|
+
const currentStep = session.currentFlow?.id === selectedFlow.id && session.currentStep
|
|
1822
|
+
? selectedFlow.getStep(session.currentStep.id)
|
|
1572
1823
|
: undefined;
|
|
1573
1824
|
|
|
1574
|
-
//
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1825
|
+
// STEP 1 (Algorithm 1): branches win over linear chain
|
|
1826
|
+
if (currentStep?.branches && currentStep.branches.length > 0) {
|
|
1827
|
+
const branchResult = await this.responsePipeline.evaluateStepBranches(
|
|
1828
|
+
currentStep, selectedFlow, session, context
|
|
1829
|
+
);
|
|
1830
|
+
if (branchResult) {
|
|
1831
|
+
// Branch resolved — yield a final chunk with the updated session and return
|
|
1832
|
+
if (branchResult.nextStep) {
|
|
1833
|
+
session = branchResult.session;
|
|
1834
|
+
nextStep = branchResult.nextStep;
|
|
1835
|
+
} else {
|
|
1836
|
+
// Flow transition or completion — no step to render
|
|
1837
|
+
yield {
|
|
1838
|
+
delta: '',
|
|
1839
|
+
accumulated: '',
|
|
1840
|
+
done: true,
|
|
1841
|
+
session: branchResult.session,
|
|
1842
|
+
} as AgentResponseStreamChunk<TData>;
|
|
1843
|
+
return;
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1581
1847
|
|
|
1582
|
-
if (
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1848
|
+
if (!nextStep!) {
|
|
1849
|
+
// Get candidate steps based on current position in the flow
|
|
1850
|
+
const flowRouter = this.agent.getFlowRouter();
|
|
1851
|
+
const candidates = await flowRouter.getCandidateStepsWithConditions(
|
|
1852
|
+
selectedFlow,
|
|
1853
|
+
currentStep, // Pass current step instead of undefined to maintain progression
|
|
1854
|
+
createTemplateContext({ data: session.data, session, context })
|
|
1855
|
+
);
|
|
1856
|
+
|
|
1857
|
+
if (candidates.length > 0) {
|
|
1858
|
+
nextStep = candidates[0].step;
|
|
1859
|
+
logger.debug(`[ResponseModal] Using first valid step: ${nextStep.id}${currentStep ? ' (progressing from ' + currentStep.id + ')' : ' for new flow'}`);
|
|
1860
|
+
} else {
|
|
1861
|
+
nextStep = selectedFlow.initialStep;
|
|
1862
|
+
logger.warn(`[FlowConfigurationError] No valid steps found: all candidates were skipped in flow. Falling back to initial step "${nextStep.id}". Review step skip conditions.`);
|
|
1863
|
+
}
|
|
1588
1864
|
}
|
|
1589
1865
|
}
|
|
1590
1866
|
|
|
@@ -1596,13 +1872,13 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
1596
1872
|
field => (sessionData as Record<string, unknown>)[String(field)] === undefined
|
|
1597
1873
|
);
|
|
1598
1874
|
if (missingRequires.length > 0) {
|
|
1599
|
-
const warning = `[
|
|
1600
|
-
`missing required fields [${missingRequires.join(', ')}]. Staying at current step.`;
|
|
1875
|
+
const warning = `[FlowConfigurationError] Cannot advance to step "${nextStep.description || nextStep.id}": ` +
|
|
1876
|
+
`missing required fields [${missingRequires.join(', ')}]. Staying at current step. Ensure preceding steps collect these fields.`;
|
|
1601
1877
|
logger.warn(warning);
|
|
1602
1878
|
console.warn(warning);
|
|
1603
1879
|
const currentStepId = session.currentStep?.id;
|
|
1604
1880
|
if (currentStepId) {
|
|
1605
|
-
const currentStepInstance =
|
|
1881
|
+
const currentStepInstance = selectedFlow.getStep(currentStepId);
|
|
1606
1882
|
if (currentStepInstance) {
|
|
1607
1883
|
nextStep = currentStepInstance;
|
|
1608
1884
|
logger.debug(`[ResponseModal] Staying at current step: ${nextStep.id} due to missing requires`);
|
|
@@ -1618,152 +1894,207 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
1618
1894
|
}
|
|
1619
1895
|
|
|
1620
1896
|
// Build response schema and prompt (same as non-streaming)
|
|
1621
|
-
const responseSchema = this.responseEngine.
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1897
|
+
const responseSchema = this.responseEngine.responseSchemaForFlow(selectedFlow, nextStep, this.agent.schema);
|
|
1898
|
+
|
|
1899
|
+
// ── HALT SHORT-CIRCUIT (Requirement 2.5, 2.6, 2.7) ──────────────────────
|
|
1900
|
+
// After pre-LLM emissions are merged, if `halt: true` then skip the LLM
|
|
1901
|
+
// call entirely. Emit a single done chunk with the appropriate content.
|
|
1902
|
+
if (mergedPreDirective?.halt) {
|
|
1903
|
+
const reply = mergedPreDirective.reply || '';
|
|
1904
|
+
const reason: StoppedReason = mergedPreDirective.reply ? 'reply' : 'halt';
|
|
1905
|
+
logger.debug(`[ResponseModal] Halt (streaming) — skipping LLM call for step ${nextStep.id}, stoppedReason: ${reason}`);
|
|
1906
|
+
await this.finalizeSession(session, context);
|
|
1907
|
+
yield {
|
|
1908
|
+
delta: reply,
|
|
1909
|
+
accumulated: reply,
|
|
1910
|
+
done: true,
|
|
1911
|
+
session,
|
|
1912
|
+
stoppedReason: reason,
|
|
1913
|
+
executedSteps: [{ id: nextStep.id, flowId: selectedFlow.id }],
|
|
1914
|
+
} as AgentResponseStreamChunk<TData>;
|
|
1915
|
+
return;
|
|
1916
|
+
}
|
|
1636
1917
|
|
|
1637
|
-
//
|
|
1638
|
-
|
|
1918
|
+
// ── STEP.REPLY SHORT-CIRCUIT (Requirement 25.1–25.7, 17.9) ──────────────
|
|
1919
|
+
// A step with `reply` set emits a verbatim template response without LLM.
|
|
1920
|
+
// onEnter and prepare have already fired normally. If prepare returned
|
|
1921
|
+
// a PreDirective with `reply`, that overrides the step-declared reply.
|
|
1922
|
+
if (nextStep.reply != null) {
|
|
1923
|
+
const effectiveReply = mergedPreDirective?.reply ?? await render(
|
|
1924
|
+
nextStep.reply,
|
|
1925
|
+
createTemplateContext({ data: session.data || {}, context, session })
|
|
1926
|
+
);
|
|
1927
|
+
logger.debug(`[ResponseModal] Step.reply (streaming) — skipping LLM call for step ${nextStep.id}`);
|
|
1928
|
+
await this.finalizeSession(session, context);
|
|
1929
|
+
yield {
|
|
1930
|
+
delta: effectiveReply,
|
|
1931
|
+
accumulated: effectiveReply,
|
|
1932
|
+
done: true,
|
|
1933
|
+
session,
|
|
1934
|
+
stoppedReason: 'reply' as StoppedReason,
|
|
1935
|
+
executedSteps: [{ id: nextStep.id, flowId: selectedFlow.id }],
|
|
1936
|
+
} as AgentResponseStreamChunk<TData>;
|
|
1937
|
+
return;
|
|
1938
|
+
}
|
|
1639
1939
|
|
|
1640
|
-
//
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1940
|
+
// Transient appendage: per-turn slot from PreDirective.appendPrompt.
|
|
1941
|
+
// Fresh each turn, never cached, never persisted.
|
|
1942
|
+
// Wrapped in try/finally to ensure cleanup even on abnormal termination.
|
|
1943
|
+
let turnTransientAppendage: string[] | undefined = transientAppendage;
|
|
1944
|
+
try {
|
|
1945
|
+
const { prompt: responsePrompt, appliedInstructions } = await this.responseEngine.buildResponsePrompt({
|
|
1946
|
+
flow: selectedFlow,
|
|
1947
|
+
currentStep: nextStep,
|
|
1948
|
+
rules: [],
|
|
1949
|
+
prohibitions: [],
|
|
1950
|
+
directives: responseDirectives,
|
|
1951
|
+
history: historyEvents,
|
|
1952
|
+
agentOptions: this.agent.getAgentOptions(),
|
|
1953
|
+
instructions: this.collectScopedInstructions(selectedFlow, nextStep),
|
|
1954
|
+
combinedTerms: this.agent.getTerms(),
|
|
1955
|
+
context,
|
|
1956
|
+
session,
|
|
1957
|
+
agentSchema: this.agent.schema,
|
|
1958
|
+
transientAppendage: turnTransientAppendage,
|
|
1959
|
+
});
|
|
1650
1960
|
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
let toolCalls: Array<{ toolName: string; arguments: Record<string, unknown> }> | undefined = undefined;
|
|
1961
|
+
// Collect available tools for AI
|
|
1962
|
+
const availableTools = this.collectAvailableTools(selectedFlow, nextStep);
|
|
1654
1963
|
|
|
1655
|
-
//
|
|
1656
|
-
|
|
1657
|
-
|
|
1964
|
+
// Generate message stream using AI provider
|
|
1965
|
+
const agentOptions = this.agent.getAgentOptions();
|
|
1966
|
+
const stream = agentOptions.provider.generateMessageStream({
|
|
1967
|
+
prompt: responsePrompt,
|
|
1968
|
+
history, // Use HistoryItem[] for AI provider
|
|
1969
|
+
context,
|
|
1970
|
+
tools: availableTools,
|
|
1971
|
+
signal,
|
|
1972
|
+
parameters: { jsonSchema: responseSchema, schemaName: "response_stream_output" },
|
|
1973
|
+
});
|
|
1658
1974
|
|
|
1659
|
-
|
|
1975
|
+
// Stream chunks with unified tool handling
|
|
1976
|
+
for await (const chunk of stream) {
|
|
1977
|
+
let toolCalls: Array<{ toolName: string; arguments: Record<string, unknown> }> | undefined = undefined;
|
|
1660
1978
|
|
|
1661
|
-
//
|
|
1662
|
-
if (
|
|
1663
|
-
|
|
1664
|
-
id: `${tc.toolName}-${i}-${Date.now()}`,
|
|
1665
|
-
toolName: tc.toolName,
|
|
1666
|
-
arguments: tc.arguments,
|
|
1667
|
-
}));
|
|
1979
|
+
// Extract tool calls from AI response on final chunk
|
|
1980
|
+
if (chunk.done && chunk.structured?.toolCalls) {
|
|
1981
|
+
toolCalls = chunk.structured.toolCalls;
|
|
1668
1982
|
|
|
1669
|
-
const
|
|
1983
|
+
const toolManager = this.getToolManager();
|
|
1670
1984
|
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1985
|
+
// Use concurrent execution for the initial batch of tool calls
|
|
1986
|
+
if (toolManager && typeof toolManager.executeWithConcurrency === 'function') {
|
|
1987
|
+
const toolCallRequests: ToolCallRequest[] = toolCalls.map((tc, i) => ({
|
|
1988
|
+
id: `${tc.toolName}-${i}-${Date.now()}`,
|
|
1989
|
+
toolName: tc.toolName,
|
|
1990
|
+
arguments: tc.arguments,
|
|
1991
|
+
}));
|
|
1992
|
+
|
|
1993
|
+
const historyEvents = historyToEvents(history);
|
|
1994
|
+
|
|
1995
|
+
try {
|
|
1996
|
+
for await (const update of toolManager.executeWithConcurrency({
|
|
1997
|
+
toolCalls: toolCallRequests,
|
|
1998
|
+
context,
|
|
1999
|
+
data: session.data,
|
|
2000
|
+
history: historyEvents,
|
|
2001
|
+
signal,
|
|
2002
|
+
flow: selectedFlow,
|
|
2003
|
+
step: nextStep,
|
|
2004
|
+
})) {
|
|
2005
|
+
// Apply context updates
|
|
2006
|
+
if (update.contextUpdate) {
|
|
2007
|
+
try {
|
|
2008
|
+
await this.agent.updateContext(update.contextUpdate as Partial<TContext>);
|
|
2009
|
+
} catch (error) {
|
|
2010
|
+
logger.error(`[ResponseModal] Failed to update context from concurrent tool:`, error);
|
|
2011
|
+
}
|
|
1687
2012
|
}
|
|
1688
|
-
}
|
|
1689
2013
|
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
2014
|
+
// Apply data updates
|
|
2015
|
+
if (update.dataUpdate) {
|
|
2016
|
+
try {
|
|
2017
|
+
const updateDataMethod = this.agent.getUpdateDataMethod();
|
|
2018
|
+
session = await updateDataMethod(session, update.dataUpdate);
|
|
2019
|
+
} catch (error) {
|
|
2020
|
+
logger.error(`[ResponseModal] Failed to update data from concurrent tool:`, error);
|
|
2021
|
+
}
|
|
1697
2022
|
}
|
|
1698
|
-
}
|
|
1699
2023
|
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
2024
|
+
// Yield progress updates immediately
|
|
2025
|
+
if (update.progress) {
|
|
2026
|
+
yield {
|
|
2027
|
+
delta: '',
|
|
2028
|
+
accumulated: chunk.accumulated,
|
|
2029
|
+
done: false,
|
|
2030
|
+
session,
|
|
2031
|
+
toolCalls: undefined,
|
|
2032
|
+
isFlowComplete: false,
|
|
2033
|
+
metadata: { toolProgress: update.progress, toolCallId: update.toolCallId },
|
|
2034
|
+
};
|
|
2035
|
+
}
|
|
1711
2036
|
}
|
|
1712
|
-
}
|
|
1713
2037
|
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
2038
|
+
logger.debug(`[ResponseModal] Concurrent tool execution completed for ${toolCallRequests.length} tools`);
|
|
2039
|
+
} catch (error) {
|
|
2040
|
+
logger.error(`[ResponseModal] Concurrent tool execution failed, falling back to sequential:`, error);
|
|
2041
|
+
// Fall back to the unified tool loop on failure
|
|
2042
|
+
const toolResult = await this.executeUnifiedToolLoop({
|
|
2043
|
+
toolCalls, context, session, history, selectedFlow,
|
|
2044
|
+
responsePrompt, availableTools, responseSchema, signal,
|
|
2045
|
+
});
|
|
2046
|
+
session = toolResult.session;
|
|
2047
|
+
toolCalls = toolResult.finalToolCalls;
|
|
2048
|
+
}
|
|
2049
|
+
} else {
|
|
2050
|
+
// Fallback: no ToolManager or no executeWithConcurrency, use unified tool loop
|
|
1718
2051
|
const toolResult = await this.executeUnifiedToolLoop({
|
|
1719
|
-
toolCalls, context, session, history,
|
|
2052
|
+
toolCalls, context, session, history, selectedFlow,
|
|
1720
2053
|
responsePrompt, availableTools, responseSchema, signal,
|
|
1721
2054
|
});
|
|
1722
2055
|
session = toolResult.session;
|
|
1723
2056
|
toolCalls = toolResult.finalToolCalls;
|
|
1724
2057
|
}
|
|
1725
|
-
}
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
2058
|
+
}
|
|
2059
|
+
|
|
2060
|
+
// Extract collected data on final chunk
|
|
2061
|
+
if (chunk.done && chunk.structured && nextStep.collect) {
|
|
2062
|
+
session = await this.collectDataFromResponse({
|
|
2063
|
+
result: { structured: chunk.structured },
|
|
2064
|
+
selectedFlow,
|
|
2065
|
+
nextStep,
|
|
2066
|
+
session,
|
|
1730
2067
|
});
|
|
1731
|
-
session = toolResult.session;
|
|
1732
|
-
toolCalls = toolResult.finalToolCalls;
|
|
1733
2068
|
}
|
|
1734
|
-
}
|
|
1735
2069
|
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
selectedRoute,
|
|
1741
|
-
nextStep,
|
|
1742
|
-
session,
|
|
1743
|
-
});
|
|
1744
|
-
}
|
|
2070
|
+
// Handle session finalization on final chunk
|
|
2071
|
+
if (chunk.done) {
|
|
2072
|
+
await this.finalizeSession(session, context);
|
|
2073
|
+
}
|
|
1745
2074
|
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
2075
|
+
// Response structure completeness (Requirement 8.1, 8.2, 8.3)
|
|
2076
|
+
// - executedSteps: single step executed in this response
|
|
2077
|
+
// - stoppedReason: 'needs_input' for single-step execution (waiting for user input)
|
|
2078
|
+
// - session.currentStep: reflects the executed step
|
|
2079
|
+
yield {
|
|
2080
|
+
delta: chunk.delta,
|
|
2081
|
+
accumulated: chunk.accumulated,
|
|
2082
|
+
done: chunk.done,
|
|
2083
|
+
session,
|
|
2084
|
+
toolCalls,
|
|
2085
|
+
isFlowComplete: false,
|
|
2086
|
+
executedSteps: chunk.done ? [{ id: nextStep.id, flowId: selectedFlow.id }] : undefined,
|
|
2087
|
+
stoppedReason: chunk.done ? 'needs_input' : undefined,
|
|
2088
|
+
metadata: chunk.metadata,
|
|
2089
|
+
structured: chunk.structured,
|
|
2090
|
+
appliedInstructions: chunk.done ? appliedInstructions : undefined,
|
|
2091
|
+
};
|
|
1749
2092
|
}
|
|
1750
|
-
|
|
1751
|
-
//
|
|
1752
|
-
//
|
|
1753
|
-
//
|
|
1754
|
-
|
|
1755
|
-
yield {
|
|
1756
|
-
delta: chunk.delta,
|
|
1757
|
-
accumulated: chunk.accumulated,
|
|
1758
|
-
done: chunk.done,
|
|
1759
|
-
session,
|
|
1760
|
-
toolCalls,
|
|
1761
|
-
isRouteComplete: false,
|
|
1762
|
-
executedSteps: chunk.done ? [{ id: nextStep.id, routeId: selectedRoute.id }] : undefined,
|
|
1763
|
-
stoppedReason: chunk.done ? 'needs_input' : undefined,
|
|
1764
|
-
metadata: chunk.metadata,
|
|
1765
|
-
structured: chunk.structured,
|
|
1766
|
-
};
|
|
2093
|
+
} finally {
|
|
2094
|
+
// Drain the transient appendage at end of turn.
|
|
2095
|
+
// This ensures PreDirective.appendPrompt does not leak to subsequent
|
|
2096
|
+
// turns even when the turn terminates abnormally (error, abort, reject).
|
|
2097
|
+
turnTransientAppendage = undefined;
|
|
1767
2098
|
}
|
|
1768
2099
|
}
|
|
1769
2100
|
|
|
@@ -1777,7 +2108,7 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
1777
2108
|
context: TContext;
|
|
1778
2109
|
session: SessionState<TData>;
|
|
1779
2110
|
history: HistoryItem[];
|
|
1780
|
-
|
|
2111
|
+
selectedFlow?: Flow<TContext, TData>;
|
|
1781
2112
|
responsePrompt: string;
|
|
1782
2113
|
availableTools: Array<{
|
|
1783
2114
|
id: string;
|
|
@@ -1794,7 +2125,7 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
1794
2125
|
structured?: AgentStructuredResponse;
|
|
1795
2126
|
}> {
|
|
1796
2127
|
try {
|
|
1797
|
-
const { context, history,
|
|
2128
|
+
const { context, history, selectedFlow, responsePrompt, availableTools, responseSchema, signal } = params;
|
|
1798
2129
|
let { toolCalls, session } = params;
|
|
1799
2130
|
|
|
1800
2131
|
// Convert HistoryItem[] to Event[] for internal processing
|
|
@@ -1802,15 +2133,17 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
1802
2133
|
|
|
1803
2134
|
// Map to store tool execution results for history
|
|
1804
2135
|
const toolResultsMap = new Map<string, string>();
|
|
2136
|
+
// Map to store tool call arguments for history reconstruction
|
|
2137
|
+
const toolArgsMap = new Map<string, Record<string, unknown>>();
|
|
1805
2138
|
|
|
1806
2139
|
// Execute initial dynamic tool calls
|
|
1807
2140
|
if (toolCalls && toolCalls.length > 0) {
|
|
1808
2141
|
logger.debug(`[ResponseModal] Executing ${toolCalls.length} dynamic tool calls:`, toolCalls.map(tc => tc.toolName));
|
|
1809
2142
|
|
|
1810
2143
|
for (const toolCall of toolCalls) {
|
|
1811
|
-
const tool = this.findAvailableTool(toolCall.toolName,
|
|
2144
|
+
const tool = this.findAvailableTool(toolCall.toolName, selectedFlow);
|
|
1812
2145
|
if (!tool) {
|
|
1813
|
-
logger.warn(`[
|
|
2146
|
+
logger.warn(`[ToolExecutionError] Tool not found: "${toolCall.toolName}" is not registered in any scope. Skipping this tool call. Register the tool or check the tool name.`);
|
|
1814
2147
|
continue;
|
|
1815
2148
|
}
|
|
1816
2149
|
|
|
@@ -1836,6 +2169,7 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
1836
2169
|
|
|
1837
2170
|
// Store the actual tool result data for history
|
|
1838
2171
|
toolResultsMap.set(toolCall.toolName, serializeToolResult(toolResult));
|
|
2172
|
+
toolArgsMap.set(toolCall.toolName, toolCall.arguments);
|
|
1839
2173
|
|
|
1840
2174
|
// Check if tool execution was successful
|
|
1841
2175
|
if (!toolResult.success) {
|
|
@@ -1889,7 +2223,7 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
1889
2223
|
// Create tool result history items
|
|
1890
2224
|
const toolResultHistoryItems: HistoryItem[] = [];
|
|
1891
2225
|
for (const toolCall of toolCalls || []) {
|
|
1892
|
-
const tool = this.findAvailableTool(toolCall.toolName,
|
|
2226
|
+
const tool = this.findAvailableTool(toolCall.toolName, selectedFlow);
|
|
1893
2227
|
if (tool) {
|
|
1894
2228
|
// Create HistoryItem format for tool results
|
|
1895
2229
|
// Add assistant message with tool_calls
|
|
@@ -1955,9 +2289,9 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
1955
2289
|
|
|
1956
2290
|
// Execute the follow-up tool calls
|
|
1957
2291
|
for (const toolCall of followUpToolCalls!) {
|
|
1958
|
-
const tool = this.findAvailableTool(toolCall.toolName,
|
|
2292
|
+
const tool = this.findAvailableTool(toolCall.toolName, selectedFlow);
|
|
1959
2293
|
if (!tool) {
|
|
1960
|
-
logger.warn(`[
|
|
2294
|
+
logger.warn(`[ToolExecutionError] Tool not found in follow-up: "${toolCall.toolName}" is not registered in any scope. Skipping this tool call. Register the tool or check the tool name.`);
|
|
1961
2295
|
continue;
|
|
1962
2296
|
}
|
|
1963
2297
|
|
|
@@ -2008,6 +2342,7 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
2008
2342
|
|
|
2009
2343
|
// Store the follow-up tool result for potential next loop iteration
|
|
2010
2344
|
toolResultsMap.set(toolCall.toolName, serializeToolResult(toolResult));
|
|
2345
|
+
toolArgsMap.set(toolCall.toolName, toolCall.arguments);
|
|
2011
2346
|
|
|
2012
2347
|
logger.debug(`[ResponseModal] Executed follow-up tool: ${toolCall.toolName} (success: ${toolResult.success})`);
|
|
2013
2348
|
} catch (error) {
|
|
@@ -2029,7 +2364,7 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
2029
2364
|
}
|
|
2030
2365
|
|
|
2031
2366
|
if (toolLoopCount >= MAX_TOOL_LOOPS) {
|
|
2032
|
-
logger.warn(`[
|
|
2367
|
+
logger.warn(`[ResponseGenerationError] Tool loop limit reached: ${toolLoopCount} iterations hit the cap (${MAX_TOOL_LOOPS}). Stopping tool execution. Increase MAX_TOOL_LOOPS or reduce recursive tool calls.`);
|
|
2033
2368
|
}
|
|
2034
2369
|
|
|
2035
2370
|
// If tools were executed but no final text message was produced,
|
|
@@ -2051,7 +2386,7 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
2051
2386
|
tool_calls: [{
|
|
2052
2387
|
id: toolName,
|
|
2053
2388
|
name: toolName,
|
|
2054
|
-
arguments: {},
|
|
2389
|
+
arguments: toolArgsMap.get(toolName) || {},
|
|
2055
2390
|
}],
|
|
2056
2391
|
});
|
|
2057
2392
|
finalToolResultHistoryItems.push({
|
|
@@ -2118,41 +2453,41 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
2118
2453
|
*/
|
|
2119
2454
|
private async collectDataFromResponse(params: {
|
|
2120
2455
|
result: { structured?: AgentStructuredResponse };
|
|
2121
|
-
|
|
2456
|
+
selectedFlow?: Flow<TContext, TData>;
|
|
2122
2457
|
nextStep?: Step<TContext, TData>;
|
|
2123
2458
|
session: SessionState<TData>;
|
|
2124
2459
|
}): Promise<SessionState<TData>> {
|
|
2125
2460
|
try {
|
|
2126
|
-
const { result,
|
|
2461
|
+
const { result, selectedFlow, nextStep, session } = params;
|
|
2127
2462
|
let updatedSession = session;
|
|
2128
2463
|
|
|
2129
|
-
// Extract collected data from final response (only for
|
|
2130
|
-
if (
|
|
2464
|
+
// Extract collected data from final response (only for flow-based interactions)
|
|
2465
|
+
if (selectedFlow && result.structured) {
|
|
2131
2466
|
try {
|
|
2132
2467
|
const collectedData: Record<string, unknown> = {};
|
|
2133
2468
|
// AgentStructuredResponse extends Record<string, unknown>, so we can safely access properties
|
|
2134
2469
|
const structuredData = result.structured;
|
|
2135
2470
|
|
|
2136
|
-
// Collect ALL
|
|
2137
|
-
const
|
|
2471
|
+
// Collect ALL flow fields (required + optional) from structured response
|
|
2472
|
+
const allFlowFields = new Set<string>();
|
|
2138
2473
|
|
|
2139
|
-
// Add
|
|
2140
|
-
if (
|
|
2141
|
-
|
|
2474
|
+
// Add flow required fields
|
|
2475
|
+
if (selectedFlow.requiredFields) {
|
|
2476
|
+
selectedFlow.requiredFields.forEach(field => allFlowFields.add(String(field)));
|
|
2142
2477
|
}
|
|
2143
2478
|
|
|
2144
|
-
// Add
|
|
2145
|
-
if (
|
|
2146
|
-
|
|
2479
|
+
// Add flow optional fields
|
|
2480
|
+
if (selectedFlow.optionalFields) {
|
|
2481
|
+
selectedFlow.optionalFields.forEach(field => allFlowFields.add(String(field)));
|
|
2147
2482
|
}
|
|
2148
2483
|
|
|
2149
|
-
// Also include current step's collect fields (in case they're not in
|
|
2484
|
+
// Also include current step's collect fields (in case they're not in flow fields)
|
|
2150
2485
|
if (nextStep?.collect) {
|
|
2151
|
-
nextStep.collect.forEach(field =>
|
|
2486
|
+
nextStep.collect.forEach(field => allFlowFields.add(String(field)));
|
|
2152
2487
|
}
|
|
2153
2488
|
|
|
2154
2489
|
// Extract all available fields from structured response
|
|
2155
|
-
for (const field of
|
|
2490
|
+
for (const field of allFlowFields) {
|
|
2156
2491
|
const fieldKey = String(field);
|
|
2157
2492
|
if (fieldKey in structuredData && structuredData[fieldKey] !== undefined && structuredData[fieldKey] !== null) {
|
|
2158
2493
|
collectedData[fieldKey] = structuredData[fieldKey];
|
|
@@ -2203,240 +2538,147 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
2203
2538
|
}
|
|
2204
2539
|
|
|
2205
2540
|
/**
|
|
2206
|
-
*
|
|
2541
|
+
* Apply flow completion: release the session to idle state.
|
|
2542
|
+
*
|
|
2543
|
+
* This is a pure state transition. The framework emits **no message of
|
|
2544
|
+
* its own** at the completion boundary — every word delivered to the
|
|
2545
|
+
* user comes from a developer-defined step prompt. If the dev wants a
|
|
2546
|
+
* closing turn, they add a final interactive step with their own
|
|
2547
|
+
* `prompt`; the framework respects that step's natural LLM output.
|
|
2548
|
+
*
|
|
2549
|
+
* Behavior:
|
|
2550
|
+
* - Marks the active `flowHistory` entry as `completed: true` and
|
|
2551
|
+
* stamps `exitedAt`.
|
|
2552
|
+
* - Evaluates `flow.onComplete` for an explicit follow-up transition.
|
|
2553
|
+
* When set, populates `session.pendingDirective` (the next turn's
|
|
2554
|
+
* pipeline applies it). When absent, the session is fully idle.
|
|
2555
|
+
* - Clears `currentFlow` and `currentStep` to `undefined`.
|
|
2556
|
+
* - Clears owned fields when the flow is `reentrant` so subsequent
|
|
2557
|
+
* re-selections start from a clean state.
|
|
2558
|
+
*
|
|
2559
|
+
* Returns the updated session. Callers compose any reply text from
|
|
2560
|
+
* their own sources (an upstream LLM turn, a directive's `reply`, or
|
|
2561
|
+
* an empty string for silent completion).
|
|
2562
|
+
*
|
|
2207
2563
|
* @private
|
|
2208
2564
|
*/
|
|
2209
|
-
private async
|
|
2210
|
-
|
|
2565
|
+
private async applyFlowCompletion(params: {
|
|
2566
|
+
selectedFlow: Flow<TContext, TData>;
|
|
2211
2567
|
session: SessionState<TData>;
|
|
2212
2568
|
context: TContext;
|
|
2213
2569
|
history: HistoryItem[];
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
}): Promise<string> {
|
|
2217
|
-
const { selectedRoute, session, context, history, historyEvents, signal } = params;
|
|
2218
|
-
|
|
2219
|
-
// Get endStep spec from route
|
|
2220
|
-
const endStepSpec = selectedRoute.endStepSpec;
|
|
2221
|
-
|
|
2222
|
-
// Create a temporary step for completion message generation using endStep configuration
|
|
2223
|
-
const completionStep = new Step<TContext, TData>(selectedRoute.id, {
|
|
2224
|
-
description: endStepSpec.description,
|
|
2225
|
-
id: endStepSpec.id || END_ROUTE_ID,
|
|
2226
|
-
collect: endStepSpec.collect,
|
|
2227
|
-
requires: endStepSpec.requires,
|
|
2228
|
-
prompt: endStepSpec.prompt || "Send a brief, natural farewell message thanking the user. Do NOT list or mention any collected data, field names, or internal information.",
|
|
2229
|
-
});
|
|
2230
|
-
|
|
2231
|
-
// Build response schema for completion (message only, no data collection)
|
|
2232
|
-
const completionSchema = {
|
|
2233
|
-
type: "object",
|
|
2234
|
-
properties: {
|
|
2235
|
-
message: {
|
|
2236
|
-
type: "string",
|
|
2237
|
-
description: "A natural, warm farewell message for the user. Must NOT contain task names, field names, collected data, or any internal/technical information.",
|
|
2238
|
-
},
|
|
2239
|
-
},
|
|
2240
|
-
required: ["message"],
|
|
2241
|
-
additionalProperties: false,
|
|
2242
|
-
};
|
|
2243
|
-
|
|
2244
|
-
const templateContext = createTemplateContext({ context, session, history: historyEvents });
|
|
2245
|
-
|
|
2246
|
-
// Build completion response prompt using ResponseEngine
|
|
2247
|
-
// Filter out conditional guidelines - only include always-active ones
|
|
2248
|
-
const alwaysActiveGuidelines = [
|
|
2249
|
-
...this.agent.getGuidelines().filter(g => !g.condition),
|
|
2250
|
-
...selectedRoute.getGuidelines().filter(g => !g.condition),
|
|
2251
|
-
];
|
|
2252
|
-
let completitionPrompt = "Send a brief, natural farewell message. Do NOT mention internal data or task details."
|
|
2253
|
-
if (endStepSpec.prompt) {
|
|
2254
|
-
completitionPrompt = await render(endStepSpec.prompt, templateContext)
|
|
2255
|
-
}
|
|
2570
|
+
}): Promise<SessionState<TData>> {
|
|
2571
|
+
const { selectedFlow, session, context } = params;
|
|
2256
2572
|
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
rules: selectedRoute.getRules(),
|
|
2261
|
-
prohibitions: selectedRoute.getProhibitions(),
|
|
2262
|
-
directives: [
|
|
2263
|
-
"The conversation task has been completed successfully",
|
|
2264
|
-
"Generate a natural, friendly farewell message for the user",
|
|
2265
|
-
"Do NOT mention task names, route names, collected data, field names, or any internal/technical information",
|
|
2266
|
-
"Do NOT list or summarize the data you collected - the user already knows what they told you",
|
|
2267
|
-
"Do NOT use words like 'tarefa', 'dados coletados', 'prospecção', 'concluída' or similar internal terms",
|
|
2268
|
-
"Keep it brief, warm, and conversational - as if ending a natural conversation",
|
|
2269
|
-
"Do NOT ask for more information - the conversation is ending",
|
|
2270
|
-
completitionPrompt,
|
|
2271
|
-
],
|
|
2272
|
-
history: historyEvents,
|
|
2273
|
-
agentOptions: this.agent.getAgentOptions(),
|
|
2274
|
-
combinedGuidelines: alwaysActiveGuidelines, // Only non-conditional guidelines
|
|
2275
|
-
combinedTerms: this.mergeTerms(this.agent.getTerms(), selectedRoute.getTerms()),
|
|
2573
|
+
// 1) Evaluate onComplete first — needs the still-active session shape.
|
|
2574
|
+
const transitionConfig = await selectedFlow.evaluateOnComplete(
|
|
2575
|
+
{ data: session.data },
|
|
2276
2576
|
context,
|
|
2277
|
-
|
|
2278
|
-
agentSchema: undefined, // No data collection schema for completion
|
|
2279
|
-
});
|
|
2280
|
-
|
|
2281
|
-
// Generate completion message using AI provider
|
|
2282
|
-
const agentOptions = this.agent.getAgentOptions();
|
|
2283
|
-
logger.debug(`[ResponseModal] Calling AI provider for completion message...`);
|
|
2577
|
+
);
|
|
2284
2578
|
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2579
|
+
// 2) Release to idle. If the flow is reentrant, scrub its owned
|
|
2580
|
+
// fields so re-selection on a future turn starts clean. When
|
|
2581
|
+
// onComplete fires we still go idle here — the next turn's
|
|
2582
|
+
// pipeline applies the pendingDirective before any routing.
|
|
2583
|
+
const ownedFields = selectedFlow.reentrant
|
|
2584
|
+
? [
|
|
2585
|
+
...(selectedFlow.requiredFields ?? []),
|
|
2586
|
+
...(selectedFlow.optionalFields ?? []),
|
|
2587
|
+
]
|
|
2588
|
+
: undefined;
|
|
2589
|
+
|
|
2590
|
+
let nextSession = completeCurrentFlow(session, {
|
|
2591
|
+
clearOwnedFields: ownedFields,
|
|
2291
2592
|
});
|
|
2292
2593
|
|
|
2293
|
-
|
|
2294
|
-
const message = completionResult.structured?.message || completionResult.message;
|
|
2295
|
-
logger.debug(`[ResponseModal] Generated completion message for route: ${selectedRoute.title}`);
|
|
2296
|
-
|
|
2297
|
-
// Check for onComplete transition
|
|
2298
|
-
const transitionConfig = await selectedRoute.evaluateOnComplete({ data: session.data }, context);
|
|
2299
|
-
|
|
2594
|
+
// 3) Wire pendingDirective when onComplete returned a target.
|
|
2300
2595
|
if (transitionConfig) {
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2596
|
+
const goToTarget = typeof transitionConfig.goTo === 'string'
|
|
2597
|
+
? transitionConfig.goTo
|
|
2598
|
+
: transitionConfig.goTo?.flow;
|
|
2599
|
+
|
|
2600
|
+
const targetFlow = goToTarget ? this.agent.getFlows().find(
|
|
2601
|
+
(r) =>
|
|
2602
|
+
r.id === goToTarget ||
|
|
2603
|
+
r.title === goToTarget,
|
|
2604
|
+
) : undefined;
|
|
2605
|
+
|
|
2606
|
+
if (targetFlow) {
|
|
2607
|
+
nextSession = {
|
|
2608
|
+
...nextSession,
|
|
2609
|
+
pendingDirective: {
|
|
2610
|
+
goTo: targetFlow.id,
|
|
2611
|
+
},
|
|
2313
2612
|
};
|
|
2314
|
-
logger.debug(
|
|
2315
|
-
|
|
2316
|
-
|
|
2613
|
+
logger.debug(
|
|
2614
|
+
`[ResponseModal] Flow ${selectedFlow.title} completed with pending directive to: ${targetFlow.title}`,
|
|
2615
|
+
);
|
|
2616
|
+
} else if (goToTarget) {
|
|
2617
|
+
logger.warn(
|
|
2618
|
+
`[FlowConfigurationError] onComplete target not found: flow "${selectedFlow.title}" completed but onComplete target "${goToTarget}" does not match any flow. ` +
|
|
2619
|
+
`Fix the onComplete value to reference an existing flow id/title, or remove onComplete to release the session to idle.`,
|
|
2620
|
+
);
|
|
2317
2621
|
}
|
|
2622
|
+
} else {
|
|
2623
|
+
logger.debug(
|
|
2624
|
+
`[ResponseModal] Flow ${selectedFlow.title} completed; session released to idle.`,
|
|
2625
|
+
);
|
|
2318
2626
|
}
|
|
2319
2627
|
|
|
2320
|
-
return
|
|
2628
|
+
return nextSession;
|
|
2321
2629
|
}
|
|
2322
2630
|
|
|
2323
2631
|
/**
|
|
2324
|
-
* Stream
|
|
2632
|
+
* Stream flow completion response
|
|
2633
|
+
* @private
|
|
2634
|
+
*/
|
|
2635
|
+
/**
|
|
2636
|
+
* Stream a flow completion as a single terminal chunk.
|
|
2637
|
+
*
|
|
2638
|
+
* No LLM call is made. The framework no longer authors a farewell — the
|
|
2639
|
+
* completion path is a pure state transition. The chunk emits an empty
|
|
2640
|
+
* `delta` and a `done: true` flag with the idle session attached so
|
|
2641
|
+
* downstream consumers can finalize cleanly.
|
|
2642
|
+
*
|
|
2643
|
+
* If the developer wants closing copy in a streaming response, they
|
|
2644
|
+
* should add a final interactive step whose own LLM turn delivers it.
|
|
2645
|
+
*
|
|
2325
2646
|
* @private
|
|
2326
2647
|
*/
|
|
2327
|
-
private async *
|
|
2328
|
-
|
|
2648
|
+
private async *streamFlowCompletion(params: {
|
|
2649
|
+
selectedFlow: Flow<TContext, TData>;
|
|
2329
2650
|
session: SessionState<TData>;
|
|
2330
2651
|
context: TContext;
|
|
2331
2652
|
history: HistoryItem[];
|
|
2332
2653
|
historyEvents: Event[];
|
|
2654
|
+
stoppedReason?: StoppedReason;
|
|
2333
2655
|
signal?: AbortSignal;
|
|
2334
2656
|
}): AsyncGenerator<AgentResponseStreamChunk<TData>> {
|
|
2335
|
-
const {
|
|
2336
|
-
let session = params.session;
|
|
2337
|
-
|
|
2338
|
-
// Get endStep spec from route
|
|
2339
|
-
const endStepSpec = selectedRoute.endStepSpec;
|
|
2340
|
-
|
|
2341
|
-
// Create a temporary step for completion message generation using endStep configuration
|
|
2342
|
-
const completionStep = new Step<TContext, TData>(selectedRoute.id, {
|
|
2343
|
-
description: endStepSpec.description,
|
|
2344
|
-
id: endStepSpec.id || END_ROUTE_ID,
|
|
2345
|
-
collect: endStepSpec.collect,
|
|
2346
|
-
requires: endStepSpec.requires,
|
|
2347
|
-
prompt: endStepSpec.prompt || "Send a brief, natural farewell message thanking the user. Do NOT list or mention any collected data, field names, or internal information.",
|
|
2348
|
-
});
|
|
2657
|
+
const { selectedFlow, context, history } = params;
|
|
2349
2658
|
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
// Build completion response prompt
|
|
2355
|
-
const completionPrompt = await this.responseEngine.buildResponsePrompt({
|
|
2356
|
-
route: selectedRoute,
|
|
2357
|
-
currentStep: completionStep,
|
|
2358
|
-
rules: selectedRoute.getRules(),
|
|
2359
|
-
prohibitions: selectedRoute.getProhibitions(),
|
|
2360
|
-
directives: undefined, // No directives for completion
|
|
2361
|
-
history: historyEvents,
|
|
2362
|
-
agentOptions: this.agent.getAgentOptions(),
|
|
2363
|
-
combinedGuidelines: [...this.agent.getGuidelines(), ...selectedRoute.getGuidelines()],
|
|
2364
|
-
combinedTerms: this.mergeTerms(this.agent.getTerms(), selectedRoute.getTerms()),
|
|
2365
|
-
context,
|
|
2366
|
-
session,
|
|
2367
|
-
agentSchema: this.agent.getSchema(),
|
|
2368
|
-
});
|
|
2369
|
-
|
|
2370
|
-
// Stream completion message using AI provider
|
|
2371
|
-
const agentOptions = this.agent.getAgentOptions();
|
|
2372
|
-
const stream = agentOptions.provider.generateMessageStream({
|
|
2373
|
-
prompt: completionPrompt,
|
|
2374
|
-
history, // Use HistoryItem[] for AI provider
|
|
2659
|
+
const session = await this.applyFlowCompletion({
|
|
2660
|
+
selectedFlow,
|
|
2661
|
+
session: params.session,
|
|
2375
2662
|
context,
|
|
2376
|
-
|
|
2377
|
-
parameters: { jsonSchema: responseSchema, schemaName: "completion_message_stream" },
|
|
2663
|
+
history,
|
|
2378
2664
|
});
|
|
2379
2665
|
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
// Check for onComplete transition
|
|
2383
|
-
const transitionConfig = await selectedRoute.evaluateOnComplete({ data: session.data }, context);
|
|
2384
|
-
|
|
2385
|
-
if (transitionConfig) {
|
|
2386
|
-
// Find target route by ID or title
|
|
2387
|
-
const targetRoute = this.agent.getRoutes().find(
|
|
2388
|
-
(r) => r.id === transitionConfig.nextStep || r.title === transitionConfig.nextStep
|
|
2389
|
-
);
|
|
2390
|
-
|
|
2391
|
-
if (targetRoute) {
|
|
2392
|
-
const renderedCondition = await render(transitionConfig.condition, templateContext);
|
|
2393
|
-
// Set pending transition in session
|
|
2394
|
-
session = {
|
|
2395
|
-
...session,
|
|
2396
|
-
pendingTransition: {
|
|
2397
|
-
targetRouteId: targetRoute.id,
|
|
2398
|
-
condition: renderedCondition,
|
|
2399
|
-
reason: "route_complete",
|
|
2400
|
-
},
|
|
2401
|
-
};
|
|
2402
|
-
logger.debug(`[ResponseModal] Route ${selectedRoute.title} completed with pending transition to: ${targetRoute.title}`);
|
|
2403
|
-
} else {
|
|
2404
|
-
logger.warn(`[ResponseModal] Route ${selectedRoute.title} completed but target route not found: ${transitionConfig.nextStep}`);
|
|
2405
|
-
}
|
|
2406
|
-
}
|
|
2407
|
-
|
|
2408
|
-
// Set step to END_ROUTE marker
|
|
2409
|
-
session = enterStep(session, END_ROUTE_ID, "Route completed");
|
|
2410
|
-
logger.debug(`[ResponseModal] Route ${selectedRoute.title} completed. Entered END_ROUTE step.`);
|
|
2411
|
-
|
|
2412
|
-
// Stream completion chunks
|
|
2413
|
-
for await (const chunk of stream) {
|
|
2414
|
-
// Update current session if we have one
|
|
2415
|
-
if (chunk.done) {
|
|
2416
|
-
await this.finalizeSession(session, context);
|
|
2417
|
-
}
|
|
2666
|
+
await this.finalizeSession(session, context);
|
|
2418
2667
|
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
isRouteComplete: true,
|
|
2430
|
-
executedSteps: chunk.done ? [] : undefined,
|
|
2431
|
-
stoppedReason: chunk.done ? 'route_complete' : undefined,
|
|
2432
|
-
metadata: chunk.metadata,
|
|
2433
|
-
structured: chunk.structured,
|
|
2434
|
-
};
|
|
2435
|
-
}
|
|
2668
|
+
yield {
|
|
2669
|
+
delta: '',
|
|
2670
|
+
accumulated: '',
|
|
2671
|
+
done: true,
|
|
2672
|
+
session,
|
|
2673
|
+
toolCalls: undefined,
|
|
2674
|
+
isFlowComplete: true,
|
|
2675
|
+
executedSteps: [],
|
|
2676
|
+
stoppedReason: params.stoppedReason ?? 'completed',
|
|
2677
|
+
};
|
|
2436
2678
|
}
|
|
2437
2679
|
|
|
2438
2680
|
/**
|
|
2439
|
-
* Generate fallback response when no
|
|
2681
|
+
* Generate fallback response when no flows are available
|
|
2440
2682
|
* @private
|
|
2441
2683
|
*/
|
|
2442
2684
|
private async generateFallbackResponse(params: {
|
|
@@ -2444,16 +2686,16 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
2444
2686
|
context: TContext;
|
|
2445
2687
|
session: SessionState<TData>;
|
|
2446
2688
|
signal?: AbortSignal;
|
|
2447
|
-
}): Promise<string> {
|
|
2689
|
+
}): Promise<{ message: string; appliedInstructions?: AppliedInstruction[] }> {
|
|
2448
2690
|
const { history, context, session, signal } = params;
|
|
2449
2691
|
|
|
2450
|
-
logger.debug(`[ResponseModal] No
|
|
2692
|
+
logger.debug(`[ResponseModal] No flow selected, generating basic response`);
|
|
2451
2693
|
|
|
2452
|
-
// Build basic response prompt without
|
|
2453
|
-
const fallbackPrompt = await this.responseEngine.buildFallbackPrompt({
|
|
2694
|
+
// Build basic response prompt without flow context
|
|
2695
|
+
const { prompt: fallbackPrompt, appliedInstructions } = await this.responseEngine.buildFallbackPrompt({
|
|
2454
2696
|
agentOptions: this.agent.getAgentOptions(),
|
|
2455
2697
|
terms: this.agent.getTerms(),
|
|
2456
|
-
|
|
2698
|
+
instructions: this.collectScopedInstructions(),
|
|
2457
2699
|
context,
|
|
2458
2700
|
session,
|
|
2459
2701
|
});
|
|
@@ -2475,11 +2717,11 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
2475
2717
|
},
|
|
2476
2718
|
});
|
|
2477
2719
|
|
|
2478
|
-
return result.structured?.message || result.message;
|
|
2720
|
+
return { message: result.structured?.message || result.message, appliedInstructions };
|
|
2479
2721
|
}
|
|
2480
2722
|
|
|
2481
2723
|
/**
|
|
2482
|
-
* Stream fallback response when no
|
|
2724
|
+
* Stream fallback response when no flows are available
|
|
2483
2725
|
* @private
|
|
2484
2726
|
*/
|
|
2485
2727
|
private async *streamFallbackResponse(params: {
|
|
@@ -2490,10 +2732,10 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
2490
2732
|
}): AsyncGenerator<AgentResponseStreamChunk<TData>> {
|
|
2491
2733
|
const { history, context, session, signal } = params;
|
|
2492
2734
|
|
|
2493
|
-
const fallbackPrompt = await this.responseEngine.buildFallbackPrompt({
|
|
2735
|
+
const { prompt: fallbackPrompt, appliedInstructions } = await this.responseEngine.buildFallbackPrompt({
|
|
2494
2736
|
agentOptions: this.agent.getAgentOptions(),
|
|
2495
2737
|
terms: this.agent.getTerms(),
|
|
2496
|
-
|
|
2738
|
+
instructions: this.collectScopedInstructions(),
|
|
2497
2739
|
context,
|
|
2498
2740
|
session,
|
|
2499
2741
|
});
|
|
@@ -2522,8 +2764,8 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
2522
2764
|
}
|
|
2523
2765
|
|
|
2524
2766
|
// Response structure completeness (Requirement 8.1, 8.2, 8.3)
|
|
2525
|
-
// - executedSteps: empty for fallback (no
|
|
2526
|
-
// - stoppedReason: undefined for fallback (no
|
|
2767
|
+
// - executedSteps: empty for fallback (no flow/step execution)
|
|
2768
|
+
// - stoppedReason: undefined for fallback (no flow context)
|
|
2527
2769
|
// - session.currentStep: unchanged (no step progression)
|
|
2528
2770
|
yield {
|
|
2529
2771
|
delta: chunk.delta,
|
|
@@ -2531,11 +2773,12 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
2531
2773
|
done: chunk.done,
|
|
2532
2774
|
session,
|
|
2533
2775
|
toolCalls: undefined,
|
|
2534
|
-
|
|
2776
|
+
isFlowComplete: false,
|
|
2535
2777
|
executedSteps: chunk.done ? [] : undefined,
|
|
2536
2778
|
stoppedReason: undefined,
|
|
2537
2779
|
metadata: chunk.metadata,
|
|
2538
2780
|
structured: chunk.structured,
|
|
2781
|
+
appliedInstructions: chunk.done ? appliedInstructions : undefined,
|
|
2539
2782
|
};
|
|
2540
2783
|
}
|
|
2541
2784
|
}
|
|
@@ -2561,9 +2804,9 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
2561
2804
|
await this.executeStepFinalize(session, context);
|
|
2562
2805
|
|
|
2563
2806
|
// Update current session if we have one
|
|
2564
|
-
const currentSession = this.agent.
|
|
2807
|
+
const currentSession = this.agent.currentSession;
|
|
2565
2808
|
if (currentSession) {
|
|
2566
|
-
this.agent.
|
|
2809
|
+
this.agent.currentSession = session;
|
|
2567
2810
|
}
|
|
2568
2811
|
}
|
|
2569
2812
|
// ============================================================================
|
|
@@ -2572,45 +2815,45 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
2572
2815
|
|
|
2573
2816
|
|
|
2574
2817
|
/**
|
|
2575
|
-
* Find an available tool by name for the given
|
|
2818
|
+
* Find an available tool by name for the given flow using ToolManager
|
|
2576
2819
|
* Delegates to ToolManager for unified tool resolution
|
|
2577
2820
|
* @private
|
|
2578
2821
|
*/
|
|
2579
2822
|
private findAvailableTool(
|
|
2580
2823
|
toolName: string,
|
|
2581
|
-
|
|
2824
|
+
flow?: Flow<TContext, TData>
|
|
2582
2825
|
): Tool<TContext, TData> | undefined {
|
|
2583
2826
|
// Use ToolManager for unified tool resolution
|
|
2584
2827
|
const toolManager = this.getToolManager();
|
|
2585
2828
|
if (toolManager) {
|
|
2586
|
-
return toolManager.find(toolName, undefined, undefined,
|
|
2829
|
+
return toolManager.find(toolName, undefined, undefined, flow);
|
|
2587
2830
|
}
|
|
2588
2831
|
|
|
2589
2832
|
// Fallback to legacy resolution if ToolManager not available
|
|
2590
2833
|
logger.warn(`[ResponseModal] ToolManager not available, using legacy tool resolution for: ${toolName}`);
|
|
2591
2834
|
|
|
2592
|
-
// Check
|
|
2593
|
-
if (
|
|
2594
|
-
const
|
|
2835
|
+
// Check flow-level tools first (if flow provided)
|
|
2836
|
+
if (flow) {
|
|
2837
|
+
const flowTool = flow
|
|
2595
2838
|
.getTools()
|
|
2596
|
-
.find((tool: Tool<TContext, TData>) => tool.id === toolName || tool.
|
|
2597
|
-
if (
|
|
2839
|
+
.find((tool: Tool<TContext, TData>) => tool.id === toolName || tool.id === toolName);
|
|
2840
|
+
if (flowTool) return flowTool;
|
|
2598
2841
|
}
|
|
2599
2842
|
|
|
2600
2843
|
// Fall back to agent-level tools
|
|
2601
2844
|
const agentTools = this.agent.getTools();
|
|
2602
2845
|
return agentTools.find(
|
|
2603
|
-
(tool) => tool.id === toolName || tool.
|
|
2846
|
+
(tool) => tool.id === toolName || tool.id === toolName
|
|
2604
2847
|
);
|
|
2605
2848
|
}
|
|
2606
2849
|
|
|
2607
2850
|
/**
|
|
2608
|
-
* Collect all available tools for the given
|
|
2851
|
+
* Collect all available tools for the given flow and step context using ToolManager
|
|
2609
2852
|
* Delegates to ToolManager for unified tool resolution and deduplication
|
|
2610
2853
|
* @private
|
|
2611
2854
|
*/
|
|
2612
2855
|
private collectAvailableTools(
|
|
2613
|
-
|
|
2856
|
+
flow?: Flow<TContext, TData>,
|
|
2614
2857
|
step?: Step<TContext, TData>
|
|
2615
2858
|
): Array<{
|
|
2616
2859
|
id: string;
|
|
@@ -2621,10 +2864,10 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
2621
2864
|
// Use ToolManager for unified tool collection if available
|
|
2622
2865
|
const toolManager = this.getToolManager();
|
|
2623
2866
|
if (toolManager) {
|
|
2624
|
-
const availableTools = toolManager.getAvailable(undefined, step,
|
|
2867
|
+
const availableTools = toolManager.getAvailable(undefined, step, flow);
|
|
2625
2868
|
return availableTools.map((tool) => ({
|
|
2626
2869
|
id: tool.id,
|
|
2627
|
-
name: tool.
|
|
2870
|
+
name: tool.id || tool.id,
|
|
2628
2871
|
description: tool.description,
|
|
2629
2872
|
parameters: tool.parameters,
|
|
2630
2873
|
}));
|
|
@@ -2640,9 +2883,9 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
2640
2883
|
availableTools.set(tool.id, tool);
|
|
2641
2884
|
});
|
|
2642
2885
|
|
|
2643
|
-
// Add
|
|
2644
|
-
if (
|
|
2645
|
-
|
|
2886
|
+
// Add flow-level tools (these take precedence)
|
|
2887
|
+
if (flow) {
|
|
2888
|
+
flow.getTools().forEach((tool: Tool<TContext, TData>) => {
|
|
2646
2889
|
availableTools.set(tool.id, tool);
|
|
2647
2890
|
});
|
|
2648
2891
|
}
|
|
@@ -2688,7 +2931,7 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
2688
2931
|
// Convert to the format expected by AI providers
|
|
2689
2932
|
return Array.from(availableTools.values()).map((tool) => ({
|
|
2690
2933
|
id: tool.id,
|
|
2691
|
-
name: tool.
|
|
2934
|
+
name: tool.id || tool.id,
|
|
2692
2935
|
description: tool.description,
|
|
2693
2936
|
parameters: tool.parameters,
|
|
2694
2937
|
}));
|
|
@@ -2702,11 +2945,11 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
2702
2945
|
prepareOrFinalize:
|
|
2703
2946
|
| string
|
|
2704
2947
|
| Tool<TContext, TData>
|
|
2705
|
-
| ((context: TContext, data?: Partial<TData>) => void | Promise<void>)
|
|
2948
|
+
| ((context: TContext, data?: Partial<TData>) => void | PrepareResult | Promise<void | PrepareResult>)
|
|
2706
2949
|
| undefined,
|
|
2707
2950
|
context: TContext,
|
|
2708
2951
|
data?: Partial<TData>,
|
|
2709
|
-
|
|
2952
|
+
flow?: Flow<TContext, TData>,
|
|
2710
2953
|
step?: Step<TContext, TData>
|
|
2711
2954
|
): Promise<void> {
|
|
2712
2955
|
if (!prepareOrFinalize) return;
|
|
@@ -2722,7 +2965,7 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
2722
2965
|
// Tool ID - use ToolManager for unified resolution
|
|
2723
2966
|
const toolManager = this.getToolManager();
|
|
2724
2967
|
if (toolManager) {
|
|
2725
|
-
tool = toolManager.find(prepareOrFinalize, undefined, step,
|
|
2968
|
+
tool = toolManager.find(prepareOrFinalize, undefined, step, flow);
|
|
2726
2969
|
} else {
|
|
2727
2970
|
// Fallback to legacy resolution if ToolManager not available
|
|
2728
2971
|
logger.warn(`[ResponseModal] ToolManager not available, using legacy tool resolution for prepare/finalize: ${prepareOrFinalize}`);
|
|
@@ -2734,9 +2977,9 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
2734
2977
|
availableTools.set(t.id, t);
|
|
2735
2978
|
});
|
|
2736
2979
|
|
|
2737
|
-
// Add
|
|
2738
|
-
if (
|
|
2739
|
-
|
|
2980
|
+
// Add flow-level tools
|
|
2981
|
+
if (flow) {
|
|
2982
|
+
flow.getTools().forEach((t: Tool<TContext, TData>) => {
|
|
2740
2983
|
availableTools.set(t.id, t);
|
|
2741
2984
|
});
|
|
2742
2985
|
}
|
|
@@ -2795,30 +3038,4 @@ export class ResponseModal<TContext = unknown, TData = unknown> {
|
|
|
2795
3038
|
}
|
|
2796
3039
|
}
|
|
2797
3040
|
|
|
2798
|
-
/**
|
|
2799
|
-
* Merge terms with route-specific taking precedence on conflicts
|
|
2800
|
-
* @private
|
|
2801
|
-
*/
|
|
2802
|
-
private mergeTerms(
|
|
2803
|
-
agentTerms: Term<TContext, TData>[],
|
|
2804
|
-
routeTerms: Term<TContext, TData>[]
|
|
2805
|
-
): Term<TContext, TData>[] {
|
|
2806
|
-
const merged = new Map<string, Term<TContext, TData>>();
|
|
2807
|
-
|
|
2808
|
-
// Add agent terms first
|
|
2809
|
-
agentTerms.forEach((term) => {
|
|
2810
|
-
const name =
|
|
2811
|
-
typeof term.name === "string" ? term.name : term.name.toString();
|
|
2812
|
-
merged.set(name, term);
|
|
2813
|
-
});
|
|
2814
|
-
|
|
2815
|
-
// Add route terms (these take precedence)
|
|
2816
|
-
routeTerms.forEach((term) => {
|
|
2817
|
-
const name =
|
|
2818
|
-
typeof term.name === "string" ? term.name : term.name.toString();
|
|
2819
|
-
merged.set(name, term);
|
|
2820
|
-
});
|
|
2821
|
-
|
|
2822
|
-
return Array.from(merged.values());
|
|
2823
|
-
}
|
|
2824
3041
|
}
|